@@ -3,18 +3,22 @@ package io.iohk.ethereum.ledger
3
3
import akka .testkit .TestProbe
4
4
import akka .util .ByteString
5
5
import cats .data .NonEmptyList
6
+ import io .iohk .ethereum .blockchain .sync .regular .BlockImporter .NewCheckpoint
6
7
import io .iohk .ethereum .blockchain .sync .regular .{BlockFetcher , BlockImporter }
8
+ import io .iohk .ethereum .checkpointing .CheckpointingTestHelpers
7
9
import io .iohk .ethereum .consensus .blocks .CheckpointBlockGenerator
8
10
import io .iohk .ethereum .domain ._
9
11
import io .iohk .ethereum .mpt .MerklePatriciaTrie
10
12
import io .iohk .ethereum .utils .Config .SyncConfig
11
13
import 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
13
16
import monix .execution .Scheduler
14
17
import org .scalamock .scalatest .MockFactory
15
18
import org .scalatest .BeforeAndAfterAll
16
19
import org .scalatest .flatspec .AsyncFlatSpecLike
17
20
import org .scalatest .matchers .should .Matchers
21
+
18
22
import scala .concurrent .duration ._
19
23
20
24
class BlockImporterItSpec extends MockFactory with TestSetupWithVmAndValidators with AsyncFlatSpecLike with Matchers with BeforeAndAfterAll {
@@ -26,17 +30,15 @@ class BlockImporterItSpec extends MockFactory with TestSetupWithVmAndValidators
26
30
testScheduler.awaitTermination(60 .second)
27
31
}
28
32
29
- val bl = BlockchainImpl (storagesInstance.storages)
30
-
31
- val blockQueue = BlockQueue (bl, SyncConfig (Config .config))
33
+ val blockQueue = BlockQueue (blockchain, SyncConfig (Config .config))
32
34
33
35
val genesis = Block (
34
36
Fixtures .Blocks .Genesis .header.copy(stateRoot = ByteString (MerklePatriciaTrie .EmptyRootHash )),
35
37
Fixtures .Blocks .Genesis .body
36
38
)
37
39
val genesisWeight = ChainWeight .zero.increase(genesis.header)
38
40
39
- bl .save(genesis, Seq (), genesisWeight, saveAsBestBlock = true )
41
+ blockchain .save(genesis, Seq (), genesisWeight, saveAsBestBlock = true )
40
42
41
43
lazy val checkpointBlockGenerator : CheckpointBlockGenerator = new CheckpointBlockGenerator
42
44
@@ -46,15 +48,27 @@ class BlockImporterItSpec extends MockFactory with TestSetupWithVmAndValidators
46
48
val pendingTransactionsManagerProbe = TestProbe ()
47
49
val supervisor = TestProbe ()
48
50
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
+ }
51
65
}
52
66
53
67
val blockImporter = system.actorOf(
54
68
BlockImporter .props(
55
69
fetcherProbe.ref,
56
70
ledger,
57
- bl ,
71
+ blockchain ,
58
72
syncConfig,
59
73
ommersPoolProbe.ref,
60
74
broadcasterProbe.ref,
@@ -63,46 +77,123 @@ class BlockImporterItSpec extends MockFactory with TestSetupWithVmAndValidators
63
77
supervisor.ref
64
78
))
65
79
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 )
92
154
93
155
val newBranch = List (newBlock2, newBlock3)
94
156
95
- blockImporter ! BlockImporter .Start
96
157
blockImporter ! BlockFetcher .PickedBlocks (NonEmptyList .fromListUnsafe(newBranch))
97
158
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)
101
192
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))
105
194
106
- bl.getBestBlock() shouldEqual newBlock3
195
+ Thread .sleep(1000 )
196
+ blockchain.getBestBlock().get shouldEqual checkpointBlock
197
+ blockchain.getLatestCheckpointBlockNumber() shouldEqual newBlock5.header.number + 1
107
198
}
108
199
}
0 commit comments