Skip to content

[Kaizen] Unflake BlockImporterItSpec #1047

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 10, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 121 additions & 119 deletions src/it/scala/io/iohk/ethereum/ledger/BlockImporterItSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import io.iohk.ethereum.utils.Config.SyncConfig

class BlockImporterItSpec
extends MockFactory
with TestSetupWithVmAndValidators
with AnyFlatSpecLike
with Matchers
with BeforeAndAfterAll
Expand All @@ -54,114 +53,9 @@ class BlockImporterItSpec
testScheduler.awaitTermination(60.second)
}

override lazy val blockQueue: BlockQueue = BlockQueue(blockchain, blockchainReader, SyncConfig(Config.config))

val genesis: Block = Block(
Fixtures.Blocks.Genesis.header.copy(stateRoot = ByteString(MerklePatriciaTrie.EmptyRootHash)),
Fixtures.Blocks.Genesis.body
)
val genesisWeight: ChainWeight = ChainWeight.zero.increase(genesis.header)

blockchainWriter.save(genesis, Seq(), genesisWeight, saveAsBestBlock = true)

lazy val checkpointBlockGenerator: CheckpointBlockGenerator = new CheckpointBlockGenerator

val fetcherProbe: TestProbe = TestProbe()
val ommersPoolProbe: TestProbe = TestProbe()
val broadcasterProbe: TestProbe = TestProbe()
val pendingTransactionsManagerProbe: TestProbe = TestProbe()
val supervisor: TestProbe = TestProbe()

val emptyWorld: InMemoryWorldStateProxy = InMemoryWorldStateProxy(
storagesInstance.storages.evmCodeStorage,
blockchain.getBackingMptStorage(-1),
(number: BigInt) => blockchainReader.getBlockHeaderByNumber(number).map(_.hash),
blockchainConfig.accountStartNonce,
ByteString(MerklePatriciaTrie.EmptyRootHash),
noEmptyAccounts = false,
ethCompatibleStorage = true
)

override protected lazy val successValidators: Validators = new Mocks.MockValidatorsAlwaysSucceed {
override val ommersValidator: OmmersValidator = (
parentHash: ByteString,
blockNumber: BigInt,
ommers: Seq[BlockHeader],
getBlockHeaderByHash: GetBlockHeaderByHash,
getNBlocksBack: GetNBlocksBack
) =>
new StdOmmersValidator(blockHeaderValidator)
.validate(parentHash, blockNumber, ommers, getBlockHeaderByHash, getNBlocksBack)
}

