Skip to content

Commit

Permalink
Adds bestBlock hash in app state
Browse files Browse the repository at this point in the history
  • Loading branch information
Aurélien Richez committed Jul 26, 2021
1 parent 5ccbdfd commit 6549743
Show file tree
Hide file tree
Showing 24 changed files with 139 additions and 84 deletions.
2 changes: 2 additions & 0 deletions bytes/src/main/scala/io/iohk/ethereum/utils/Hex.scala
@@ -1,5 +1,7 @@
package io.iohk.ethereum.utils

import akka.util.ByteString

object Hex {
def toHexString(bytes: Array[Byte]): String =
bytes.map("%02x".format(_)).mkString
Expand Down
Expand Up @@ -107,7 +107,7 @@ class BlockImporterItSpec
blockchainWriter.save(oldBlock3, Nil, oldWeight3, saveAsBestBlock = true)
blockchainWriter.save(oldBlock4, Nil, oldWeight4, saveAsBestBlock = true)
// simulation of node restart
blockchain.saveBestKnownBlocks(blockchainReader.getBestBlockNumber() - 1)
blockchain.saveBestKnownBlocks(oldBlock3.header.hash, oldBlock3.number)
blockchainWriter.save(newBlock4ParentOldBlock3, Nil, newBlock4WeightParentOldBlock3, saveAsBestBlock = true)

//not reorganising anymore until oldBlock4(not part of the chain anymore), no block/ommer validation when not part of the chain, resolveBranch is returning UnknownBranch
Expand Down
Expand Up @@ -1136,7 +1136,7 @@ class FastSync(
val bestReceivedBlock = fullBlocks.maxBy(_.number)
val lastStoredBestBlockNumber = appStateStorage.getBestBlockNumber()
if (lastStoredBestBlockNumber < bestReceivedBlock.number) {
blockchain.saveBestKnownBlocks(bestReceivedBlock.number)
blockchain.saveBestKnownBlocks(bestReceivedBlock.hash, bestReceivedBlock.number)
appStateStorage.putBestBlockNumber(bestReceivedBlock.number).commit()
}
syncState = syncState.copy(lastFullBlockNumber = bestReceivedBlock.number.max(lastStoredBestBlockNumber))
Expand Down
29 changes: 27 additions & 2 deletions src/main/scala/io/iohk/ethereum/db/storage/AppStateStorage.scala
@@ -1,12 +1,19 @@
package io.iohk.ethereum.db.storage

import java.math.BigInteger
import akka.util.ByteString
import boopickle.Default.Unpickle
import boopickle.Pickler
import boopickle.DefaultBasic._

import java.math.BigInteger
import scala.collection.immutable.ArraySeq

import io.iohk.ethereum.db.dataSource.DataSource
import io.iohk.ethereum.db.dataSource.DataSourceBatchUpdate
import io.iohk.ethereum.db.storage.AppStateStorage._
import io.iohk.ethereum.domain.appstate.BestBlockInfo
import io.iohk.ethereum.utils.ByteUtils.{byteSequenceToBuffer, compactPickledBytes}
import io.iohk.ethereum.utils.Hex
import io.iohk.ethereum.utils.Picklers._

/** This class is used to store app state variables
* Key: see AppStateStorage.Keys
Expand All @@ -27,6 +34,16 @@ class AppStateStorage(val dataSource: DataSource) extends TransactionalKeyValueS
def getBestBlockNumber(): BigInt =
getBigInt(Keys.BestBlockNumber)

def getBestBlockData(): BestBlockInfo =
BestBlockInfo( // FIXME default value for hash ?
get(Keys.BestBlockHash).map(v => ByteString(Hex.decode(v))).getOrElse(ByteString.empty),
getBigInt(Keys.BestBlockNumber)
)

def putBestBlockData(b: BestBlockInfo): DataSourceBatchUpdate =
put(Keys.BestBlockNumber, b.number.toString)
.and(put(Keys.BestBlockHash, Hex.toHexString(b.hash.toArray)))

def putBestBlockNumber(bestBlockNumber: BigInt): DataSourceBatchUpdate =
put(Keys.BestBlockNumber, bestBlockNumber.toString)

Expand Down Expand Up @@ -72,9 +89,17 @@ object AppStateStorage {

object Keys {
val BestBlockNumber = "BestBlockNumber"
val BestBlockHash = "BestBlockHash"
val FastSyncDone = "FastSyncDone"
val EstimatedHighestBlock = "EstimatedHighestBlock"
val SyncStartingBlock = "SyncStartingBlock"
val LatestCheckpointBlockNumber = "LatestCheckpointBlockNumber"
}

implicit private val bestBlockDataPickler: Pickler[BestBlockInfo] = generatePickler[BestBlockInfo]
private val bestBlockDataSerializer = (bestBlockData: BestBlockInfo) =>
compactPickledBytes(Pickle.intoBytes(bestBlockData))
private val bestBlockDataDeserializer =
(byteSequenceToBuffer _).andThen(Unpickle[BestBlockInfo].fromBytes)

}
47 changes: 30 additions & 17 deletions src/main/scala/io/iohk/ethereum/domain/Blockchain.scala
@@ -1,15 +1,14 @@
package io.iohk.ethereum.domain

import akka.util.ByteString

import cats.instances.option._
import cats.syntax.flatMap._

import scala.annotation.tailrec

import io.iohk.ethereum.db.dataSource.DataSourceBatchUpdate
import io.iohk.ethereum.db.storage._
import io.iohk.ethereum.domain
import io.iohk.ethereum.domain.appstate.BestBlockInfo
import io.iohk.ethereum.jsonrpc.ProofService.StorageProof
import io.iohk.ethereum.ledger.InMemoryWorldStateProxy
import io.iohk.ethereum.ledger.InMemoryWorldStateProxyStorage
Expand Down Expand Up @@ -76,7 +75,11 @@ trait Blockchain {

def removeBlock(hash: ByteString, withState: Boolean): Unit

def saveBestKnownBlocks(bestBlockNumber: BigInt, latestCheckpointNumber: Option[BigInt] = None): Unit
def saveBestKnownBlocks(
bestBlockHash: ByteString,
bestBlockNumber: BigInt,
latestCheckpointNumber: Option[BigInt] = None
): Unit

}

Expand Down Expand Up @@ -171,20 +174,29 @@ class BlockchainImpl(
.commit()
}

override def saveBestKnownBlocks(bestBlockNumber: BigInt, latestCheckpointNumber: Option[BigInt] = None): Unit =
override def saveBestKnownBlocks(
bestBlockHash: ByteString,
bestBlockNumber: BigInt,
latestCheckpointNumber: Option[BigInt] = None
): Unit =
latestCheckpointNumber match {
case Some(number) =>
saveBestKnownBlockAndLatestCheckpointNumber(bestBlockNumber, number)
saveBestKnownBlockAndLatestCheckpointNumber(bestBlockHash, bestBlockNumber, number)
case None =>
saveBestKnownBlock(bestBlockNumber)
saveBestKnownBlock(bestBlockHash, bestBlockNumber)
}

private def saveBestKnownBlock(bestBlockNumber: BigInt): Unit =
blockchainMetadata.bestKnownBlockAndLatestCheckpoint.updateAndGet(_.copy(bestBlockNumber = bestBlockNumber))

private def saveBestKnownBlockAndLatestCheckpointNumber(number: BigInt, latestCheckpointNumber: BigInt): Unit =
private def saveBestKnownBlock(bestBlockHash: ByteString, bestBlockNumber: BigInt): Unit =
blockchainMetadata.bestKnownBlockAndLatestCheckpoint.updateAndGet(v =>
v.copy(bestBlockInfo = BestBlockInfo(bestBlockHash, bestBlockNumber))
)
private def saveBestKnownBlockAndLatestCheckpointNumber(
bestBlockHash: ByteString,
number: BigInt,
latestCheckpointNumber: BigInt
): Unit =
blockchainMetadata.bestKnownBlockAndLatestCheckpoint.set(
BestBlockLatestCheckpointNumbers(number, latestCheckpointNumber)
BestBlockLatestCheckpointNumbers(BestBlockInfo(bestBlockHash, number), latestCheckpointNumber)
)

private def removeBlockNumberMapping(number: BigInt): DataSourceBatchUpdate =
Expand Down Expand Up @@ -215,7 +227,8 @@ class BlockchainImpl(
removeBlockNumberMapping(block.number)
else blockNumberMappingStorage.emptyBatchUpdate

val newBestBlockNumber: BigInt = (bestBlockNumber - 1).max(0)
val potientialNewBestBlockNumber: BigInt = (block.number - 1).max(0)
val potientialNewBestBlockHash: ByteString = block.header.parentHash
val newLatestCheckpointNumber: BigInt =
if (block.hasCheckpoint && block.number == latestCheckpointNumber) {
findPreviousCheckpointBlockNumber(block.number, block.number)
Expand All @@ -231,8 +244,8 @@ class BlockchainImpl(
into the case of having an incomplete best block and so an inconsistent db
*/
val bestBlockNumberUpdates =
if (appStateStorage.getBestBlockNumber() > newBestBlockNumber)
appStateStorage.putBestBlockNumber(newBestBlockNumber)
if (appStateStorage.getBestBlockNumber() > potientialNewBestBlockNumber)
appStateStorage.putBestBlockData(BestBlockInfo(potientialNewBestBlockHash, potientialNewBestBlockNumber))
else appStateStorage.emptyBatchUpdate
val latestCheckpointNumberUpdates =
if (appStateStorage.getLatestCheckpointBlockNumber() > newLatestCheckpointNumber)
Expand All @@ -241,7 +254,7 @@ class BlockchainImpl(

log.debug(
"Persisting app info data into database. Persisted block number is {}. Persisted checkpoint number is {}",
newBestBlockNumber,
potientialNewBestBlockNumber,
newLatestCheckpointNumber
)

Expand All @@ -256,11 +269,11 @@ class BlockchainImpl(
.and(latestCheckpointNumberUpdates)
.commit()

saveBestKnownBlocks(newBestBlockNumber, Some(newLatestCheckpointNumber))
saveBestKnownBlocks(potientialNewBestBlockHash, potientialNewBestBlockNumber, Some(newLatestCheckpointNumber))
log.debug(
"Removed block with hash {}. New best block number - {}, new best checkpoint block number - {}",
ByteStringUtils.hash2string(blockHash),
newBestBlockNumber,
potientialNewBestBlockNumber,
newLatestCheckpointNumber
)

Expand Down
@@ -1,10 +1,12 @@
package io.iohk.ethereum.domain

import io.iohk.ethereum.domain.appstate.BestBlockInfo

import java.util.concurrent.atomic.AtomicReference

class BlockchainMetadata(bestBlockNumber: BigInt, latestCheckpointNumber: BigInt) {
class BlockchainMetadata(bestBlockData: BestBlockInfo, latestCheckpointNumber: BigInt) {
lazy val bestKnownBlockAndLatestCheckpoint: AtomicReference[BestBlockLatestCheckpointNumbers] =
new AtomicReference(BestBlockLatestCheckpointNumbers(bestBlockNumber, latestCheckpointNumber))
new AtomicReference(BestBlockLatestCheckpointNumbers(bestBlockData, latestCheckpointNumber))
}

case class BestBlockLatestCheckpointNumbers(bestBlockNumber: BigInt, latestCheckpointNumber: BigInt)
case class BestBlockLatestCheckpointNumbers(bestBlockInfo: BestBlockInfo, latestCheckpointNumber: BigInt)
Expand Up @@ -84,7 +84,7 @@ class BlockchainReader(

def getBestBlockNumber(): BigInt = {
val bestSavedBlockNumber = appStateStorage.getBestBlockNumber()
val bestKnownBlockNumber = blockchainMetadata.bestKnownBlockAndLatestCheckpoint.get().bestBlockNumber
val bestKnownBlockNumber = blockchainMetadata.bestKnownBlockAndLatestCheckpoint.get().bestBlockInfo.number
log.debug(
"Current best saved block number {}. Current best known block number {}",
bestSavedBlockNumber,
Expand Down
26 changes: 16 additions & 10 deletions src/main/scala/io/iohk/ethereum/domain/BlockchainWriter.scala
@@ -1,7 +1,6 @@
package io.iohk.ethereum.domain

import akka.util.ByteString

import io.iohk.ethereum.db.dataSource.DataSourceBatchUpdate
import io.iohk.ethereum.db.storage.AppStateStorage
import io.iohk.ethereum.db.storage.BlockBodiesStorage
Expand All @@ -12,6 +11,7 @@ import io.iohk.ethereum.db.storage.ReceiptStorage
import io.iohk.ethereum.db.storage.StateStorage
import io.iohk.ethereum.db.storage.TransactionMappingStorage
import io.iohk.ethereum.db.storage.TransactionMappingStorage.TransactionLocation
import io.iohk.ethereum.domain.appstate.BestBlockInfo
import io.iohk.ethereum.utils.Logger

class BlockchainWriter(
Expand All @@ -33,13 +33,13 @@ class BlockchainWriter(
block.header.number,
block.header.number
)
saveBestKnownBlockAndLatestCheckpointNumber(block.header.number, block.header.number)
saveBestKnownBlockAndLatestCheckpointNumber(block.header.hash, block.header.number, block.header.number)
} else if (saveAsBestBlock) {
log.debug(
"New best known block number - {}",
block.header.number
)
saveBestKnownBlock(block.header.number)
saveBestKnownBlock(block.header.hash, block.header.number)
}

log.debug("Saving new block {} to database", block.idTag)
Expand All @@ -59,8 +59,10 @@ class BlockchainWriter(
def storeChainWeight(blockHash: ByteString, weight: ChainWeight): DataSourceBatchUpdate =
chainWeightStorage.put(blockHash, weight)

private def saveBestKnownBlock(bestBlockNumber: BigInt): Unit =
blockchainMetadata.bestKnownBlockAndLatestCheckpoint.updateAndGet(_.copy(bestBlockNumber = bestBlockNumber))
private def saveBestKnownBlock(bestBlockHash: ByteString, bestBlockNumber: BigInt): Unit =
blockchainMetadata.bestKnownBlockAndLatestCheckpoint.updateAndGet(v =>
v.copy(bestBlockInfo = BestBlockInfo(bestBlockHash, bestBlockNumber))
)

/** Persists a block in the underlying Blockchain Database
* Note: all store* do not update the database immediately, rather they create
Expand Down Expand Up @@ -88,23 +90,27 @@ class BlockchainWriter(
updates.and(transactionMappingStorage.put(tx.hash, TransactionLocation(blockHash, index)))
}

private def saveBestKnownBlockAndLatestCheckpointNumber(number: BigInt, latestCheckpointNumber: BigInt): Unit =
private def saveBestKnownBlockAndLatestCheckpointNumber(
bestBlockHash: ByteString,
number: BigInt,
latestCheckpointNumber: BigInt
): Unit =
blockchainMetadata.bestKnownBlockAndLatestCheckpoint.set(
BestBlockLatestCheckpointNumbers(number, latestCheckpointNumber)
BestBlockLatestCheckpointNumbers(BestBlockInfo(bestBlockHash, number), latestCheckpointNumber)
)

private def persistBestBlocksData(): Unit = {
val currentBestBlockNumber = blockchainMetadata.bestKnownBlockAndLatestCheckpoint.get().bestBlockNumber
val currentBestBlockInfo = blockchainMetadata.bestKnownBlockAndLatestCheckpoint.get().bestBlockInfo
val currentBestCheckpointNumber = blockchainMetadata.bestKnownBlockAndLatestCheckpoint.get().latestCheckpointNumber
log.debug(
"Persisting app info data into database. Persisted block number is {}. " +
"Persisted checkpoint number is {}",
currentBestBlockNumber,
currentBestBlockInfo.number,
currentBestCheckpointNumber
)

appStateStorage
.putBestBlockNumber(currentBestBlockNumber)
.putBestBlockData(currentBestBlockInfo)
.and(appStateStorage.putLatestCheckpointBlockNumber(currentBestCheckpointNumber))
.commit()
}
Expand Down
@@ -0,0 +1,5 @@
package io.iohk.ethereum.domain.appstate

import akka.util.ByteString

case class BestBlockInfo(hash: ByteString, number: BigInt)
6 changes: 3 additions & 3 deletions src/main/scala/io/iohk/ethereum/ledger/BlockImport.scala
Expand Up @@ -299,9 +299,9 @@ class BlockImport(
case BlockData(block, _, _) if block.hasCheckpoint => block.number
}.maximumOption

val bestNumber = oldBranch.last.block.header.number
blockchain.saveBestKnownBlocks(bestNumber, checkpointNumber)
executedBlocks.foreach(data => blockQueue.enqueueBlock(data.block, bestNumber))
val bestHeader = oldBranch.last.block.header
blockchain.saveBestKnownBlocks(bestHeader.hash, bestHeader.number, checkpointNumber)
executedBlocks.foreach(data => blockQueue.enqueueBlock(data.block, bestHeader.number))

newBranch.diff(executedBlocks.map(_.block)).headOption.foreach { block =>
blockQueue.removeSubtree(block.header.hash)
Expand Down
Expand Up @@ -176,7 +176,7 @@ trait BlockchainBuilder {

private lazy val blockchainMetadata: BlockchainMetadata =
new BlockchainMetadata(
storagesInstance.storages.appStateStorage.getBestBlockNumber(),
storagesInstance.storages.appStateStorage.getBestBlockData(),
storagesInstance.storages.appStateStorage.getLatestCheckpointBlockNumber()
)
lazy val blockchainReader: BlockchainReader = BlockchainReader(storagesInstance.storages, blockchainMetadata)
Expand Down
@@ -1,10 +1,12 @@
package io.iohk.ethereum.blockchain.sync

import io.iohk.ethereum.Fixtures
import io.iohk.ethereum.db.components.EphemDataSourceComponent
import io.iohk.ethereum.db.components.Storages
import io.iohk.ethereum.db.storage.pruning.ArchivePruning
import io.iohk.ethereum.db.storage.pruning.PruningMode
import io.iohk.ethereum.domain.BlockchainMetadata
import io.iohk.ethereum.domain.appstate.BestBlockInfo
import io.iohk.ethereum.ledger.VMImpl
import io.iohk.ethereum.nodebuilder.PruningConfigBuilder

Expand All @@ -24,5 +26,5 @@ trait EphemBlockchainTestSetup extends ScenarioSetup {
def getNewStorages: EphemDataSourceComponent with LocalPruningConfigBuilder with Storages.DefaultStorages =
new EphemDataSourceComponent with LocalPruningConfigBuilder with Storages.DefaultStorages

def getNewBlockchainMetadata = new BlockchainMetadata(0, 0)
def getNewBlockchainMetadata = new BlockchainMetadata(BestBlockInfo(Fixtures.Blocks.Genesis.header.hash, 0), 0)
}
Expand Up @@ -736,7 +736,7 @@ class RegularSyncSpec
goToTop()

val num: BigInt = 42
blockchain.saveBestKnownBlocks(num, Some(num))
blockchain.saveBestKnownBlocks(testBlocks.head.hash, num, Some(num))

etcPeerManager.expectMsg(GetHandshakedPeers)
etcPeerManager.reply(HandshakedPeers(handshakedPeers))
Expand Down
Expand Up @@ -464,7 +464,7 @@ class StdOmmersValidatorSpec extends AnyFlatSpec with Matchers with ScalaCheckPr
.and(blockchainWriter.storeBlock(block95))
.and(blockchainWriter.storeBlock(block96))
.commit()
blockchain.saveBestKnownBlocks(block96.number)
blockchain.saveBestKnownBlocks(block96.hash, block96.number)

}
}
4 changes: 2 additions & 2 deletions src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala
Expand Up @@ -44,7 +44,7 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh
it should "be able to store a block and retrieve it by number" in new EphemBlockchainTestSetup {
val validBlock = Fixtures.Blocks.ValidBlock.block
blockchainWriter.storeBlock(validBlock).commit()
blockchain.saveBestKnownBlocks(validBlock.number)
blockchain.saveBestKnownBlocks(validBlock.hash, validBlock.number)
val block = blockchainReader.getBestBranch().getBlockByNumber(validBlock.header.number)
block.isDefined should ===(true)
validBlock should ===(block.get)
Expand All @@ -61,7 +61,7 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh
blockchainWriter.save(validBlock, Seq.empty, ChainWeight(100, 100), saveAsBestBlock = true)
blockchainReader.getBestBranch().isInChain(validBlock.hash) should ===(true)
// simulation of node restart
blockchain.saveBestKnownBlocks(validBlock.header.number - 1)
blockchain.saveBestKnownBlocks(validBlock.header.parentHash, validBlock.header.number - 1)
blockchainReader.getBestBranch().isInChain(validBlock.hash) should ===(false)
}

Expand Down

0 comments on commit 6549743

Please sign in to comment.