@@ -3,18 +3,22 @@ package io.iohk.ethereum.ledger
33import akka .testkit .TestProbe
44import akka .util .ByteString
55import cats .data .NonEmptyList
6+ import io .iohk .ethereum .blockchain .sync .regular .BlockImporter .NewCheckpoint
67import io .iohk .ethereum .blockchain .sync .regular .{BlockFetcher , BlockImporter }
8+ import io .iohk .ethereum .checkpointing .CheckpointingTestHelpers
79import io .iohk .ethereum .consensus .blocks .CheckpointBlockGenerator
810import io .iohk .ethereum .domain ._
911import io .iohk .ethereum .mpt .MerklePatriciaTrie
1012import io .iohk .ethereum .utils .Config .SyncConfig
1113import io .iohk .ethereum .utils .Config
12- import io .iohk .ethereum .Fixtures
14+ import io .iohk .ethereum .{Fixtures , ObjectGenerators , crypto }
15+ import io .iohk .ethereum .ledger .Ledger .BlockResult
1316import monix .execution .Scheduler
1417import org .scalamock .scalatest .MockFactory
1518import org .scalatest .BeforeAndAfterAll
1619import org .scalatest .flatspec .AsyncFlatSpecLike
1720import org .scalatest .matchers .should .Matchers
21+
1822import scala .concurrent .duration ._
1923
2024class BlockImporterItSpec extends MockFactory with TestSetupWithVmAndValidators with AsyncFlatSpecLike with Matchers with BeforeAndAfterAll {
@@ -26,17 +30,15 @@ class BlockImporterItSpec extends MockFactory with TestSetupWithVmAndValidators
2630 testScheduler.awaitTermination(60 .second)
2731 }
2832
29- val bl = BlockchainImpl (storagesInstance.storages)
30-
31- val blockQueue = BlockQueue (bl, SyncConfig (Config .config))
33+ val blockQueue = BlockQueue (blockchain, SyncConfig (Config .config))
3234
3335 val genesis = Block (
3436 Fixtures .Blocks .Genesis .header.copy(stateRoot = ByteString (MerklePatriciaTrie .EmptyRootHash )),
3537 Fixtures .Blocks .Genesis .body
3638 )
3739 val genesisWeight = ChainWeight .zero.increase(genesis.header)
3840
39- bl .save(genesis, Seq (), genesisWeight, saveAsBestBlock = true )
41+ blockchain .save(genesis, Seq (), genesisWeight, saveAsBestBlock = true )
4042
4143 lazy val checkpointBlockGenerator : CheckpointBlockGenerator = new CheckpointBlockGenerator
4244
@@ -46,15 +48,27 @@ class BlockImporterItSpec extends MockFactory with TestSetupWithVmAndValidators
4648 val pendingTransactionsManagerProbe = TestProbe ()
4749 val supervisor = TestProbe ()
4850
49- override lazy val ledger : TestLedgerImpl = new TestLedgerImpl (validators) {
50- override private [ledger] lazy val blockExecution = mock[BlockExecution ]
51+ val emptyWorld : InMemoryWorldStateProxy =
52+ blockchain.getWorldStateProxy(
53+ - 1 ,
54+ UInt256 .Zero ,
55+ ByteString (MerklePatriciaTrie .EmptyRootHash ),
56+ noEmptyAccounts = false ,
57+ ethCompatibleStorage = true
58+ )
59+
60+ override lazy val ledger = new TestLedgerImpl (successValidators) {
61+ override private [ledger] lazy val blockExecution = new BlockExecution (blockchain, blockchainConfig, consensus.blockPreparator, blockValidation) {
62+ override def executeAndValidateBlock (block : Block , alreadyValidated : Boolean = false ): Either [BlockExecutionError , Seq [Receipt ]] =
63+ Right (BlockResult (emptyWorld).receipts)
64+ }
5165 }
5266
5367 val blockImporter = system.actorOf(
5468 BlockImporter .props(
5569 fetcherProbe.ref,
5670 ledger,
57- bl ,
71+ blockchain ,
5872 syncConfig,
5973 ommersPoolProbe.ref,
6074 broadcasterProbe.ref,
@@ -63,46 +77,123 @@ class BlockImporterItSpec extends MockFactory with TestSetupWithVmAndValidators
6377 supervisor.ref
6478 ))
6579
66- " BlockImporter" should " return a correct new best block after reorganising longer chain to a shorter one" in {
67-
68- val genesis = bl.getBestBlock()
69- val block1 : Block = getBlock(genesis.number + 1 , parent = genesis.header.hash)
70- // new chain is shorter but has a higher weight
71- val newBlock2 : Block = getBlock(genesis.number + 2 , difficulty = 101 , parent = block1.header.hash)
72- val newBlock3 : Block = getBlock(genesis.number + 3 , difficulty = 333 , parent = newBlock2.header.hash)
73- val oldBlock2 : Block = getBlock(genesis.number + 2 , difficulty = 102 , parent = block1.header.hash)
74- val oldBlock3 : Block = getBlock(genesis.number + 3 , difficulty = 103 , parent = oldBlock2.header.hash)
75- val oldBlock4 : Block = getBlock(genesis.number + 4 , difficulty = 104 , parent = oldBlock3.header.hash)
76-
77- val weight1 = ChainWeight .totalDifficultyOnly(block1.header.difficulty + 999 )
78- val newWeight2 = weight1.increase(newBlock2.header)
79- val newWeight3 = newWeight2.increase(newBlock3.header)
80- val oldWeight2 = weight1.increase(oldBlock2.header)
81- val oldWeight3 = oldWeight2.increase(oldBlock3.header)
82- val oldWeight4 = oldWeight3.increase(oldBlock4.header)
83-
84- // saving initial main chain
85- bl.save(block1, Nil , weight1, saveAsBestBlock = true )
86- bl.save(oldBlock2, Nil , oldWeight2, saveAsBestBlock = true )
87- bl.save(oldBlock3, Nil , oldWeight3, saveAsBestBlock = true )
88- bl.save(oldBlock4, Nil , oldWeight4, saveAsBestBlock = true )
89-
90- val blockData2 = BlockData (newBlock2, Seq .empty[Receipt ], newWeight2)
91- val blockData3 = BlockData (newBlock3, Seq .empty[Receipt ], newWeight3)
80+ val genesisBlock = blockchain.genesisBlock
81+ val block1 : Block = getBlock(genesisBlock.number + 1 , parent = genesisBlock.header.hash)
82+ // new chain is shorter but has a higher weight
83+ val newBlock2 : Block = getBlock(genesisBlock.number + 2 , difficulty = 108 , parent = block1.header.hash)
84+ val newBlock3 : Block = getBlock(genesisBlock.number + 3 , difficulty = 300 , parent = newBlock2.header.hash)
85+ val oldBlock2 : Block = getBlock(genesisBlock.number + 2 , difficulty = 102 , parent = block1.header.hash)
86+ val oldBlock3 : Block = getBlock(genesisBlock.number + 3 , difficulty = 103 , parent = oldBlock2.header.hash)
87+ val oldBlock4 : Block = getBlock(genesisBlock.number + 4 , difficulty = 104 , parent = oldBlock3.header.hash)
88+
89+ val weight1 = ChainWeight .totalDifficultyOnly(block1.header.difficulty)
90+ val newWeight2 = weight1.increase(newBlock2.header)
91+ val newWeight3 = newWeight2.increase(newBlock3.header)
92+ val oldWeight2 = weight1.increase(oldBlock2.header)
93+ val oldWeight3 = oldWeight2.increase(oldBlock3.header)
94+ val oldWeight4 = oldWeight3.increase(oldBlock4.header)
95+
96+ // saving initial main chain
97+ blockchain.save(block1, Nil , weight1, saveAsBestBlock = true )
98+ blockchain.save(oldBlock2, Nil , oldWeight2, saveAsBestBlock = true )
99+ blockchain.save(oldBlock3, Nil , oldWeight3, saveAsBestBlock = true )
100+ blockchain.save(oldBlock4, Nil , oldWeight4, saveAsBestBlock = true )
101+
102+ val oldBranch = List (oldBlock2, oldBlock3, oldBlock4)
103+ val newBranch = List (newBlock2, newBlock3)
104+
105+ blockImporter ! BlockImporter .Start
106+
107+ /** TODO: this should not behave like that, but instead return a proper error and revert reorganisation to the initial chain. Currently if we had a chain, and then
108+ reorganisation started and error occurred in BlockExecution.executeAndValidateBlock() we end up with a discarded main chain to the point of the common parent**/
109+
110+ " BlockImporter" should " (not) discard blocks of the main chain if the reorganisation failed" in {
111+
112+ // ledger with not mocked blockExecution
113+ val ledger = new TestLedgerImpl (successValidators)
114+ val blockImporter = system.actorOf(
115+ BlockImporter .props(
116+ fetcherProbe.ref,
117+ ledger,
118+ blockchain,
119+ syncConfig,
120+ ommersPoolProbe.ref,
121+ broadcasterProbe.ref,
122+ pendingTransactionsManagerProbe.ref,
123+ checkpointBlockGenerator,
124+ supervisor.ref
125+ ))
126+
127+ blockImporter ! BlockImporter .Start
128+ blockImporter ! BlockFetcher .PickedBlocks (NonEmptyList .fromListUnsafe(newBranch))
129+
130+ Thread .sleep(1000 )
131+ // because the blocks are not valid, we shouldn't reorganise, but at least stay with a current chain, and the best block of the current chain is oldBlock4
132+ blockchain.getBestBlock().get shouldEqual block1
133+ }
134+
135+ it should " return a correct new best block after reorganising longer chain to a shorter one" in {
136+
137+ // returning discarded initial chain
138+ blockchain.save(oldBlock2, Nil , oldWeight2, saveAsBestBlock = true )
139+ blockchain.save(oldBlock3, Nil , oldWeight3, saveAsBestBlock = true )
140+ blockchain.save(oldBlock4, Nil , oldWeight4, saveAsBestBlock = true )
141+
142+ blockImporter ! BlockFetcher .PickedBlocks (NonEmptyList .fromListUnsafe(newBranch))
143+
144+ Thread .sleep(200 )
145+ blockchain.getBestBlock().get shouldEqual newBlock3
146+ }
147+
148+
149+ it should " switch to a branch with a checkpoint" in {
150+
151+ val chackpoint = ObjectGenerators .fakeCheckpointGen(3 , 3 ).sample.get
152+ val oldBlock5WithCheckpoint : Block = checkpointBlockGenerator.generate(oldBlock4, chackpoint)
153+ blockchain.save(oldBlock5WithCheckpoint, Nil , oldWeight4, saveAsBestBlock = true )
92154
93155 val newBranch = List (newBlock2, newBlock3)
94156
95- blockImporter ! BlockImporter .Start
96157 blockImporter ! BlockFetcher .PickedBlocks (NonEmptyList .fromListUnsafe(newBranch))
97158
98- (ledger.blockExecution.executeAndValidateBlocks _)
99- .expects(newBranch, * )
100- .returning((List (blockData2, blockData3), None ))
159+ Thread .sleep(200 )
160+ blockchain.getBestBlock().get shouldEqual oldBlock5WithCheckpoint
161+ blockchain.getLatestCheckpointBlockNumber() shouldEqual oldBlock5WithCheckpoint.header.number
162+ }
163+
164+ it should " return a correct checkpointed block after reorganising longer chain to a shorter one and back" in {
165+
166+ val chackpoint = ObjectGenerators .fakeCheckpointGen(3 , 3 ).sample.get
167+ val newBlock4WithCheckpoint : Block = checkpointBlockGenerator.generate(newBlock3, chackpoint)
168+ blockchain.save(newBlock4WithCheckpoint, Nil , newWeight3, saveAsBestBlock = true )
169+
170+ val newBranch = List (newBlock4WithCheckpoint)
171+
172+ blockImporter ! BlockFetcher .PickedBlocks (NonEmptyList .fromListUnsafe(newBranch))
173+
174+ Thread .sleep(200 )
175+ blockchain.getBestBlock().get shouldEqual newBlock4WithCheckpoint
176+ blockchain.getLatestCheckpointBlockNumber() shouldEqual newBlock4WithCheckpoint.header.number
177+ }
178+
179+ it should " return a correct checkpointed block after receiving a new chackpoint from morpho" in {
180+
181+ val parent = blockchain.getBestBlock().get
182+ val newBlock5 : Block = getBlock(genesisBlock.number + 5 , difficulty = 104 , parent = parent.header.hash)
183+ val newWeight5 = newWeight3.increase(newBlock5.header)
184+
185+ blockchain.save(newBlock5, Nil , newWeight5, saveAsBestBlock = true )
186+
187+ val signatures = CheckpointingTestHelpers .createCheckpointSignatures(
188+ Seq (crypto.generateKeyPair(secureRandom)),
189+ newBlock5.hash
190+ )
191+ blockImporter ! NewCheckpoint (newBlock5.hash, signatures)
101192
102- // Saving new blocks, because it's part of executeBlocks method mechanism
103- bl.save(blockData2.block, blockData2.receipts, blockData2.weight, saveAsBestBlock = true )
104- bl.save(blockData3.block, blockData3.receipts, blockData3.weight, saveAsBestBlock = true )
193+ val checkpointBlock = checkpointBlockGenerator.generate(newBlock5, Checkpoint (signatures))
105194
106- bl.getBestBlock() shouldEqual newBlock3
195+ Thread .sleep(1000 )
196+ blockchain.getBestBlock().get shouldEqual checkpointBlock
197+ blockchain.getLatestCheckpointBlockNumber() shouldEqual newBlock5.header.number + 1
107198 }
108199}
0 commit comments