Skip to content

Commit c40bc7b

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

File tree

6 files changed

+171
-27
lines changed

6 files changed

+171
-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: 83 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,8 @@ 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] =
217+
new AtomicReference(BestBlockLatestCheckpointNumbers(BigInt(0), BigInt(0)))
213218

214219
override def getBlockHeaderByHash(hash: ByteString): Option[BlockHeader] =
215220
blockHeadersStorage.get(hash)
@@ -225,12 +230,20 @@ class BlockchainImpl(
225230

226231
override def getBestBlockNumber(): BigInt = {
227232
val bestBlockNum = appStateStorage.getBestBlockNumber()
228-
if (bestKnownBlock.get() > bestBlockNum)
229-
bestKnownBlock.get()
233+
if (bestKnownBlockAndLatestCheckpoint.get().bestBlockNumber > bestBlockNum)
234+
bestKnownBlockAndLatestCheckpoint.get().bestBlockNumber
230235
else
231236
bestBlockNum
232237
}
233238

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

@@ -252,8 +265,10 @@ class BlockchainImpl(
252265
ByteString(mpt.get(position).getOrElse(BigInt(0)).toByteArray)
253266
}
254267

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

259274
def save(block: Block, receipts: Seq[Receipt], totalDifficulty: BigInt, saveAsBestBlock: Boolean): Unit = {
@@ -263,8 +278,12 @@ class BlockchainImpl(
263278
.commit()
264279

265280
// not transactional part
266-
stateStorage.onBlockSave(block.header.number, appStateStorage.getBestBlockNumber())(saveBestBlock)
267-
if (saveAsBestBlock) {
281+
// the best blocks data will be persisted only when the cache will be persisted
282+
stateStorage.onBlockSave(block.header.number, appStateStorage.getBestBlockNumber())(persistBestBlocksData())
283+
284+
if (saveAsBestBlock && block.hasCheckpoint) {
285+
saveBestKnownBlockAndLatestCheckpointNumber(block.header.number, block.header.number)
286+
} else if (saveAsBestBlock) {
268287
saveBestKnownBlock(block.header.number)
269288
}
270289
}
@@ -290,7 +309,15 @@ class BlockchainImpl(
290309
evmCodeStorage.put(hash, evmCode)
291310

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

296323
def storeTotalDifficulty(blockhash: ByteString, td: BigInt): DataSourceBatchUpdate =
@@ -310,6 +337,7 @@ class BlockchainImpl(
310337
blockNumberMappingStorage.remove(number)
311338
}
312339

340+
// scalastyle:off method.length
313341
override def removeBlock(blockHash: ByteString, withState: Boolean): Unit = {
314342
val maybeBlockHeader = getBlockHeaderByHash(blockHash)
315343
val maybeTxList = getBlockBodyByHash(blockHash).map(_.transactionList)
@@ -323,20 +351,64 @@ class BlockchainImpl(
323351
)
324352
}
325353

354+
val checkpointUpdates = maybeBlockHeader match {
355+
case Some(header) =>
356+
if (header.hasCheckpoint && header.number == getLatestCheckpointBlockNumber()) {
357+
val prev = findPreviousCheckpointBlockNumber(header.number, header.number)
358+
prev.map { num =>
359+
// side effect
360+
saveLatestCheckpointNumber(num)
361+
appStateStorage.putLatestCheckpointBlockNumber(num)
362+
}.getOrElse {
363+
// side effect
364+
saveLatestCheckpointNumber(0)
365+
appStateStorage.removeLatestCheckpointBlockNumber()
366+
}
367+
} else appStateStorage.emptyBatchUpdate
368+
case None =>
369+
appStateStorage.emptyBatchUpdate
370+
}
371+
326372
blockHeadersStorage.remove(blockHash)
327373
.and(blockBodiesStorage.remove(blockHash))
328374
.and(totalDifficultyStorage.remove(blockHash))
329375
.and(receiptStorage.remove(blockHash))
330376
.and(maybeTxList.fold(transactionMappingStorage.emptyBatchUpdate)(removeTxsLocations))
331377
.and(blockNumberMappingUpdates)
378+
// immediate last checkpoint save to not need to manage it from outside
379+
.and(checkpointUpdates)
332380
.commit()
333381

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

341413
private def saveTxsLocations(blockHash: ByteString, blockBody: BlockBody): DataSourceBatchUpdate =
342414
blockBody.transactionList.zipWithIndex.foldLeft(transactionMappingStorage.emptyBatchUpdate) {
@@ -423,4 +495,6 @@ object BlockchainImpl {
423495
appStateStorage = storages.appStateStorage,
424496
stateStorage = storages.stateStorage
425497
)
498+
499+
private case class BestBlockLatestCheckpointNumbers(bestBlockNumber: BigInt, latestCheckpointNumber: BigInt)
426500
}

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)