override lazy val blockImport: BlockImport = mkBlockImport(
validators = successValidators,
blockExecutionOpt = Some(
new BlockExecution(
blockchain,
blockchainReader,
blockchainWriter,
storagesInstance.storages.evmCodeStorage,
blockchainConfig,
consensus.blockPreparator,
new BlockValidation(consensus, blockchainReader, blockQueue)
) {
override def executeAndValidateBlock(
block: Block,
alreadyValidated: Boolean = false
): Either[BlockExecutionError, Seq[Receipt]] =
Right(BlockResult(emptyWorld).receipts)
}
)
)
// }
"BlockImporter" should "not discard blocks of the main chain if the reorganisation failed" in new TestFixture() {

val blockImporter: ActorRef = system.actorOf(
BlockImporter.props(
fetcherProbe.ref,
blockImport,
blockchain,
blockchainReader,
storagesInstance.storages.stateStorage,
new BranchResolution(blockchain, blockchainReader),
syncConfig,
ommersPoolProbe.ref,
broadcasterProbe.ref,
pendingTransactionsManagerProbe.ref,
supervisor.ref
)
)

val genesisBlock = blockchainReader.genesisBlock
val block1: Block = getBlock(genesisBlock.number + 1, parent = genesisBlock.header.hash)
// new chain is shorter but has a higher weight
val newBlock2: Block = getBlock(genesisBlock.number + 2, difficulty = 108, parent = block1.header.hash)
val newBlock3: Block = getBlock(genesisBlock.number + 3, difficulty = 300, parent = newBlock2.header.hash)
val oldBlock2: Block = getBlock(genesisBlock.number + 2, difficulty = 102, parent = block1.header.hash)
val oldBlock3: Block = getBlock(genesisBlock.number + 3, difficulty = 103, parent = oldBlock2.header.hash)
val oldBlock4: Block = getBlock(genesisBlock.number + 4, difficulty = 104, parent = oldBlock3.header.hash)

val weight1: ChainWeight = ChainWeight.totalDifficultyOnly(block1.header.difficulty)
val newWeight2: ChainWeight = weight1.increase(newBlock2.header)
val newWeight3: ChainWeight = newWeight2.increase(newBlock3.header)
val oldWeight2: ChainWeight = weight1.increase(oldBlock2.header)
val oldWeight3: ChainWeight = oldWeight2.increase(oldBlock3.header)
val oldWeight4: ChainWeight = oldWeight3.increase(oldBlock4.header)

//saving initial main chain
blockchainWriter.save(block1, Nil, weight1, saveAsBestBlock = true)
blockchainWriter.save(oldBlock2, Nil, oldWeight2, saveAsBestBlock = true)
blockchainWriter.save(oldBlock3, Nil, oldWeight3, saveAsBestBlock = true)
blockchainWriter.save(oldBlock4, Nil, oldWeight4, saveAsBestBlock = true)

val oldBranch: List[Block] = List(oldBlock2, oldBlock3, oldBlock4)
val newBranch: List[Block] = List(newBlock2, newBlock3)

blockImporter ! BlockImporter.Start

"BlockImporter" should "not discard blocks of the main chain if the reorganisation failed" in {

val blockImporter = system.actorOf(
override val blockImporter = system.actorOf(
BlockImporter.props(
fetcherProbe.ref,
mkBlockImport(validators = successValidators),
Expand All @@ -184,8 +78,7 @@ class BlockImporterItSpec
eventually(blockchainReader.getBestBlock().get shouldEqual oldBlock4)
}

it should "return a correct new best block after reorganising longer chain to a shorter one if its weight is bigger" in {

it should "return a correct new best block after reorganising longer chain to a shorter one if its weight is bigger" in new StartedImportFixture() {
//returning discarded initial chain
blockchainWriter.save(oldBlock2, Nil, oldWeight2, saveAsBestBlock = true)
blockchainWriter.save(oldBlock3, Nil, oldWeight3, saveAsBestBlock = true)
Expand All @@ -196,7 +89,7 @@ class BlockImporterItSpec
eventually(blockchainReader.getBestBlock().get shouldEqual newBlock3)
}

it should "return Unknown branch, in case of PickedBlocks with block that has a parent that's not in the chain" in {
it should "return Unknown branch, in case of PickedBlocks with block that has a parent that's not in the chain" in new StartedImportFixture() {
val newBlock4ParentOldBlock3: Block =
getBlock(genesisBlock.number + 4, difficulty = 104, parent = oldBlock3.header.hash)
val newBlock4WeightParentOldBlock3 = oldWeight3.increase(newBlock4ParentOldBlock3.header)
Expand All @@ -223,35 +116,35 @@ class BlockImporterItSpec
eventually(blockchainReader.getBestBlock().get shouldEqual newBlock4ParentOldBlock3)
}

it should "switch to a branch with a checkpoint" in {
it should "switch to a branch with a checkpoint" in new StartedImportFixture() {

val checkpoint = ObjectGenerators.fakeCheckpointGen(3, 3).sample.get
val oldBlock5WithCheckpoint: Block = checkpointBlockGenerator.generate(oldBlock4, checkpoint)
blockchainWriter.save(oldBlock5WithCheckpoint, Nil, oldWeight4, saveAsBestBlock = true)

val newBranch = List(newBlock2, newBlock3)
override val newBranch = List(newBlock2, newBlock3)

blockImporter ! BlockFetcher.PickedBlocks(NonEmptyList.fromListUnsafe(newBranch))

eventually(blockchainReader.getBestBlock().get shouldEqual oldBlock5WithCheckpoint)
eventually(blockchain.getLatestCheckpointBlockNumber() shouldEqual oldBlock5WithCheckpoint.header.number)
}

it should "switch to a branch with a newer checkpoint" in {
it should "switch to a branch with a newer checkpoint" in new StartedImportFixture() {

val checkpoint = ObjectGenerators.fakeCheckpointGen(3, 3).sample.get
val newBlock4WithCheckpoint: Block = checkpointBlockGenerator.generate(newBlock3, checkpoint)
blockchainWriter.save(newBlock4WithCheckpoint, Nil, newWeight3, saveAsBestBlock = true)

val newBranch = List(newBlock4WithCheckpoint)
override val newBranch = List(newBlock4WithCheckpoint)

blockImporter ! BlockFetcher.PickedBlocks(NonEmptyList.fromListUnsafe(newBranch))

eventually(blockchainReader.getBestBlock().get shouldEqual newBlock4WithCheckpoint)
eventually(blockchain.getLatestCheckpointBlockNumber() shouldEqual newBlock4WithCheckpoint.header.number)
}

it should "return a correct checkpointed block after receiving a request for generating a new checkpoint" in {
it should "return a correct checkpointed block after receiving a request for generating a new checkpoint" in new StartedImportFixture() {

val parent = blockchainReader.getBestBlock().get
val newBlock5: Block = getBlock(genesisBlock.number + 5, difficulty = 104, parent = parent.header.hash)
Expand All @@ -270,12 +163,12 @@ class BlockImporterItSpec
eventually(blockchain.getLatestCheckpointBlockNumber() shouldEqual newBlock5.header.number + 1)
}

it should "ask BlockFetcher to resolve missing node" in {
it should "ask BlockFetcher to resolve missing node" in new TestFixture() {
val parent = blockchainReader.getBestBlock().get
val newBlock: Block = getBlock(genesisBlock.number + 5, difficulty = 104, parent = parent.header.hash)
val invalidBlock = newBlock.copy(header = newBlock.header.copy(beneficiary = Address(111).bytes))

val blockImporter = system.actorOf(
override val blockImporter = system.actorOf(
BlockImporter.props(
fetcherProbe.ref,
mkBlockImport(validators = successValidators),
Expand All @@ -296,7 +189,7 @@ class BlockImporterItSpec

eventually {
val msg = fetcherProbe
.fishForMessage(Timeouts.longTimeout) {
.fishForMessage(Timeouts.normalTimeout) {
case BlockFetcher.FetchStateNode(_, _) => true
case _ => false
}
Expand All @@ -307,3 +200,112 @@ class BlockImporterItSpec

}
}

class TestFixture extends TestSetupWithVmAndValidators {

override lazy val blockQueue: BlockQueue = BlockQueue(blockchain, blockchainReader, SyncConfig(Config.config))

val genesis: Block = Block(
Fixtures.Blocks.Genesis.header.copy(stateRoot = ByteString(MerklePatriciaTrie.EmptyRootHash)),
Fixtures.Blocks.Genesis.body
)
val genesisWeight: ChainWeight = ChainWeight.zero.increase(genesis.header)

blockchainWriter.save(genesis, Seq(), genesisWeight, saveAsBestBlock = true)

lazy val checkpointBlockGenerator: CheckpointBlockGenerator = new CheckpointBlockGenerator

val fetcherProbe: TestProbe = TestProbe()
val ommersPoolProbe: TestProbe = TestProbe()
val broadcasterProbe: TestProbe = TestProbe()
val pendingTransactionsManagerProbe: TestProbe = TestProbe()
val supervisor: TestProbe = TestProbe()

val emptyWorld: InMemoryWorldStateProxy = InMemoryWorldStateProxy(
storagesInstance.storages.evmCodeStorage,
blockchain.getBackingMptStorage(-1),
(number: BigInt) => blockchainReader.getBlockHeaderByNumber(number).map(_.hash),
blockchainConfig.accountStartNonce,
ByteString(MerklePatriciaTrie.EmptyRootHash),
noEmptyAccounts = false,
ethCompatibleStorage = true
)

override protected lazy val successValidators: Validators = new Mocks.MockValidatorsAlwaysSucceed {
override val ommersValidator: OmmersValidator = (
parentHash: ByteString,
blockNumber: BigInt,
ommers: Seq[BlockHeader],
getBlockHeaderByHash: GetBlockHeaderByHash,
getNBlocksBack: GetNBlocksBack
) =>
new StdOmmersValidator(blockHeaderValidator)
.validate(parentHash, blockNumber, ommers, getBlockHeaderByHash, getNBlocksBack)
}

override lazy val blockImport: BlockImport = mkBlockImport(
validators = successValidators,
blockExecutionOpt = Some(
new BlockExecution(
blockchain,
blockchainReader,
blockchainWriter,
storagesInstance.storages.evmCodeStorage,
blockchainConfig,
consensus.blockPreparator,
new BlockValidation(consensus, blockchainReader, blockQueue)
) {
override def executeAndValidateBlock(
block: Block,
alreadyValidated: Boolean = false
): Either[BlockExecutionError, Seq[Receipt]] =
Right(BlockResult(emptyWorld).receipts)
}
)
)

val blockImporter: ActorRef = system.actorOf(
BlockImporter.props(
fetcherProbe.ref,
blockImport,
blockchain,
blockchainReader,
storagesInstance.storages.stateStorage,
new BranchResolution(blockchain, blockchainReader),
syncConfig,
ommersPoolProbe.ref,
broadcasterProbe.ref,
pendingTransactionsManagerProbe.ref,
supervisor.ref
)
)

val genesisBlock = blockchainReader.genesisBlock
val block1: Block = getBlock(genesisBlock.number + 1, parent = genesisBlock.header.hash)
// new chain is shorter but has a higher weight
val newBlock2: Block = getBlock(genesisBlock.number + 2, difficulty = 108, parent = block1.header.hash)
val newBlock3: Block = getBlock(genesisBlock.number + 3, difficulty = 300, parent = newBlock2.header.hash)
val oldBlock2: Block = getBlock(genesisBlock.number + 2, difficulty = 102, parent = block1.header.hash)
val oldBlock3: Block = getBlock(genesisBlock.number + 3, difficulty = 103, parent = oldBlock2.header.hash)
val oldBlock4: Block = getBlock(genesisBlock.number + 4, difficulty = 104, parent = oldBlock3.header.hash)

val weight1: ChainWeight = ChainWeight.totalDifficultyOnly(block1.header.difficulty)
val newWeight2: ChainWeight = weight1.increase(newBlock2.header)
val newWeight3: ChainWeight = newWeight2.increase(newBlock3.header)
val oldWeight2: ChainWeight = weight1.increase(oldBlock2.header)
val oldWeight3: ChainWeight = oldWeight2.increase(oldBlock3.header)
val oldWeight4: ChainWeight = oldWeight3.increase(oldBlock4.header)

//saving initial main chain
blockchainWriter.save(block1, Nil, weight1, saveAsBestBlock = true)
blockchainWriter.save(oldBlock2, Nil, oldWeight2, saveAsBestBlock = true)
blockchainWriter.save(oldBlock3, Nil, oldWeight3, saveAsBestBlock = true)
blockchainWriter.save(oldBlock4, Nil, oldWeight4, saveAsBestBlock = true)

val oldBranch: List[Block] = List(oldBlock2, oldBlock3, oldBlock4)
val newBranch: List[Block] = List(newBlock2, newBlock3)
}

class StartedImportFixture extends TestFixture {
blockImporter ! BlockImporter.Start
}