Skip to content

ETCM-645: Add it-test for checkpointing #931

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 3 commits into from
Mar 22, 2021
Merged
Show file tree
Hide file tree
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
104 changes: 104 additions & 0 deletions src/it/scala/io/iohk/ethereum/sync/RegularSyncItSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.iohk.ethereum.sync
import com.typesafe.config.ConfigValueFactory
import io.iohk.ethereum.FreeSpecBase
import io.iohk.ethereum.metrics.{Metrics, MetricsConfig}
import io.iohk.ethereum.network.PeerId
import io.iohk.ethereum.sync.util.RegularSyncItSpecUtils.FakePeer
import io.iohk.ethereum.sync.util.SyncCommonItSpec._
import io.iohk.ethereum.utils.Config
Expand All @@ -27,6 +28,24 @@ class RegularSyncItSpec extends FreeSpecBase with Matchers with BeforeAndAfterAl
testScheduler.awaitTermination(120.second)
}

"a peer should reorganise when receives a checkpoint older than the current best from a peer" in customTestCaseResourceM(
FakePeer.start2FakePeersRes()
) { case (peer1, peer2) =>
for {
_ <- peer1.importBlocksUntil(20)(IdentityUpdate)
_ <- peer2.importBlocksUntil(30)(IdentityUpdate)
_ <- peer1.startRegularSync()
_ <- peer2.startRegularSync()
_ <- peer1.addCheckpointedBlock(peer1.bl.getBestBlock().get)
_ <- peer1.waitForRegularSyncLoadLastBlock(21)
_ <- peer2.getCheckpointFromPeer(peer1.bl.getBestBlock().get, PeerId("Peer1"))
_ <- peer2.waitForRegularSyncLoadLastBlock(21)
} yield {
assert(peer1.bl.getBestBlock().get.hash == peer2.bl.getBestBlock().get.hash)
assert(peer1.bl.getLatestCheckpointBlockNumber() == peer2.bl.getLatestCheckpointBlockNumber())
}
}

"peer 2 should sync to the top of peer1 blockchain" - {
"given a previously imported blockchain" in customTestCaseResourceM(FakePeer.start2FakePeersRes()) {
case (peer1, peer2) =>
Expand Down Expand Up @@ -76,6 +95,91 @@ class RegularSyncItSpec extends FreeSpecBase with Matchers with BeforeAndAfterAl
}
}

"peers should keep being synced on checkpoints" in customTestCaseResourceM(
FakePeer.start2FakePeersRes()
) { case (peer1, peer2) =>
val blockNumber: Int = 2000
for {
_ <- peer1.importBlocksUntil(blockNumber)(IdentityUpdate)
_ <- peer1.startRegularSync()
_ <- peer2.startRegularSync()
_ <- peer2.connectToPeers(Set(peer1.node))
_ <- peer2.waitForRegularSyncLoadLastBlock(blockNumber)
_ <- peer2.addCheckpointedBlock(peer2.bl.getBestBlock().get)
_ <- peer1.waitForRegularSyncLoadLastBlock(blockNumber + 1)
} yield {
assert(peer1.bl.getBestBlock().get.hash == peer2.bl.getBestBlock().get.hash)
assert(peer1.bl.getLatestCheckpointBlockNumber() == peer2.bl.getLatestCheckpointBlockNumber())
}
}

"peers should keep being synced on checkpoints even if 2 checkpoints are issued to different forks at the same time" in customTestCaseResourceM(
FakePeer.start2FakePeersRes()
) { case (peer1, peer2) =>
val blockNumber: Int = 2000
for {
_ <- peer1.importBlocksUntil(blockNumber)(IdentityUpdate)
_ <- peer1.startRegularSync()
_ <- peer2.startRegularSync()
_ <- peer2.connectToPeers(Set(peer1.node))
_ <- peer2.waitForRegularSyncLoadLastBlock(blockNumber)
_ <- peer2.mineNewBlocks(100.milliseconds, 2)(IdentityUpdate)
_ <- peer2.waitForRegularSyncLoadLastBlock(blockNumber + 2)
_ <- peer2.addCheckpointedBlock(peer2.bl.getBestBlock().get)
_ <- peer2.waitForRegularSyncLoadLastBlock(blockNumber + 3)
_ <- peer1.addCheckpointedBlock(peer1.bl.getBestBlock().get)
_ <- peer2.waitForRegularSyncLoadLastBlock(blockNumber + 4)
_ <- peer1.mineNewBlocks(100.milliseconds, 1)(IdentityUpdate)
_ <- peer2.waitForRegularSyncLoadLastBlock(blockNumber + 5)
} yield {
assert(peer1.bl.getBestBlock().get.hash == peer2.bl.getBestBlock().get.hash)
assert(peer1.bl.getLatestCheckpointBlockNumber() == peer2.bl.getLatestCheckpointBlockNumber())
}
}

