Skip to content

Commit 499fe54

Browse files
committed
[ETCM-645] Add tests for checkpointing
1 parent a05c76a commit 499fe54

File tree

2 files changed

+135
-44
lines changed

2 files changed

+135
-44
lines changed

src/it/scala/io/iohk/ethereum/ledger/BlockImporterItSpec.scala

Lines changed: 133 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@ package io.iohk.ethereum.ledger
33
import akka.testkit.TestProbe
44
import akka.util.ByteString
55
import cats.data.NonEmptyList
6+
import io.iohk.ethereum.blockchain.sync.regular.BlockImporter.NewCheckpoint
67
import io.iohk.ethereum.blockchain.sync.regular.{BlockFetcher, BlockImporter}
8+
import io.iohk.ethereum.checkpointing.CheckpointingTestHelpers
79
import io.iohk.ethereum.consensus.blocks.CheckpointBlockGenerator
810
import io.iohk.ethereum.domain._
911
import io.iohk.ethereum.mpt.MerklePatriciaTrie
1012
import io.iohk.ethereum.utils.Config.SyncConfig
1113
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
1316
import monix.execution.Scheduler
1417
import org.scalamock.scalatest.MockFactory
1518
import org.scalatest.BeforeAndAfterAll
1619
import org.scalatest.flatspec.AsyncFlatSpecLike
1720
import org.scalatest.matchers.should.Matchers
21+
1822
import scala.concurrent.duration._
1923

2024
class 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
}

src/main/scala/io/iohk/ethereum/ledger/BlockValidation.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ class BlockValidation(consensus: Consensus, blockchain: Blockchain, blockQueue:
4343

4444
def validateBlockAfterExecution(
4545
block: Block,
46-
hash: ByteString,
46+
stateRootHash: ByteString,
4747
receipts: Seq[Receipt],
4848
gasUsed: BigInt
4949
): Either[BlockExecutionError, BlockExecutionSuccess] = {
5050
consensus.validators.validateBlockAfterExecution(
5151
block = block,
52-
stateRootHash = hash,
52+
stateRootHash = stateRootHash,
5353
receipts = receipts,
5454
gasUsed = gasUsed
5555
)

0 commit comments

Comments
 (0)