Skip to content

Commit 6ed0963

Browse files
committed
etcm-75 added support for saving latest checkpoint number in blockchain
1 parent 8b0b08f commit 6ed0963

File tree

6 files changed

+170
-27
lines changed

6 files changed

+170
-27
lines changed

src/it/scala/io/iohk/ethereum/txExecTest/util/DumpChainApp.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,6 @@ object DumpChainApp extends App with NodeKeyBuilder with SecureRandomBuilder wit
169169
override def save(block: Block, receipts: Seq[Receipt], totalDifficulty: BigInt, saveAsBestBlock: Boolean): Unit = ???
170170

171171
override def getStateStorage: StateStorage = ???
172+
173+
override def getLatestCheckpointBlockNumber(): BigInt = ???
172174
}

src/main/scala/io/iohk/ethereum/db/storage/StateStorage.scala

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ trait StateStorage {
1818
def getBackingStorage(bn: BigInt): MptStorage
1919
def getReadOnlyStorage: MptStorage
2020

21-
def onBlockSave(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlock: Option[BigInt] => Unit): Unit
22-
def onBlockRollback(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlock: Option[BigInt] => Unit): Unit
21+
def onBlockSave(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlocksData: => Unit): Unit
22+
def onBlockRollback(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlocksData: => Unit): Unit
2323

2424
def saveNode(nodeHash: NodeHash, nodeEncoded: NodeEncoded, bn: BigInt)
2525
def getNode(nodeHash: NodeHash): Option[MptNode]
@@ -34,15 +34,15 @@ class ArchiveStateStorage(private val nodeStorage: NodeStorage,
3434
true
3535
}
3636

37-
override def onBlockSave(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlock: Option[BigInt] => Unit): Unit = {
37+
override def onBlockSave(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlocksData: => Unit): Unit = {
3838
if (cachedNodeStorage.persist()) {
39-
updateBestBlock(None)
39+
updateBestBlocksData
4040
}
4141
}
4242

43-
override def onBlockRollback(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlock: Option[BigInt] => Unit): Unit = {
43+
override def onBlockRollback(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlocksData: => Unit): Unit = {
4444
if (cachedNodeStorage.persist()) {
45-
updateBestBlock(None)
45+
updateBestBlocksData
4646
}
4747
}
4848

@@ -71,21 +71,21 @@ class ReferenceCountedStateStorage(private val nodeStorage: NodeStorage,
7171
true
7272
}
7373

74-
override def onBlockSave(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlock: Option[BigInt] => Unit): Unit = {
74+
override def onBlockSave(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlocksData: => Unit): Unit = {
7575
val blockToPrune = bn - pruningHistory
7676

7777
ReferenceCountNodeStorage.prune(blockToPrune, cachedNodeStorage, inMemory = blockToPrune > currentBestSavedBlock)
7878

7979
if (cachedNodeStorage.persist()) {
80-
updateBestBlock(None)
80+
updateBestBlocksData
8181
}
8282
}
8383

84-
override def onBlockRollback(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlock: Option[BigInt] => Unit): Unit = {
84+
override def onBlockRollback(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlocksData: => Unit): Unit = {
8585
ReferenceCountNodeStorage.rollback(bn, cachedNodeStorage, inMemory = bn > currentBestSavedBlock)
8686

8787
if (cachedNodeStorage.persist()) {
88-
updateBestBlock(None)
88+
updateBestBlocksData
8989
}
9090
}
9191

@@ -120,19 +120,19 @@ class CachedReferenceCountedStateStorage(private val nodeStorage: NodeStorage,
120120
}
121121
}
122122

123-
override def onBlockSave(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlock: Option[BigInt] => Unit): Unit = {
123+
override def onBlockSave(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlocksData: => Unit): Unit = {
124124
val blockToPrune = bn - pruningHistory
125125
changeLog.persistChangeLog(bn)
126126
changeLog.getDeathRowFromStorage(blockToPrune).foreach {deathRow =>
127127
CachedReferenceCountedStorage.prune(deathRow, lruCache, blockToPrune)
128128
}
129129
if (CachedReferenceCountedStorage.persistCache(lruCache, nodeStorage)) {
130-
updateBestBlock(None)
130+
updateBestBlocksData
131131
}
132132
changeLog.removeBlockMetaData(blockToPrune)
133133
}
134134

135-
override def onBlockRollback(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlock: Option[BigInt] => Unit): Unit = {
135+
override def onBlockRollback(bn: BigInt, currentBestSavedBlock: BigInt)(updateBestBlocksData: => Unit): Unit = {
136136
changeLog.getChangeLogFromStorage(bn).foreach { changeLog =>
137137
CachedReferenceCountedStorage.rollback(lruCache, nodeStorage, changeLog, bn)
138138
}

src/main/scala/io/iohk/ethereum/domain/Block.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ case class Block(header: BlockHeader, body: BlockBody) {
2525

2626
def hash: ByteString = header.hash
2727

28+
val hasCheckpoint: Boolean = header.hasCheckpoint
29+
2830
def isParentOf(child: Block): Boolean = number + 1 == child.number && child.header.parentHash == hash
2931
}
3032

src/main/scala/io/iohk/ethereum/domain/Blockchain.scala

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import io.iohk.ethereum.db.storage.TransactionMappingStorage.TransactionLocation
1010
import io.iohk.ethereum.db.storage._
1111
import io.iohk.ethereum.db.storage.pruning.PruningMode
1212
import io.iohk.ethereum.domain
13+
import io.iohk.ethereum.domain.BlockchainImpl.BestBlockLatestCheckpointNumbers
1314
import io.iohk.ethereum.ledger.{InMemoryWorldStateProxy, InMemoryWorldStateProxyStorage}
1415
import io.iohk.ethereum.mpt.{MerklePatriciaTrie, MptNode}
1516
import io.iohk.ethereum.vm.{Storage, WorldStateProxy}
1617

18+
import scala.annotation.tailrec
19+
1720
/**
1821
* Entity to be used to persist and query Blockchain related objects (blocks, transactions, ommers)
1922
*/
@@ -123,6 +126,7 @@ trait Blockchain {
123126

124127
def getBestBlock(): Block
125128

129+
def getLatestCheckpointBlockNumber(): BigInt
126130

127131
/**
128132
* Persists full block along with receipts and total difficulty
@@ -209,7 +213,7 @@ class BlockchainImpl(
209213

210214
// There is always only one writer thread (ensured by actor), but can by many readers (api calls)
211215
// to ensure visibility of writes, needs to be volatile or atomic ref
212-
private val bestKnownBlock: AtomicReference[BigInt] = new AtomicReference(BigInt(0))
216+
private val bestKnownBlockAndLatestCheckpoint: AtomicReference[BestBlockLatestCheckpointNumbers] = new AtomicReference(BestBlockLatestCheckpointNumbers(BigInt(0), BigInt(0)))
213217

214218
override def getBlockHeaderByHash(hash: ByteString): Option[BlockHeader] =
215219
blockHeadersStorage.get(hash)
@@ -225,12 +229,20 @@ class BlockchainImpl(
225229

226230
override def getBestBlockNumber(): BigInt = {
227231
val bestBlockNum = appStateStorage.getBestBlockNumber()
228-
if (bestKnownBlock.get() > bestBlockNum)
229-
bestKnownBlock.get()
232+
if (bestKnownBlockAndLatestCheckpoint.get().bestBlockNumber > bestBlockNum)
233+
bestKnownBlockAndLatestCheckpoint.get().bestBlockNumber
230234
else
231235
bestBlockNum
232236
}
233237

238+
override def getLatestCheckpointBlockNumber(): BigInt = {
239+
val latestCheckpointNumberInStorage = appStateStorage.getLatestCheckpointBlockNumber()
240+
if (bestKnownBlockAndLatestCheckpoint.get().latestCheckpointNumber > latestCheckpointNumberInStorage)
241+
bestKnownBlockAndLatestCheckpoint.get().latestCheckpointNumber
242+
else
243+
latestCheckpointNumberInStorage
244+
}
245+
234246
override def getBestBlock(): Block =
235247
getBlockByNumber(getBestBlockNumber()).get
236248

@@ -252,8 +264,10 @@ class BlockchainImpl(
252264
ByteString(mpt.get(position).getOrElse(BigInt(0)).toByteArray)
253265
}
254266

255-
def saveBestBlock(bestBlock: Option[BigInt]): Unit = {
256-
bestBlock.fold(appStateStorage.putBestBlockNumber(getBestBlockNumber()).commit())(best => appStateStorage.putBestBlockNumber(best).commit())
267+
private def persistBestBlocksData(): Unit = {
268+
appStateStorage.putBestBlockNumber(getBestBlockNumber())
269+
.and(appStateStorage.putLatestCheckpointBlockNumber(getLatestCheckpointBlockNumber()))
270+
.commit()
257271
}
258272

259273
def save(block: Block, receipts: Seq[Receipt], totalDifficulty: BigInt, saveAsBestBlock: Boolean): Unit = {
@@ -263,8 +277,12 @@ class BlockchainImpl(
263277
.commit()
264278

265279
// not transactional part
266-
stateStorage.onBlockSave(block.header.number, appStateStorage.getBestBlockNumber())(saveBestBlock)
267-
if (saveAsBestBlock) {
280+
// the best blocks data will be persisted only when the cache will be persisted
281+
stateStorage.onBlockSave(block.header.number, appStateStorage.getBestBlockNumber())(persistBestBlocksData())
282+
283+
if (saveAsBestBlock && block.hasCheckpoint) {
284+
saveBestKnownBlockAndLatestCheckpointNumber(block.header.number, block.header.number)
285+
} else if (saveAsBestBlock) {
268286
saveBestKnownBlock(block.header.number)
269287
}
270288
}
@@ -290,7 +308,15 @@ class BlockchainImpl(
290308
evmCodeStorage.put(hash, evmCode)
291309

292310
override def saveBestKnownBlock(number: BigInt): Unit = {
293-
bestKnownBlock.set(number)
311+
bestKnownBlockAndLatestCheckpoint.updateAndGet(_.copy(bestBlockNumber = number))
312+
}
313+
314+
def saveBestKnownBlockAndLatestCheckpointNumber(number: BigInt, latestCheckpointNumber: BigInt): Unit = {
315+
bestKnownBlockAndLatestCheckpoint.set(BestBlockLatestCheckpointNumbers(number, latestCheckpointNumber))
316+
}
317+
318+
def saveLatestCheckpointNumber(latestCheckpointNumber: BigInt): Unit = {
319+
bestKnownBlockAndLatestCheckpoint.updateAndGet(_.copy(latestCheckpointNumber = latestCheckpointNumber))
294320
}
295321

296322
def storeTotalDifficulty(blockhash: ByteString, td: BigInt): DataSourceBatchUpdate =
@@ -310,6 +336,7 @@ class BlockchainImpl(
310336
blockNumberMappingStorage.remove(number)
311337
}
312338

339+
// scalastyle:off method.length
313340
override def removeBlock(blockHash: ByteString, withState: Boolean): Unit = {
314341
val maybeBlockHeader = getBlockHeaderByHash(blockHash)
315342
val maybeTxList = getBlockBodyByHash(blockHash).map(_.transactionList)
@@ -323,20 +350,64 @@ class BlockchainImpl(
323350
)
324351
}
325352

353+
val checkpointUpdates = maybeBlockHeader match {
354+
case Some(header) =>
355+
if (header.hasCheckpoint && header.number == getLatestCheckpointBlockNumber()) {
356+
val prev = findPreviousCheckpointBlockNumber(header.number, header.number)
357+
prev.map { num =>
358+
// side effect
359+
saveLatestCheckpointNumber(num)
360+
appStateStorage.putLatestCheckpointBlockNumber(num)
361+
}.getOrElse {
362+
// side effect
363+
saveLatestCheckpointNumber(0)
364+
appStateStorage.removeLatestCheckpointBlockNumber()
365+
}
366+
} else appStateStorage.emptyBatchUpdate
367+
case None =>
368+
appStateStorage.emptyBatchUpdate
369+
}
370+
326371
blockHeadersStorage.remove(blockHash)
327372
.and(blockBodiesStorage.remove(blockHash))
328373
.and(totalDifficultyStorage.remove(blockHash))
329374
.and(receiptStorage.remove(blockHash))
330375
.and(maybeTxList.fold(transactionMappingStorage.emptyBatchUpdate)(removeTxsLocations))
331376
.and(blockNumberMappingUpdates)
377+
// immediate last checkpoint save to not need to manage it from outside
378+
.and(checkpointUpdates)
332379
.commit()
333380

334381
// not transactional part
335382
maybeBlockHeader.foreach { h =>
336383
if (withState)
337-
stateStorage.onBlockRollback(h.number, bestSavedBlock)(saveBestBlock)
384+
stateStorage.onBlockRollback(h.number, bestSavedBlock)(persistBestBlocksData())
338385
}
339386
}
387+
// scalastyle:on method.length
388+
389+
/**
390+
* Recursive function which try to find the previous checkpoint by traversing blocks from top to the bottom.
391+
* In case of finding the checkpoint block number, the function will finish the job and return result
392+
*/
393+
@tailrec
394+
private def findPreviousCheckpointBlockNumber(
395+
blockNumberToCheck: BigInt,
396+
latestCheckpointBlockNumber: BigInt
397+
): Option[BigInt] = {
398+
if (blockNumberToCheck > 0) {
399+
val maybePreviousCheckpointBlockNumber = for {
400+
currentBlock <- getBlockByNumber(blockNumberToCheck)
401+
if currentBlock.hasCheckpoint &&
402+
currentBlock.number < latestCheckpointBlockNumber
403+
} yield currentBlock.number
404+
405+
maybePreviousCheckpointBlockNumber match {
406+
case Some(_) => maybePreviousCheckpointBlockNumber
407+
case None => findPreviousCheckpointBlockNumber(blockNumberToCheck - 1, latestCheckpointBlockNumber)
408+
}
409+
} else None
410+
}
340411

341412
private def saveTxsLocations(blockHash: ByteString, blockBody: BlockBody): DataSourceBatchUpdate =
342413
blockBody.transactionList.zipWithIndex.foldLeft(transactionMappingStorage.emptyBatchUpdate) {
@@ -423,4 +494,6 @@ object BlockchainImpl {
423494
appStateStorage = storages.appStateStorage,
424495
stateStorage = storages.stateStorage
425496
)
497+
498+
private case class BestBlockLatestCheckpointNumbers(bestBlockNumber: BigInt, latestCheckpointNumber: BigInt)
426499
}

src/test/scala/io/iohk/ethereum/db/storage/StateStorageSpec.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class StateStorageSpec extends AnyFlatSpec with Matchers with ScalaCheckProperty
7070

7171
if (testCache.shouldPersist) {
7272
val sizeBefore = ints.size
73-
archiveStateStorage.onBlockSave(1, 0) { None =>
73+
archiveStateStorage.onBlockSave(1, 0) {
7474
ints = 1 :: ints
7575
}
7676

@@ -88,7 +88,7 @@ class StateStorageSpec extends AnyFlatSpec with Matchers with ScalaCheckProperty
8888

8989
if (testCache.shouldPersist) {
9090
val sizeBefore = ints.size
91-
archiveStateStorage.onBlockRollback(1, 0) { None =>
91+
archiveStateStorage.onBlockRollback(1, 0) {
9292
ints = 1 :: ints
9393
}
9494

@@ -118,7 +118,7 @@ class StateStorageSpec extends AnyFlatSpec with Matchers with ScalaCheckProperty
118118

119119
if (testCache.shouldPersist) {
120120
val sizeBefore = ints.size
121-
referenceCounteStateStorage.onBlockSave(1, 0) { None =>
121+
referenceCounteStateStorage.onBlockSave(1, 0) {
122122
ints = 1 :: ints
123123
}
124124

@@ -136,7 +136,7 @@ class StateStorageSpec extends AnyFlatSpec with Matchers with ScalaCheckProperty
136136

137137
if (testCache.shouldPersist) {
138138
val sizeBefore = ints.size
139-
referenceCounteStateStorage.onBlockRollback(1, 0) { None =>
139+
referenceCounteStateStorage.onBlockRollback(1, 0) {
140140
ints = 1 :: ints
141141
}
142142

0 commit comments

Comments
 (0)