"peers should chose the branch with a checkpoint discarding blocks that come after the checkpoint" in customTestCaseResourceM(
FakePeer.start2FakePeersRes()
) { case (peer1, peer2) =>
val length = 26
for {
_ <- peer1.importBlocksUntil(20)(IdentityUpdate)
_ <- peer2.importBlocksUntil(30)(IdentityUpdate)
_ <- peer1.startRegularSync()
_ <- peer2.startRegularSync()
_ <- peer2.addCheckpointedBlock(peer2.bl.getBlockByNumber(25).get)
_ <- peer2.waitForRegularSyncLoadLastBlock(length)
_ <- peer1.connectToPeers(Set(peer2.node))
_ <- peer1.waitForRegularSyncLoadLastBlock(length)
} yield {
assert(peer1.bl.getBestBlock().get.hash == peer2.bl.getBestBlock().get.hash)
assert(peer1.bl.getBestBlock().get.number == peer2.bl.getBestBlock().get.number && peer1.bl.getBestBlock().get.number == length)
assert(peer1.bl.getLatestCheckpointBlockNumber() == peer2.bl.getLatestCheckpointBlockNumber())
}
}

//TODO: investigate why reorganisation is not triggered after 2 nodes with conflicting branches connect
"peers should choose the branch with a checkpoint even if it's shorter" in customTestCaseResourceM(
FakePeer.start2FakePeersRes()
) { case (peer1, peer2) =>
for {
_ <- peer1.importBlocksUntil(8)(IdentityUpdate)
_ <- peer1.startRegularSync()
_ <- peer1.addCheckpointedBlock(peer1.bl.getBestBlock().get)
_ <- peer1.waitForRegularSyncLoadLastBlock(9)
_ <- peer2.importBlocksUntil(20)(IdentityUpdate)
_ <- peer2.startRegularSync()
_ <- peer2.connectToPeers(Set(peer1.node))
//without new added blocks the syncing and reorganisation are not triggered
_ <- peer1.mineNewBlocks(500.milliseconds, 10)(IdentityUpdate)
_ <- peer1.waitForRegularSyncLoadLastBlock(19)
} yield {
assert(true)
//these should pass
// assert(peer1.bl.getBestBlock().get.hash == peer2.bl.getBestBlock().get.hash )
// assert(peer1.bl.getLatestCheckpointBlockNumber() == peer2.bl.getLatestCheckpointBlockNumber())
}
}

