com.wavesplatform.state.BlockchainUpdaterImpl.scala Maven / Gradle / Ivy
The newest version!
package com.wavesplatform.state
import cats.syntax.either.*
import cats.syntax.option.*
import com.wavesplatform.account.{Address, Alias}
import com.wavesplatform.api.BlockMeta
import com.wavesplatform.block.Block.BlockId
import com.wavesplatform.block.{Block, BlockSnapshot, MicroBlock, MicroBlockSnapshot, SignedBlockHeader}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.database.RocksDBWriter
import com.wavesplatform.events.BlockchainUpdateTriggers
import com.wavesplatform.features.BlockchainFeatures
import com.wavesplatform.features.BlockchainFeatures.ConsensusImprovements
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.metrics.{TxsInBlockchainStats, *}
import com.wavesplatform.mining.{Miner, MiningConstraint, MiningConstraints}
import com.wavesplatform.settings.{BlockchainSettings, WavesSettings}
import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult.{Applied, Ignored}
import com.wavesplatform.state.TxMeta.Status
import com.wavesplatform.state.diffs.BlockDiffer
import com.wavesplatform.transaction.*
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.TxValidationError.{BlockAppendError, GenericError, MicroBlockAppendError}
import com.wavesplatform.transaction.transfer.TransferTransactionLike
import com.wavesplatform.utils.{ScorexLogging, Time, UnsupportedFeature, forceStopApplication}
import kamon.Kamon
import monix.reactive.Observable
import monix.reactive.subjects.ReplaySubject
import java.util.concurrent.locks.{Lock, ReentrantReadWriteLock}
import scala.collection.immutable.VectorMap
class BlockchainUpdaterImpl(
val rocksdb: RocksDBWriter,
wavesSettings: WavesSettings,
time: Time,
blockchainUpdateTriggers: BlockchainUpdateTriggers,
collectActiveLeases: (Int, Int) => Map[ByteStr, LeaseDetails],
miner: Miner = _ => ()
) extends Blockchain
with BlockchainUpdater
with NG
with ScorexLogging {
import com.wavesplatform.state.BlockchainUpdaterImpl.*
import wavesSettings.blockchainSettings.functionalitySettings
private def inLock[R](l: Lock, f: => R): R = {
l.lockInterruptibly()
try f
finally l.unlock()
}
private val lock = new ReentrantReadWriteLock(true)
private def writeLock[B](f: => B): B = inLock(lock.writeLock(), f)
private def readLock[B](f: => B): B = inLock(lock.readLock(), f)
private lazy val maxBlockReadinessAge = wavesSettings.minerSettings.intervalAfterLastBlockThenGenerationIsAllowed.toMillis
@volatile
private[this] var ngState: Option[NgState] = Option.empty
@volatile
private[this] var restTotalConstraint: MiningConstraint = MiningConstraints(rocksdb, rocksdb.height).total
private val internalLastBlockInfo = ReplaySubject.createLimited[LastBlockInfo](1)
private def lastBlockReward: Option[Long] = this.blockReward(this.height)
private def publishLastBlockInfo(): Unit =
for (id <- this.lastBlockId; ts <- ngState.map(_.base.header.timestamp).orElse(rocksdb.lastBlockTimestamp)) {
val blockchainReady = ts + maxBlockReadinessAge > time.correctedTime()
internalLastBlockInfo.onNext(LastBlockInfo(id, height, score, blockchainReady))
}
publishLastBlockInfo()
def liquidBlock(id: ByteStr): Option[Block] = readLock(ngState.flatMap(_.snapshotOf(id).map(_._1)))
def liquidBlockSnapshot(id: ByteStr): Option[StateSnapshot] = readLock(ngState.flatMap(_.snapshotOf(id).map(_._2)))
def microBlockSnapshot(totalBlockId: ByteStr): Option[StateSnapshot] = readLock(ngState.flatMap(_.microSnapshots.get(totalBlockId).map(_.snapshot)))
def liquidTransactions(id: ByteStr): Option[Seq[(TxMeta, Transaction)]] =
readLock(
ngState
.flatMap(_.snapshotOf(id))
.map { case (_, snapshot, _, _, _, _) =>
snapshot.transactions.toSeq.map { case (_, info) => (TxMeta(Height(height), info.status, info.spentComplexity), info.transaction) }
}
)
def liquidBlockMeta: Option[BlockMeta] =
readLock(ngState.map { ng =>
val (_, _, totalFee) = ng.bestLiquidSnapshotAndFees
val b = ng.bestLiquidBlock
val vrf = if (b.header.version >= Block.ProtoBlockVersion) hitSource(height) else None
BlockMeta.fromBlock(b, height, totalFee, ng.reward, vrf)
})
@noinline
def bestLiquidSnapshot: Option[StateSnapshot] = readLock(ngState.map(_.bestLiquidSnapshot))
def bestLiquidSnapshotAndFees: Option[(StateSnapshot, Long, Long)] = readLock(ngState.map(_.bestLiquidSnapshotAndFees))
override val settings: BlockchainSettings = wavesSettings.blockchainSettings
override def isLastBlockId(id: ByteStr): Boolean = readLock {
ngState.fold(rocksdb.lastBlockId.contains(id))(_.contains(id))
}
override val lastBlockInfo: Observable[LastBlockInfo] = internalLastBlockInfo
private def featuresApprovedWithBlock(block: Block): Set[Short] = {
val height = rocksdb.height + 1
val featuresCheckPeriod = functionalitySettings.activationWindowSize(height)
val blocksForFeatureActivation = functionalitySettings.blocksForFeatureActivation(height)
if (height % featuresCheckPeriod == 0) {
val approvedFeatures = rocksdb
.featureVotes(height)
.map { case (feature, votes) => feature -> (if (block.header.featureVotes.contains(feature)) votes + 1 else votes) }
.filter { case (_, votes) => votes >= blocksForFeatureActivation }
.keySet
.filterNot(settings.functionalitySettings.preActivatedFeatures.contains)
if (approvedFeatures.nonEmpty) log.info(s"${displayFeatures(approvedFeatures)} APPROVED at height $height")
val unimplementedApproved = approvedFeatures.diff(BlockchainFeatures.implemented)
if (unimplementedApproved.nonEmpty) {
log.warn(s"""UNIMPLEMENTED ${displayFeatures(unimplementedApproved)} APPROVED ON BLOCKCHAIN
|PLEASE, UPDATE THE NODE AS SOON AS POSSIBLE
|OTHERWISE THE NODE WILL BE STOPPED OR FORKED UPON FEATURE ACTIVATION""".stripMargin)
}
val activatedFeatures: Set[Short] = rocksdb.activatedFeaturesAt(height)
val unimplementedActivated = activatedFeatures.diff(BlockchainFeatures.implemented)
if (unimplementedActivated.nonEmpty) {
log.error(s"UNIMPLEMENTED ${displayFeatures(unimplementedActivated)} ACTIVATED ON BLOCKCHAIN")
log.error("PLEASE, UPDATE THE NODE IMMEDIATELY")
if (wavesSettings.featuresSettings.autoShutdownOnUnsupportedFeature) {
log.error("FOR THIS REASON THE NODE WAS STOPPED AUTOMATICALLY")
forceStopApplication(UnsupportedFeature)
} else log.error("OTHERWISE THE NODE WILL END UP ON A FORK")
}
approvedFeatures
} else {
Set.empty
}
}
def computeNextReward: Option[Long] = {
val settings = this.settings.rewardsSettings
val nextHeight = this.height + 1
if (height == 0 && rocksdb.featureActivationHeight(ConsensusImprovements.id).exists(_ <= 1))
None
else
rocksdb
.featureActivationHeight(BlockchainFeatures.BlockReward.id)
.filter(_ <= nextHeight)
.flatMap { activatedAt =>
val mayBeReward = lastBlockReward
val mayBeTimeToVote = nextHeight - activatedAt
val modifiedTerm = if (rocksdb.isFeatureActivated(BlockchainFeatures.CappedReward, this.height)) {
settings.termAfterCappedRewardFeature
} else {
settings.term
}
mayBeReward match {
case Some(reward) if mayBeTimeToVote > 0 && mayBeTimeToVote % modifiedTerm == 0 =>
Some((blockRewardVotes(this.height).filter(_ >= 0), reward))
case None if mayBeTimeToVote >= 0 =>
Some((Seq(), settings.initial))
case _ => None
}
}
.flatMap { case (votes, currentReward) =>
val lt = votes.count(_ < currentReward)
val gt = votes.count(_ > currentReward)
val threshold = settings.votingInterval / 2 + 1
if (lt >= threshold)
Some(math.max(currentReward - settings.minIncrement, 0))
else if (gt >= threshold)
Some(currentReward + settings.minIncrement)
else
Some(currentReward)
}
.orElse(lastBlockReward)
}
override def processBlock(
block: Block,
hitSource: ByteStr,
snapshot: Option[BlockSnapshot],
challengedHitSource: Option[ByteStr] = None,
verify: Boolean = true,
txSignParCheck: Boolean = true
): Either[ValidationError, BlockApplyResult] =
writeLock {
val height = rocksdb.height
val notImplementedFeatures: Set[Short] = rocksdb.activatedFeaturesAt(height).diff(BlockchainFeatures.implemented)
Either
.cond(
!wavesSettings.featuresSettings.autoShutdownOnUnsupportedFeature || notImplementedFeatures.isEmpty,
(),
GenericError(s"UNIMPLEMENTED ${displayFeatures(notImplementedFeatures)} ACTIVATED ON BLOCKCHAIN, UPDATE THE NODE IMMEDIATELY")
)
.flatMap[ValidationError, BlockApplyResult](_ =>
(ngState match {
case None =>
rocksdb.lastBlockId match {
case Some(uniqueId) if uniqueId != block.header.reference =>
val logDetails = s"The referenced block(${block.header.reference})" +
s" ${if (rocksdb.contains(block.header.reference)) "exists, it's not last persisted" else "doesn't exist"}"
Left(BlockAppendError(s"References incorrect or non-existing block: " + logDetails, block))
case lastBlockId =>
val height = lastBlockId.fold(0)(rocksdb.unsafeHeightOf)
val miningConstraints = MiningConstraints(rocksdb, height)
val reward = computeNextReward
val referencedBlockchain = SnapshotBlockchain(rocksdb, reward)
BlockDiffer
.fromBlock(
referencedBlockchain,
rocksdb.lastBlock,
block,
snapshot,
miningConstraints.total,
hitSource,
challengedHitSource,
rocksdb.loadCacheData,
verify,
txSignParCheck = txSignParCheck
)
.map { r =>
val updatedBlockchain = SnapshotBlockchain(rocksdb, r.snapshot, block, hitSource, r.carry, reward, Some(r.computedStateHash))
miner.scheduleMining(Some(updatedBlockchain))
blockchainUpdateTriggers.onProcessBlock(block, r.keyBlockSnapshot, reward, hitSource, referencedBlockchain)
Option((r, Nil, reward, hitSource))
}
}
case Some(ng) =>
if (ng.base.header.reference == block.header.reference) {
if (block.header.timestamp < ng.base.header.timestamp) {
val height = rocksdb.unsafeHeightOf(ng.base.header.reference)
val miningConstraints = MiningConstraints(rocksdb, height)
val referencedBlockchain = SnapshotBlockchain(rocksdb, ng.reward)
BlockDiffer
.fromBlock(
referencedBlockchain,
rocksdb.lastBlock,
block,
snapshot,
miningConstraints.total,
hitSource,
challengedHitSource,
rocksdb.loadCacheData,
verify,
txSignParCheck = txSignParCheck
)
.map { r =>
log.trace(
s"Better liquid block(timestamp=${block.header.timestamp}) received and applied instead of existing(timestamp=${ng.base.header.timestamp})"
)
BlockStats.replaced(ng.base, block)
val (mbs, mbSnapshots) = ng.allSnapshots.unzip
val allSnapshots = ng.baseBlockSnapshot +: mbSnapshots
log.trace(s"Discarded microblocks = $mbs, snapshots = ${allSnapshots.map(_.hashString)}")
val updatedBlockchain = SnapshotBlockchain(referencedBlockchain, r.snapshot, block, hitSource, r.carry, None, None)
miner.scheduleMining(Some(updatedBlockchain))
blockchainUpdateTriggers.onRollback(this, ng.base.header.reference, rocksdb.height)
blockchainUpdateTriggers.onProcessBlock(block, r.keyBlockSnapshot, ng.reward, hitSource, referencedBlockchain)
Some((r, allSnapshots, ng.reward, hitSource))
}
} else if (areVersionsOfSameBlock(block, ng.base)) {
// silently ignore
Right(None)
} else
Left(
BlockAppendError(
s"Competitors liquid block $block(timestamp=${block.header.timestamp}) is not better than existing (ng.base ${ng.base}(timestamp=${ng.base.header.timestamp}))",
block
)
)
} else
metrics.forgeBlockTimeStats.measureOptional(ng.snapshotOf(block.header.reference)) match {
case None => Left(BlockAppendError(s"References incorrect or non-existing block", block))
case Some((referencedForgedBlock, referencedLiquidSnapshot, carry, totalFee, referencedComputedStateHash, discarded)) =>
if (!verify || referencedForgedBlock.signatureValid()) {
val height = rocksdb.heightOf(referencedForgedBlock.header.reference).getOrElse(0)
val constraint: MiningConstraint = {
val miningConstraints = MiningConstraints(rocksdb, height)
miningConstraints.total
}
val prevReward = ng.reward
val reward = computeNextReward
val prevHitSource = ng.hitSource
val liquidSnapshotWithCancelledLeases = ng.cancelExpiredLeases(referencedLiquidSnapshot)
val referencedBlockchain = SnapshotBlockchain(
rocksdb,
liquidSnapshotWithCancelledLeases,
referencedForgedBlock,
ng.hitSource,
carry,
reward,
Some(referencedComputedStateHash)
)
for {
differResult <- BlockDiffer
.fromBlock(
referencedBlockchain,
Some(referencedForgedBlock),
block,
snapshot,
constraint,
hitSource,
challengedHitSource,
rocksdb.loadCacheData,
verify,
txSignParCheck = txSignParCheck
)
} yield {
val tempBlockchain = SnapshotBlockchain(
referencedBlockchain,
differResult.snapshot,
block,
hitSource,
differResult.carry,
None,
Some(differResult.computedStateHash)
)
miner.scheduleMining(Some(tempBlockchain))
log.trace(
s"Persisting block ${referencedForgedBlock.id()}, discarded microblock refs: ${discarded.map(_._1.reference).mkString("[", ",", "]")}"
)
if (discarded.nonEmpty) {
blockchainUpdateTriggers.onMicroBlockRollback(this, block.header.reference)
metrics.microBlockForkStats.increment()
metrics.microBlockForkHeightStats.record(discarded.size)
}
rocksdb.append(
liquidSnapshotWithCancelledLeases,
carry,
totalFee,
prevReward,
prevHitSource,
referencedComputedStateHash,
referencedForgedBlock
)
BlockStats.appended(referencedForgedBlock, referencedLiquidSnapshot.scriptsComplexity)
TxsInBlockchainStats.record(ng.transactions.size)
blockchainUpdateTriggers.onProcessBlock(block, differResult.keyBlockSnapshot, reward, hitSource, rocksdb)
val (discardedMbs, discardedSnapshots) = discarded.unzip
if (discardedMbs.nonEmpty) {
log.trace(s"Discarded microblocks: $discardedMbs")
}
Some((differResult, discardedSnapshots, reward, hitSource))
}
} else {
val errorText = s"Forged block has invalid signature. Base: ${ng.base}, requested reference: ${block.header.reference}"
log.error(errorText)
Left(BlockAppendError(errorText, block))
}
}
}).map {
_ map {
case (
BlockDiffer.Result(newBlockSnapshot, carry, totalFee, updatedTotalConstraint, _, computedStateHash),
discDiffs,
reward,
hitSource
) =>
val newHeight = rocksdb.height + 1
restTotalConstraint = updatedTotalConstraint
ngState = Some(
new NgState(
block,
newBlockSnapshot,
carry,
totalFee,
computedStateHash,
featuresApprovedWithBlock(block),
reward,
hitSource,
cancelLeases(collectLeasesToCancel(newHeight), newHeight)
)
)
publishLastBlockInfo()
if (
(block.header.timestamp > time
.getTimestamp() - wavesSettings.minerSettings.intervalAfterLastBlockThenGenerationIsAllowed.toMillis) || (newHeight % 100 == 0)
) {
log.info(s"New height: $newHeight")
}
Applied(discDiffs, this.score)
} getOrElse Ignored
}
)
}
private def collectLeasesToCancel(newHeight: Int): Map[ByteStr, LeaseDetails] =
if (rocksdb.isFeatureActivated(BlockchainFeatures.LeaseExpiration, newHeight)) {
val toHeight = newHeight - rocksdb.settings.functionalitySettings.leaseExpiration
val fromHeight = rocksdb.featureActivationHeight(BlockchainFeatures.LeaseExpiration.id) match {
case Some(`newHeight`) =>
log.trace(s"Collecting leases created up till height $toHeight")
1
case _ =>
log.trace(s"Collecting leases created at height $toHeight")
toHeight
}
collectActiveLeases(fromHeight, toHeight)
} else Map.empty
private def cancelLeases(leaseDetails: Map[ByteStr, LeaseDetails], height: Int): Map[ByteStr, StateSnapshot] =
for {
(id, ld) <- leaseDetails
} yield id -> StateSnapshot
.build(
rocksdb,
Map(
ld.sender.toAddress -> Portfolio(0, LeaseBalance(0, -ld.amount.value)),
ld.recipientAddress -> Portfolio(0, LeaseBalance(-ld.amount.value, 0))
),
cancelledLeases = Map(
id -> LeaseDetails.Status.Expired(height)
)
)
.explicitGet()
override def removeAfter(blockId: ByteStr): Either[ValidationError, DiscardedBlocks] = writeLock {
log.info(s"Trying rollback blockchain to $blockId")
val prevNgState = ngState
val result = prevNgState match {
case Some(ng) if ng.contains(blockId) =>
log.trace("Resetting liquid block, no rollback necessary")
Right(Seq.empty)
case maybeNg =>
for {
height <- rocksdb.heightOf(blockId).toRight(GenericError(s"No such block $blockId"))
_ <- Either.cond(
height >= rocksdb.safeRollbackHeight,
(),
GenericError(s"Rollback is possible only to the block at the height ${rocksdb.safeRollbackHeight}")
)
_ = blockchainUpdateTriggers.onRollback(this, blockId, height)
blocks <- rocksdb.rollbackTo(height).leftMap(GenericError(_))
} yield {
ngState = None
val liquidBlockData = {
maybeNg.map { ng =>
val block = ng.bestLiquidBlock
val snapshot = if (wavesSettings.enableLightMode && block.transactionData.nonEmpty) {
Some(
BlockSnapshot(
block.id(),
ng.bestLiquidSnapshot.transactions.toSeq.map { case (_, txInfo) =>
(txInfo.snapshot.copy(transactions = VectorMap.empty), txInfo.status)
}
)
)
} else None
(block, ng.hitSource, snapshot)
}.toSeq
}
blocks ++ liquidBlockData
}
}
result match {
case Right(_) =>
log.info(s"Blockchain rollback to $blockId succeeded")
publishLastBlockInfo()
miner.scheduleMining()
case Left(error) =>
log.error(s"Blockchain rollback to $blockId failed: ${error.err}")
}
result
}
override def processMicroBlock(
microBlock: MicroBlock,
snapshot: Option[MicroBlockSnapshot],
verify: Boolean = true
): Either[ValidationError, BlockId] = writeLock {
ngState match {
case None =>
Left(MicroBlockAppendError("No base block exists", microBlock))
case Some(ng) if ng.base.header.generator.toAddress != microBlock.sender.toAddress =>
Left(MicroBlockAppendError("Base block has been generated by another account", microBlock))
case Some(ng) if ng.base.header.challengedHeader.nonEmpty =>
Left(MicroBlockAppendError("Base block has challenged header", microBlock))
case Some(ng) =>
ng.lastMicroBlock match {
case None if ng.base.id() != microBlock.reference =>
metrics.blockMicroForkStats.increment()
Left(MicroBlockAppendError("It's first micro and it doesn't reference base block(which exists)", microBlock))
case Some(_) if ng.bestLiquidBlockId != microBlock.reference =>
metrics.microMicroForkStats.increment()
Left(MicroBlockAppendError("It doesn't reference last known microBlock(which exists)", microBlock))
case _ =>
for {
_ <- microBlock.signaturesValid()
(totalSignatureValid, referencedComputedStateHash) <- ng
.snapshotOf(microBlock.reference)
.toRight(GenericError(s"No referenced block exists: $microBlock"))
.map { case (accumulatedBlock, _, _, _, computedStateHash, _) =>
Block
.create(
accumulatedBlock,
accumulatedBlock.transactionData ++ microBlock.transactionData,
microBlock.totalResBlockSig,
microBlock.stateHash
)
.signatureValid() -> computedStateHash
}
_ <- Either
.cond(
totalSignatureValid,
(),
MicroBlockAppendError("Invalid total block signature", microBlock)
)
blockDifferResult <- {
BlockDiffer.fromMicroBlock(
this,
rocksdb.lastBlockTimestamp,
referencedComputedStateHash,
microBlock,
snapshot,
restTotalConstraint,
rocksdb.loadCacheData,
verify
)
}
} yield {
val BlockDiffer.Result(snapshot, carry, totalFee, updatedMdConstraint, keyBlockSnapshot, computedStateHash) = blockDifferResult
restTotalConstraint = updatedMdConstraint
val blockId = ng.createBlockId(microBlock)
val transactionsRoot = ng.createTransactionsRoot(microBlock)
blockchainUpdateTriggers.onProcessMicroBlock(microBlock, keyBlockSnapshot, this, blockId, transactionsRoot)
this.ngState = Some(ng.append(microBlock, snapshot, carry, totalFee, System.currentTimeMillis, computedStateHash, Some(blockId)))
log.info(s"${microBlock.stringRepr(blockId)} appended, diff=${snapshot.hashString}")
internalLastBlockInfo.onNext(LastBlockInfo(blockId, height, score, ready = true))
blockId
}
}
}
}
def shutdown(): Unit = {
internalLastBlockInfo.onComplete()
}
private def newlyApprovedFeatures = ngState.fold(Map.empty[Short, Int])(_.approvedFeatures.map(_ -> height).toMap)
override def approvedFeatures: Map[Short, Int] = readLock {
newlyApprovedFeatures ++ rocksdb.approvedFeatures
}
override def activatedFeatures: Map[Short, Int] = readLock {
(newlyApprovedFeatures.view.mapValues(_ + functionalitySettings.activationWindowSize(height)) ++ rocksdb.activatedFeatures).toMap
}
override def featureVotes(height: Int): Map[Short, Int] = readLock {
val innerVotes = rocksdb.featureVotes(height)
ngState match {
case Some(ng) if this.height <= height =>
val ngVotes = ng.base.header.featureVotes.map { featureId =>
featureId -> (innerVotes.getOrElse(featureId, 0) + 1)
}.toMap
innerVotes ++ ngVotes
case _ => innerVotes
}
}
override def blockReward(height: Int): Option[Long] = readLock {
rocksdb.blockReward(height) match {
case r @ Some(_) => r
case None => ngState.collect { case ng if rocksdb.height + 1 == height => ng.reward }.flatten
}
}
override def blockRewardVotes(height: Int): Seq[Long] = readLock {
activatedFeatures.get(BlockchainFeatures.BlockReward.id) match {
case Some(activatedAt) if activatedAt <= height =>
ngState match {
case None => rocksdb.blockRewardVotes(height)
case Some(ng) =>
val innerVotes = rocksdb.blockRewardVotes(height)
val modifyTerm = activatedFeatures.get(BlockchainFeatures.CappedReward.id).exists(_ <= height)
if (height == this.height && settings.rewardsSettings.votingWindow(activatedAt, height, modifyTerm).contains(height))
innerVotes :+ ng.base.header.rewardVote
else innerVotes
}
case _ => Seq()
}
}
override def wavesAmount(height: Int): BigInt = readLock {
ngState match {
case Some(ng) if this.height == height =>
rocksdb.wavesAmount(height - 1) +
BigInt(ng.reward.getOrElse(0L)) * this.blockRewardBoost(height)
case _ =>
rocksdb.wavesAmount(height)
}
}
override def height: Int = readLock {
rocksdb.height + ngState.fold(0)(_ => 1)
}
override def heightOf(blockId: BlockId): Option[Int] = readLock {
ngState
.collect {
case ng if ng.contains(blockId) => this.height
}
.orElse(rocksdb.heightOf(blockId))
}
override def microBlock(id: BlockId): Option[MicroBlock] = readLock {
for {
ng <- ngState
mb <- ng.microBlock(id)
} yield mb
}
override def microblockIds: Seq[BlockId] = readLock {
ngState.fold(Seq.empty[BlockId])(_.microBlockIds)
}
override def bestLastBlockInfo(maxTimestamp: Long): Option[BlockMinerInfo] = readLock {
ngState
.map(_.bestLastBlockInfo(maxTimestamp))
.orElse(
rocksdb.lastBlockHeader.map { sh =>
BlockMinerInfo(sh.header.baseTarget, sh.header.generationSignature, sh.header.timestamp, sh.id())
}
)
}
override def score: BigInt = readLock {
rocksdb.score + ngState.fold(BigInt(0))(_.bestLiquidBlock.blockScore())
}
override def carryFee(refId: Option[ByteStr]): Long = readLock {
ngState
.map { ng =>
refId.filter(ng.contains).fold(ng.carryFee)(id => ng.snapshotFor(id)._2)
}
.getOrElse(rocksdb.carryFee(None))
}
override def blockHeader(height: Int): Option[SignedBlockHeader] = readLock {
if (height == rocksdb.height + 1) ngState.map { x =>
SignedBlockHeader(x.bestLiquidBlock.header, x.bestLiquidBlock.signature)
}
else rocksdb.blockHeader(height)
}
override def transferById(id: BlockId): Option[(Int, TransferTransactionLike)] = readLock {
snapshotBlockchain.transferById(id)
}
override def transactionInfo(id: ByteStr): Option[(TxMeta, Transaction)] = readLock {
snapshotBlockchain.transactionInfo(id)
}
override def transactionInfos(ids: Seq[BlockId]): Seq[Option[(TxMeta, Transaction)]] = readLock {
snapshotBlockchain.transactionInfos(ids)
}
override def containsTransaction(tx: Transaction): Boolean = readLock {
snapshotBlockchain.containsTransaction(tx)
}
override def assetDescription(id: IssuedAsset): Option[AssetDescription] = readLock {
snapshotBlockchain.assetDescription(id)
}
override def resolveAlias(alias: Alias): Either[ValidationError, Address] = readLock {
snapshotBlockchain.resolveAlias(alias)
}
override def leaseDetails(leaseId: ByteStr): Option[LeaseDetails] = readLock {
snapshotBlockchain.leaseDetails(leaseId)
}
override def filledVolumeAndFee(orderId: ByteStr): VolumeAndFee = readLock {
snapshotBlockchain.filledVolumeAndFee(orderId)
}
/** Retrieves Waves balance snapshot in the [from, to] range (inclusive) */
override def balanceAtHeight(address: Address, h: Int, assetId: Asset = Waves): Option[(Int, Long)] = readLock {
snapshotBlockchain.balanceAtHeight(address, h, assetId)
}
override def balanceSnapshots(address: Address, from: Int, to: Option[BlockId]): Seq[BalanceSnapshot] = readLock {
to.fold(ngState.flatMap(ng => ng.snapshotOf(ng.bestLiquidBlockId)))(id => ngState.flatMap(_.snapshotOf(id)))
.fold[Blockchain](rocksdb) { case (block, diff, _, _, _, _) =>
SnapshotBlockchain(rocksdb, diff, block, ByteStr.empty, 0L, None, None)
}
.balanceSnapshots(address, from, to)
}
override def accountScript(address: Address): Option[AccountScriptInfo] = readLock {
snapshotBlockchain.accountScript(address)
}
override def hasAccountScript(address: Address): Boolean = readLock {
snapshotBlockchain.hasAccountScript(address)
}
override def assetScript(asset: IssuedAsset): Option[AssetScriptInfo] = readLock {
snapshotBlockchain.assetScript(asset)
}
override def accountData(acc: Address, key: String): Option[DataEntry[?]] = readLock {
snapshotBlockchain.accountData(acc, key)
}
override def hasData(acc: Address): Boolean = readLock {
snapshotBlockchain.hasData(acc)
}
override def transactionMeta(id: ByteStr): Option[TxMeta] = readLock {
snapshotBlockchain.transactionMeta(id)
}
override def transactionSnapshot(id: ByteStr): Option[(StateSnapshot, Status)] = readLock {
snapshotBlockchain.transactionSnapshot(id)
}
override def balance(address: Address, mayBeAssetId: Asset): Long = readLock {
snapshotBlockchain.balance(address, mayBeAssetId)
}
override def balances(req: Seq[(Address, Asset)]): Map[(Address, Asset), TxTimestamp] = readLock {
snapshotBlockchain.balances(req)
}
override def wavesBalances(addresses: Seq[Address]): Map[Address, Long] = readLock {
snapshotBlockchain.wavesBalances(addresses)
}
override def effectiveBalanceBanHeights(address: Address): Seq[Int] = readLock {
snapshotBlockchain.effectiveBalanceBanHeights(address)
}
override def leaseBalance(address: Address): LeaseBalance = readLock {
snapshotBlockchain.leaseBalance(address)
}
override def leaseBalances(addresses: Seq[Address]): Map[Address, LeaseBalance] = readLock {
snapshotBlockchain.leaseBalances(addresses)
}
override def hitSource(height: Int): Option[ByteStr] = readLock {
ngState match {
case Some(ng) if this.height == height => ng.hitSource.some
case _ => rocksdb.hitSource(height)
}
}
override def resolveERC20Address(address: ERC20Address): Option[IssuedAsset] = readLock {
snapshotBlockchain.resolveERC20Address(address)
}
override def lastStateHash(refId: Option[ByteStr]): ByteStr =
ngState
.map { ng =>
refId.filter(ng.contains).fold(ng.bestLiquidComputedStateHash)(id => ng.snapshotFor(id)._4)
}
.getOrElse(rocksdb.lastStateHash(None))
def snapshotBlockchain: SnapshotBlockchain =
ngState.fold[SnapshotBlockchain](SnapshotBlockchain(rocksdb, StateSnapshot.empty))(SnapshotBlockchain(rocksdb, _))
// noinspection ScalaStyle,TypeAnnotation
private[this] object metrics {
val blockMicroForkStats = Kamon.counter("blockchain-updater.block-micro-fork").withoutTags()
val microMicroForkStats = Kamon.counter("blockchain-updater.micro-micro-fork").withoutTags()
val microBlockForkStats = Kamon.counter("blockchain-updater.micro-block-fork").withoutTags()
val microBlockForkHeightStats = Kamon.histogram("blockchain-updater.micro-block-fork-height").withoutTags()
val forgeBlockTimeStats = Kamon.timer("blockchain-updater.forge-block-time").withoutTags()
}
}
object BlockchainUpdaterImpl {
sealed trait BlockApplyResult
object BlockApplyResult {
case object Ignored extends BlockApplyResult
case class Applied(discardedDiffs: Seq[StateSnapshot], score: BigInt) extends BlockApplyResult
}
private def displayFeatures(s: Set[Short]): String =
s"FEATURE${if (s.size > 1) "S" else ""} ${s.mkString(", ")} ${if (s.size > 1) "have been" else "has been"}"
private def areVersionsOfSameBlock(b1: Block, b2: Block): Boolean =
b1.header.generator == b2.header.generator &&
b1.header.baseTarget == b2.header.baseTarget &&
b1.header.reference == b2.header.reference &&
b1.header.timestamp == b2.header.timestamp
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy