-
Notifications
You must be signed in to change notification settings - Fork 75
[ETCM-75] Support for checkpoint blocks in Blockchain #716
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,10 +10,13 @@ import io.iohk.ethereum.db.storage.TransactionMappingStorage.TransactionLocation | |
import io.iohk.ethereum.db.storage._ | ||
import io.iohk.ethereum.db.storage.pruning.PruningMode | ||
import io.iohk.ethereum.domain | ||
import io.iohk.ethereum.domain.BlockchainImpl.BestBlockLatestCheckpointNumbers | ||
import io.iohk.ethereum.ledger.{InMemoryWorldStateProxy, InMemoryWorldStateProxyStorage} | ||
import io.iohk.ethereum.mpt.{MerklePatriciaTrie, MptNode} | ||
import io.iohk.ethereum.vm.{Storage, WorldStateProxy} | ||
|
||
import scala.annotation.tailrec | ||
|
||
/** | ||
* Entity to be used to persist and query Blockchain related objects (blocks, transactions, ommers) | ||
*/ | ||
|
@@ -123,6 +126,7 @@ trait Blockchain { | |
|
||
def getBestBlock(): Block | ||
|
||
def getLatestCheckpointBlockNumber(): BigInt | ||
|
||
/** | ||
* Persists full block along with receipts and total difficulty | ||
|
@@ -158,7 +162,7 @@ trait Blockchain { | |
|
||
def storeTotalDifficulty(blockhash: ByteString, totalDifficulty: BigInt): DataSourceBatchUpdate | ||
|
||
def saveBestKnownBlock(number: BigInt): Unit | ||
def saveBestKnownBlocks(bestBlockNumber: BigInt, latestCheckpointNumber: Option[BigInt] = None): Unit | ||
|
||
def saveNode(nodeHash: NodeHash, nodeEncoded: NodeEncoded, blockNumber: BigInt): Unit | ||
|
||
|
@@ -209,7 +213,8 @@ class BlockchainImpl( | |
|
||
// There is always only one writer thread (ensured by actor), but can by many readers (api calls) | ||
// to ensure visibility of writes, needs to be volatile or atomic ref | ||
private val bestKnownBlock: AtomicReference[BigInt] = new AtomicReference(BigInt(0)) | ||
private val bestKnownBlockAndLatestCheckpoint: AtomicReference[BestBlockLatestCheckpointNumbers] = | ||
new AtomicReference(BestBlockLatestCheckpointNumbers(BigInt(0), BigInt(0))) | ||
|
||
override def getBlockHeaderByHash(hash: ByteString): Option[BlockHeader] = | ||
blockHeadersStorage.get(hash) | ||
|
@@ -225,12 +230,22 @@ class BlockchainImpl( | |
|
||
override def getBestBlockNumber(): BigInt = { | ||
val bestBlockNum = appStateStorage.getBestBlockNumber() | ||
if (bestKnownBlock.get() > bestBlockNum) | ||
bestKnownBlock.get() | ||
if (bestKnownBlockAndLatestCheckpoint.get().bestBlockNumber > bestBlockNum) | ||
bestKnownBlockAndLatestCheckpoint.get().bestBlockNumber | ||
else | ||
bestBlockNum | ||
} | ||
|
||
override def getLatestCheckpointBlockNumber(): BigInt = { | ||
val latestCheckpointNumberInStorage = appStateStorage.getLatestCheckpointBlockNumber() | ||
// The latest checkpoint number is firstly saved in memory and then persisted to the storage only when it's time to persist cache. | ||
// The latest checkpoint number in memory can be bigger than the number in storage because the cache wasn't persisted yet | ||
if (bestKnownBlockAndLatestCheckpoint.get().latestCheckpointNumber > latestCheckpointNumberInStorage) | ||
bestKnownBlockAndLatestCheckpoint.get().latestCheckpointNumber | ||
else | ||
latestCheckpointNumberInStorage | ||
} | ||
|
||
override def getBestBlock(): Block = | ||
getBlockByNumber(getBestBlockNumber()).get | ||
|
||
|
@@ -252,8 +267,10 @@ class BlockchainImpl( | |
ByteString(mpt.get(position).getOrElse(BigInt(0)).toByteArray) | ||
} | ||
|
||
def saveBestBlock(bestBlock: Option[BigInt]): Unit = { | ||
bestBlock.fold(appStateStorage.putBestBlockNumber(getBestBlockNumber()).commit())(best => appStateStorage.putBestBlockNumber(best).commit()) | ||
private def persistBestBlocksData(): Unit = { | ||
appStateStorage.putBestBlockNumber(getBestBlockNumber()) | ||
.and(appStateStorage.putLatestCheckpointBlockNumber(getLatestCheckpointBlockNumber())) | ||
.commit() | ||
} | ||
|
||
def save(block: Block, receipts: Seq[Receipt], totalDifficulty: BigInt, saveAsBestBlock: Boolean): Unit = { | ||
|
@@ -263,8 +280,12 @@ class BlockchainImpl( | |
.commit() | ||
|
||
// not transactional part | ||
stateStorage.onBlockSave(block.header.number, appStateStorage.getBestBlockNumber())(saveBestBlock) | ||
if (saveAsBestBlock) { | ||
// the best blocks data will be persisted only when the cache will be persisted | ||
stateStorage.onBlockSave(block.header.number, appStateStorage.getBestBlockNumber())(persistBestBlocksData) | ||
|
||
if (saveAsBestBlock && block.hasCheckpoint) { | ||
saveBestKnownBlockAndLatestCheckpointNumber(block.header.number, block.header.number) | ||
} else if (saveAsBestBlock) { | ||
saveBestKnownBlock(block.header.number) | ||
} | ||
} | ||
|
@@ -289,8 +310,21 @@ class BlockchainImpl( | |
override def storeEvmCode(hash: ByteString, evmCode: ByteString): DataSourceBatchUpdate = | ||
evmCodeStorage.put(hash, evmCode) | ||
|
||
override def saveBestKnownBlock(number: BigInt): Unit = { | ||
bestKnownBlock.set(number) | ||
override def saveBestKnownBlocks(bestBlockNumber: BigInt, latestCheckpointNumber: Option[BigInt] = None): Unit = { | ||
latestCheckpointNumber match { | ||
case Some(number) => | ||
saveBestKnownBlockAndLatestCheckpointNumber(bestBlockNumber, number) | ||
case None => | ||
saveBestKnownBlock(bestBlockNumber) | ||
} | ||
} | ||
|
||
private def saveBestKnownBlock(bestBlockNumber: BigInt): Unit = { | ||
bestKnownBlockAndLatestCheckpoint.updateAndGet(_.copy(bestBlockNumber = bestBlockNumber)) | ||
} | ||
|
||
private def saveBestKnownBlockAndLatestCheckpointNumber(number: BigInt, latestCheckpointNumber: BigInt): Unit = { | ||
bestKnownBlockAndLatestCheckpoint.set(BestBlockLatestCheckpointNumbers(number, latestCheckpointNumber)) | ||
} | ||
|
||
def storeTotalDifficulty(blockhash: ByteString, td: BigInt): DataSourceBatchUpdate = | ||
|
@@ -310,10 +344,18 @@ class BlockchainImpl( | |
blockNumberMappingStorage.remove(number) | ||
} | ||
|
||
// scalastyle:off method.length | ||
override def removeBlock(blockHash: ByteString, withState: Boolean): Unit = { | ||
val maybeBlockHeader = getBlockHeaderByHash(blockHash) | ||
val maybeTxList = getBlockBodyByHash(blockHash).map(_.transactionList) | ||
val bestSavedBlock = getBestBlockNumber() | ||
val bestBlocks = bestKnownBlockAndLatestCheckpoint.get() | ||
// as we are decreasing block numbers in memory more often than in storage, | ||
// we can't use here getBestBlockNumber / getLatestCheckpointBlockNumber | ||
val bestBlockNumber = if(bestBlocks.bestBlockNumber != 0) bestBlocks.bestBlockNumber else appStateStorage.getBestBlockNumber() | ||
val latestCheckpointNumber = { | ||
if(bestBlocks.latestCheckpointNumber != 0) bestBlocks.latestCheckpointNumber | ||
else appStateStorage.getLatestCheckpointBlockNumber() | ||
} | ||
|
||
val blockNumberMappingUpdates = { | ||
maybeBlockHeader.fold(blockNumberMappingStorage.emptyBatchUpdate)( h => | ||
|
@@ -323,6 +365,22 @@ class BlockchainImpl( | |
) | ||
} | ||
|
||
val (checkpointUpdates, prevCheckpointNumber): (DataSourceBatchUpdate, Option[BigInt]) = maybeBlockHeader match { | ||
case Some(header) => | ||
if (header.hasCheckpoint && header.number == latestCheckpointNumber) { | ||
val prev = findPreviousCheckpointBlockNumber(header.number, header.number) | ||
prev.map { num => | ||
(appStateStorage.putLatestCheckpointBlockNumber(num), Some(num)) | ||
}.getOrElse { | ||
(appStateStorage.removeLatestCheckpointBlockNumber(), Some(0)) | ||
} | ||
} else (appStateStorage.emptyBatchUpdate, None) | ||
case None => | ||
(appStateStorage.emptyBatchUpdate, None) | ||
} | ||
|
||
val newBestBlockNumber: BigInt = if(bestBlockNumber >= 1) bestBlockNumber - 1 else 0 | ||
|
||
blockHeadersStorage.remove(blockHash) | ||
.and(blockBodiesStorage.remove(blockHash)) | ||
.and(totalDifficultyStorage.remove(blockHash)) | ||
|
@@ -332,11 +390,40 @@ class BlockchainImpl( | |
.commit() | ||
|
||
// not transactional part | ||
saveBestKnownBlocks(newBestBlockNumber, prevCheckpointNumber) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also add this to our save function as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And much more error prone... I think we'll suffer from this eventually 😕 |
||
|
||
maybeBlockHeader.foreach { h => | ||
if (withState) | ||
stateStorage.onBlockRollback(h.number, bestSavedBlock)(saveBestBlock) | ||
if (withState) { | ||
val bestBlocksUpdates = appStateStorage.putBestBlockNumber(newBestBlockNumber) | ||
.and(checkpointUpdates) | ||
stateStorage.onBlockRollback(h.number, bestBlockNumber)(() => bestBlocksUpdates.commit()) | ||
} | ||
} | ||
} | ||
// scalastyle:on method.length | ||
|
||
/** | ||
* Recursive function which try to find the previous checkpoint by traversing blocks from top to the bottom. | ||
* In case of finding the checkpoint block number, the function will finish the job and return result | ||
*/ | ||
@tailrec | ||
private def findPreviousCheckpointBlockNumber( | ||
blockNumberToCheck: BigInt, | ||
latestCheckpointBlockNumber: BigInt | ||
): Option[BigInt] = { | ||
if (blockNumberToCheck > 0) { | ||
val maybePreviousCheckpointBlockNumber = for { | ||
currentBlock <- getBlockByNumber(blockNumberToCheck) | ||
if currentBlock.hasCheckpoint && | ||
currentBlock.number < latestCheckpointBlockNumber | ||
} yield currentBlock.number | ||
|
||
maybePreviousCheckpointBlockNumber match { | ||
case Some(_) => maybePreviousCheckpointBlockNumber | ||
case None => findPreviousCheckpointBlockNumber(blockNumberToCheck - 1, latestCheckpointBlockNumber) | ||
} | ||
} else None | ||
} | ||
|
||
private def saveTxsLocations(blockHash: ByteString, blockBody: BlockBody): DataSourceBatchUpdate = | ||
blockBody.transactionList.zipWithIndex.foldLeft(transactionMappingStorage.emptyBatchUpdate) { | ||
|
@@ -386,8 +473,8 @@ class BlockchainImpl( | |
|
||
//FIXME EC-495 this method should not be need when best block is handled properly during rollback | ||
def persistCachedNodes(): Unit = { | ||
if (stateStorage.forcePersist(RollBackFlush)){ | ||
appStateStorage.putBestBlockNumber(getBestBlockNumber()).commit() | ||
if (stateStorage.forcePersist(RollBackFlush)) { | ||
persistBestBlocksData() | ||
} | ||
} | ||
} | ||
|
@@ -423,4 +510,6 @@ object BlockchainImpl { | |
appStateStorage = storages.appStateStorage, | ||
stateStorage = storages.stateStorage | ||
) | ||
|
||
private case class BestBlockLatestCheckpointNumbers(bestBlockNumber: BigInt, latestCheckpointNumber: BigInt) | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain in what situation these numbers can be different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure every border case here, but as far as I understand the best block number is firstly saved in memory and then persisted to the storage only when it's time to persist cache. I want to save the best block number and latest checkpoint transactionally so
getLatestCheckpointBlockNumber
behaves the same. We can have the latest checkpoint number in memory bigger than the number in storage because the cache wasn't persisted yet.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, maybe add a comment about it?