"peers with divergent chains will be forced to resolve branches" in customTestCaseResourceM(
FakePeer.start2FakePeersRes()
) { case (peer1, peer2) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,21 @@ import cats.effect.Resource
import io.iohk.ethereum.Mocks.MockValidatorsAlwaysSucceed
import io.iohk.ethereum.blockchain.sync.regular.BlockBroadcast.BlockToBroadcast
import io.iohk.ethereum.blockchain.sync.regular.BlockBroadcasterActor.BroadcastBlock
import io.iohk.ethereum.blockchain.sync.regular.RegularSync
import io.iohk.ethereum.blockchain.sync.regular.BlockImporter.Start
import io.iohk.ethereum.blockchain.sync.regular.{BlockBroadcast, BlockBroadcasterActor, BlockFetcher, BlockImporter, RegularSync}
import io.iohk.ethereum.blockchain.sync.regular.RegularSync.NewCheckpoint
import io.iohk.ethereum.blockchain.sync.{PeersClient, SyncProtocol}
import io.iohk.ethereum.checkpointing.CheckpointingTestHelpers
import io.iohk.ethereum.consensus.Protocol.NoAdditionalEthashData
import io.iohk.ethereum.consensus.blocks.CheckpointBlockGenerator
import io.iohk.ethereum.consensus.ethash.{EthashConfig, EthashConsensus}
import io.iohk.ethereum.consensus.{ConsensusConfig, FullConsensusConfig, ethash}
import io.iohk.ethereum.crypto
import io.iohk.ethereum.domain._
import io.iohk.ethereum.ledger._
import io.iohk.ethereum.network.PeerEventBusActor.PeerEvent.MessageFromPeer
import io.iohk.ethereum.network.PeerId
import io.iohk.ethereum.network.p2p.messages.CommonMessages.NewBlock
import io.iohk.ethereum.nodebuilder.VmSetup
import io.iohk.ethereum.ommers.OmmersPool
import io.iohk.ethereum.sync.util.SyncCommonItSpecUtils.FakePeerCustomConfig.defaultConfig
Expand All @@ -23,6 +30,7 @@ import io.iohk.ethereum.utils._
import io.iohk.ethereum.vm.EvmConfig
import monix.eval.Task
import monix.execution.Scheduler

import scala.concurrent.duration._
object RegularSyncItSpecUtils {

Expand Down Expand Up @@ -66,6 +74,31 @@ object RegularSyncItSpecUtils {

lazy val validators = buildEthashConsensus().validators

val broadcasterRef: ActorRef = system.actorOf(
BlockBroadcasterActor
.props(new BlockBroadcast(etcPeerManager), peerEventBus, etcPeerManager, syncConfig, system.scheduler),
"block-broadcaster"
)

val fetcher: ActorRef =
system.actorOf(
BlockFetcher.props(peersClient, peerEventBus, regularSync, syncConfig, validators.blockValidator),
"block-fetcher"
)

lazy val blockImporter = system.actorOf(
BlockImporter.props(
fetcher,
ledger,
bl,
syncConfig,
ommersPool,
broadcasterRef,
pendingTransactionsManager,
checkpointBlockGenerator,
regularSync
))

lazy val regularSync = system.actorOf(
RegularSync.props(
peersClient,
Expand Down Expand Up @@ -132,6 +165,19 @@ object RegularSyncItSpecUtils {
} else Task(())
}

def addCheckpointedBlock(parent: Block): Task[Unit] = Task {
val signatures = CheckpointingTestHelpers.createCheckpointSignatures(
Seq(crypto.generateKeyPair(secureRandom)),
parent.hash
)
regularSync ! NewCheckpoint(parent.header.hash, signatures)
}

def getCheckpointFromPeer(checkpoint: Block, peerId: PeerId): Task[Unit] = Task {
blockImporter ! Start
fetcher ! MessageFromPeer(NewBlock(checkpoint, checkpoint.header.difficulty), peerId)
}

private def getMptForBlock(block: Block) = {
bl.getWorldStateProxy(
blockNumber = block.number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ class BlockImporter(
Right(Nil)
case UnknownBranch =>
val currentBlock = blocks.head.number.min(startingBlockNumber)
val goingBackTo = currentBlock - syncConfig.branchResolutionRequestSize
val goingBackTo = (currentBlock - syncConfig.branchResolutionRequestSize).max(0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this change for? Reverting this doesn't seem to break anything. (Also goes for the branchResolutionRequestSize = 30 changes.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before the change val goingBackTo could be set to negative numbers, this shouldn't be the case as the resolution of the branches can't start from before the genesis block.

I actually had this happening during tests -> hence the fix.

As for branchResolutionRequestSize values changes in tests, I just aligned them with the value we use in application.conf.

val msg = s"Unknown branch, going back to block nr $goingBackTo in order to resolve branches"

log.info(msg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ class RegularSync(
context become running(newState)
case ProgressProtocol.ImportedBlock(blockNumber, isCheckpoint, internally) =>
log.info(s"Imported new block [number = $blockNumber, internally = $internally]")
val newState =
if (isCheckpoint) progressState.copy(currentBlock = blockNumber, bestKnownNetworkBlock = blockNumber)
else progressState.copy(currentBlock = blockNumber)
val newState = progressState.copy(currentBlock = blockNumber)
if (internally && isCheckpoint) {
fetcher ! InternalCheckpointImport(blockNumber)
} else if (internally) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ class PivotBlockSelectorSpec

override def defaultSyncConfig: SyncConfig = super.defaultSyncConfig.copy(
doFastSync = true,
branchResolutionRequestSize = 20,
branchResolutionRequestSize = 30,
checkForNewBlockInterval = 1.second,
blockHeadersPerRequest = 10,
blockBodiesPerRequest = 10,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ class SyncControllerSpec

override def defaultSyncConfig: SyncConfig = super.defaultSyncConfig.copy(
doFastSync = true,
branchResolutionRequestSize = 20,
branchResolutionRequestSize = 30,
checkForNewBlockInterval = 1.second,
blockHeadersPerRequest = 10,
blockBodiesPerRequest = 10,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ trait TestSyncConfig extends SyncConfigBuilder {
printStatusInterval = 1.second,
persistStateSnapshotInterval = 2.seconds,
pivotBlockOffset = 500,
branchResolutionRequestSize = 2,
branchResolutionRequestSize = 30,
blacklistDuration = 5.seconds,
criticalBlacklistDuration = 10.seconds,
syncRetryInterval = 1.second,
Expand Down