Skip to content

Commit 1238df4

Browse files
committed
[ETCM-102] Add integration tests to fast sync
1 parent b4d1c71 commit 1238df4

File tree

1 file changed

+88
-29
lines changed

1 file changed

+88
-29
lines changed

src/it/scala/io/iohk/ethereum/sync/FastSyncItSpec.scala

Lines changed: 88 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import io.iohk.ethereum.blockchain.sync.{BlockchainHostActor, FastSync, TestSync
1313
import io.iohk.ethereum.db.components.{SharedEphemDataSources, Storages}
1414
import io.iohk.ethereum.db.storage.AppStateStorage
1515
import io.iohk.ethereum.db.storage.pruning.{ArchivePruning, PruningMode}
16-
import io.iohk.ethereum.domain.{Block, Blockchain, BlockchainImpl}
17-
import io.iohk.ethereum.mpt.MerklePatriciaTrie
16+
import io.iohk.ethereum.domain.{Account, Address, Block, Blockchain, BlockchainImpl}
17+
import io.iohk.ethereum.ledger.InMemoryWorldStateProxy
18+
import io.iohk.ethereum.mpt.{HashNode, MerklePatriciaTrie, MptNode, MptTraversals}
1819
import io.iohk.ethereum.network.EtcPeerManagerActor.PeerInfo
1920
import io.iohk.ethereum.network.PeerManagerActor.{FastSyncHostConfiguration, PeerConfiguration}
2021
import io.iohk.ethereum.network.discovery.Node
@@ -25,7 +26,7 @@ import io.iohk.ethereum.network.rlpx.AuthHandshaker
2526
import io.iohk.ethereum.network.rlpx.RLPxConnectionHandler.RLPxConfiguration
2627
import io.iohk.ethereum.network.{EtcPeerManagerActor, ForkResolver, KnownNodesManager, PeerEventBusActor, PeerManagerActor, ServerActor}
2728
import io.iohk.ethereum.nodebuilder.{PruningConfigBuilder, SecureRandomBuilder}
28-
import io.iohk.ethereum.sync.FastSyncItSpec.{FakePeer, customTestCaseResourceM}
29+
import io.iohk.ethereum.sync.FastSyncItSpec.{FakePeer, IdentityUpdate, customTestCaseResourceM, updateStateAtBlock}
2930
import io.iohk.ethereum.utils.ServerStatus.Listening
3031
import io.iohk.ethereum.utils.{Config, NodeStatus, ServerStatus, VmConfig}
3132
import io.iohk.ethereum.vm.EvmConfig
@@ -36,16 +37,16 @@ import org.scalatest.{Assertion, AsyncFlatSpec, BeforeAndAfter, Matchers}
3637

3738
import scala.concurrent.Future
3839
import scala.concurrent.duration._
40+
import scala.util.Try
3941

4042
class FastSyncItSpec extends AsyncFlatSpec with Matchers with BeforeAndAfter {
4143
implicit val testScheduler = Scheduler.fixedPool("test", 16)
4244

4345
"FastSync" should "should sync blockchain without state nodes" in customTestCaseResourceM(FakePeer.start3FakePeersRes()) {
4446
case (peer1, peer2, peer3) =>
4547
for {
46-
_ <- Task.parZip3(peer1.startPeer(), peer2.startPeer(), peer3.startPeer())
47-
_ <- peer2.saveNBlocks(1000)
48-
_ <- peer3.saveNBlocks(1000)
48+
_ <- peer2.importNBlocksToTheTopForm(peer2.getCurrentState(), 1000)(IdentityUpdate)
49+
_ <- peer3.importNBlocksToTheTopForm(peer3.getCurrentState(), 1000)(IdentityUpdate)
4950
_ <- peer1.connectToPeers(Set(peer2.node, peer3.node))
5051
_ <- peer1.startFastSync()
5152
_ <- peer1.waitForFastSyncFinish()
@@ -55,6 +56,21 @@ class FastSyncItSpec extends AsyncFlatSpec with Matchers with BeforeAndAfter {
5556
}
5657
}
5758

59+
it should "should sync blockchain with state nodes" in customTestCaseResourceM(FakePeer.start3FakePeersRes()) {
60+
case (peer1, peer2, peer3) =>
61+
for {
62+
_ <- peer2.importNBlocksToTheTopForm(peer2.getCurrentState(), 1000)(updateStateAtBlock(500))
63+
_ <- peer3.importNBlocksToTheTopForm(peer3.getCurrentState(), 1000)(updateStateAtBlock(500))
64+
_ <- peer1.connectToPeers(Set(peer2.node, peer3.node))
65+
_ <- peer1.startFastSync()
66+
_ <- peer1.waitForFastSyncFinish()
67+
} yield {
68+
val trie = peer1.getBestBlockTrie()
69+
assert(peer1.bl.getBestBlockNumber() == peer2.bl.getBestBlockNumber() - peer2.syncConfig.targetBlockOffset)
70+
assert(peer1.bl.getBestBlockNumber() == peer3.bl.getBestBlockNumber() - peer3.syncConfig.targetBlockOffset)
71+
assert(trie.isDefined)
72+
}
73+
}
5874
}
5975

6076
object FastSyncItSpec {
@@ -88,16 +104,31 @@ object FastSyncItSpec {
88104
fixture.use(theTest).runToFuture
89105
}
90106

91-
def generateBlockChain(startBlock: Block, number: Int): Seq[Block] = {
92-
def recur(last: Block, blocksLeft: Int, blocksCreated: List[Block]): List[Block] = {
93-
if (blocksLeft <= 0) {
94-
blocksCreated.reverse
95-
} else {
96-
val newBlock = last.copy(header = last.header.copy(parentHash = last.header.hash, number = last.header.number + 1))
97-
recur(newBlock, blocksLeft - 1, newBlock :: blocksCreated)
98-
}
107+
final case class BlockchainState(bestBlock: Block, currentWorldState: InMemoryWorldStateProxy, currentTd: BigInt)
108+
109+
val IdentityUpdate: (BigInt, InMemoryWorldStateProxy) => InMemoryWorldStateProxy = (_, world) => world
110+
111+
def updateWorldWithNRandomAcounts(n:Int, world: InMemoryWorldStateProxy): InMemoryWorldStateProxy = {
112+
val resultWorld = (0 until n).foldLeft(world) { (world, num) =>
113+
val randomBalance = num
114+
val randomAddress = Address(num)
115+
val codeBytes = BigInt(num).toByteArray
116+
val storage = world.getStorage(randomAddress)
117+
val changedStorage = (num until num + 20).foldLeft(storage)((storage, value) => storage.store(value, value))
118+
world
119+
.saveAccount(randomAddress, Account.empty().copy(balance = randomBalance))
120+
.saveCode(randomAddress, ByteString(codeBytes))
121+
.saveStorage(randomAddress, changedStorage)
122+
}
123+
InMemoryWorldStateProxy.persistState(resultWorld)
124+
}
125+
126+
def updateStateAtBlock(blockWithUpdate: BigInt): (BigInt, InMemoryWorldStateProxy) => InMemoryWorldStateProxy = { (blockNr: BigInt, world: InMemoryWorldStateProxy) =>
127+
if (blockNr == blockWithUpdate) {
128+
updateWorldWithNRandomAcounts(1000, world)
129+
} else {
130+
IdentityUpdate(blockNr, world)
99131
}
100-
recur(startBlock, number, List.empty)
101132
}
102133

103134
class FakePeer(peerName: String) extends SecureRandomBuilder with TestSyncConfig {
@@ -227,6 +258,7 @@ object FastSyncItSpec {
227258
receiptsPerRequest = 50,
228259
fastSyncThrottle = 10.milliseconds,
229260
startRetryInterval = 50.milliseconds,
261+
nodesPerRequest = 200
230262
)
231263

232264
lazy val fastSync = system.actorOf(FastSync.props(
@@ -240,16 +272,23 @@ object FastSyncItSpec {
240272
system.scheduler
241273
))
242274

243-
def getMptForBlock(blockHeaderNumber: BigInt) = {
275+
private def getMptForBlock(block: Block) = {
244276
bl.getWorldStateProxy(
245-
blockNumber = blockHeaderNumber,
277+
blockNumber = block.number,
246278
accountStartNonce = blockchainConfig.accountStartNonce,
247-
stateRootHash = bl.getBlockByNumber(blockHeaderNumber).map(_.header.stateRoot),
248-
noEmptyAccounts = EvmConfig.forBlock(blockHeaderNumber, blockchainConfig).noEmptyAccounts,
279+
stateRootHash = Some(block.header.stateRoot),
280+
noEmptyAccounts = EvmConfig.forBlock(block.number, blockchainConfig).noEmptyAccounts,
249281
ethCompatibleStorage = blockchainConfig.ethCompatibleStorage
250282
)
251283
}
252284

285+
def getCurrentState(): BlockchainState = {
286+
val bestBlock = bl.getBestBlock()
287+
val currentWorldState = getMptForBlock(bestBlock)
288+
val currentTd = bl.getTotalDifficultyByHash(bestBlock.hash).get
289+
BlockchainState(bestBlock, currentWorldState, currentTd)
290+
}
291+
253292
def startPeer(): Task[Unit] = {
254293
for {
255294
_ <- Task {
@@ -279,18 +318,29 @@ object FastSyncItSpec {
279318
} yield ()
280319
}
281320

282-
import akka.pattern.ask
283-
def getHandshakedPeers: Task[PeerManagerActor.Peers] = {
284-
Task.deferFutureAction{s =>
285-
implicit val ec = s
286-
(peerManager ? PeerManagerActor.GetPeers).mapTo[PeerManagerActor.Peers]
287-
}
321+
private def createChildBlock(parent: Block, parentTd: BigInt, parentWorld: InMemoryWorldStateProxy)
322+
(updateWorldForBlock: (BigInt, InMemoryWorldStateProxy) => InMemoryWorldStateProxy): (Block, BigInt, InMemoryWorldStateProxy) = {
323+
val newBlockNumber = parent.header.number + 1
324+
val newWorld = updateWorldForBlock(newBlockNumber, parentWorld)
325+
val newBlock = parent.copy(header = parent.header.copy(parentHash = parent.header.hash, number = newBlockNumber, stateRoot = newWorld.stateRootHash))
326+
val newTd = newBlock.header.difficulty + parentTd
327+
(newBlock, newTd, parentWorld)
288328
}
289329

290-
def saveNBlocks(n: Int) = Task {
291-
val lastBlock = bl.getBestBlock()
292-
val chain = generateBlockChain(lastBlock, n)
293-
chain.foreach(block => bl.save(block, Seq(), block.header.difficulty, true))
330+
def importNBlocksToTheTopForm(startState: BlockchainState, n: Int)
331+
(updateWorldForBlock: (BigInt, InMemoryWorldStateProxy) => InMemoryWorldStateProxy): Task[Unit] = {
332+
def go(parent: Block, parentTd: BigInt, parentWorld: InMemoryWorldStateProxy, blocksLeft: Int): Task[Unit] = {
333+
if (blocksLeft <= 0) {
334+
Task.now(())
335+
} else {
336+
val (newBlock, newTd, newWorld) = createChildBlock(parent, parentTd, parentWorld)(updateWorldForBlock)
337+
bl.save(newBlock, Seq(), newTd, saveAsBestBlock = true)
338+
bl.persistCachedNodes()
339+
go(newBlock, newTd, newWorld, blocksLeft - 1)
340+
}
341+
}
342+
343+
go(startState.bestBlock, startState.currentTd, startState.currentWorldState, n)
294344
}
295345

296346
def startFastSync(): Task[Unit] = Task {
@@ -302,6 +352,15 @@ object FastSyncItSpec {
302352
isDone
303353
}
304354
}
355+
356+
// Reads whole trie into memory, if the trie lacks nodes in storage it will be None
357+
def getBestBlockTrie(): Option[MptNode] = {
358+
Try {
359+
val bestBlock = bl.getBestBlock()
360+
val bestStateRoot =bestBlock.header.stateRoot
361+
MptTraversals.parseTrieIntoMemory(HashNode(bestStateRoot.toArray), storagesInstance.storages.stateStorage.getBackingStorage(bestBlock.number))
362+
}.toOption
363+
}
305364
}
306365

307366
object FakePeer {

0 commit comments

Comments
 (0)