Skip to content
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

Fixed InvalidStateHash #3937

Merged
merged 7 commits into from
Jan 13, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish-docker-image.yml
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ on:
baseImage:
description: 'Base image'
required: true
default: 'eclipse-temurin:11-jre-noble'
default: 'eclipse-temurin:21-jre-noble'
type: string

env:
5 changes: 3 additions & 2 deletions node/src/main/scala/com/wavesplatform/mining/Miner.scala
Original file line number Diff line number Diff line change
@@ -166,12 +166,13 @@ class MinerImpl(
}
}

def forgeBlock(account: KeyPair): Either[String, (Block, MiningConstraint)] = {
def forgeBlock(account: KeyPair, referenceOpt: Option[ByteStr] = None): Either[String, (Block, MiningConstraint)] = {
// should take last block right at the time of mining since microblocks might have been added
val height = blockchainUpdater.height
val version = blockchainUpdater.nextBlockVersion
val lastBlockHeader = blockchainUpdater.lastBlockHeader.get.header
val reference = blockchainUpdater.bestLastBlockInfo(System.currentTimeMillis() - minMicroBlockDurationMills).get.blockId
val lastBlockInfo = blockchainUpdater.bestLastBlockInfo(System.currentTimeMillis() - minMicroBlockDurationMills)
val reference = referenceOpt.getOrElse(lastBlockInfo.get.blockId)

metrics.blockBuildTimeStats.measureSuccessful(for {
_ <- checkQuorumAvailable()
Original file line number Diff line number Diff line change
@@ -53,7 +53,7 @@ class MicroBlockMinerImpl(
case Retry =>
Task
.defer(generateMicroBlockSequence(account, accumulatedBlock, restTotalConstraint, lastMicroBlock))
.delayExecution(1 second)
.delayExecution((settings.microBlockInterval / 2).max(1.millis))
case Stop =>
setDebugState(MinerDebugInfo.MiningBlocks)
Task(log.debug("MicroBlock mining completed, block is full"))
Original file line number Diff line number Diff line change
@@ -196,6 +196,33 @@ class BlockchainUpdaterImpl(
.orElse(lastBlockReward)
}

/** Referenced blockchain for mining.
* @return
* SnapshotBlockchain with a reward for a next height
* @note
* Do not use for other purposes
*/
def referencedBlockchain(reference: ByteStr): Blockchain =
ngState
.flatMap { ng =>
if (ng.base.header.reference == reference)
Some(SnapshotBlockchain(rocksdb, ng.reward)) // Same reward for a competitor's block, because same height
else
ng.snapshotOf(reference)
.map { case (forgedBlock, liquidSnapshot, carry, _, stateHash, _) =>
SnapshotBlockchain(
rocksdb,
liquidSnapshot,
forgedBlock,
ng.hitSource,
carry,
computeNextReward,
Some(stateHash)
)
}
}
.getOrElse(SnapshotBlockchain(rocksdb, computeNextReward)) // WARN: This seems not happen

override def processBlock(
block: Block,
hitSource: ByteStr,
@@ -798,15 +825,17 @@ class BlockchainUpdaterImpl(
snapshotBlockchain.resolveERC20Address(address)
}

override def lastStateHash(refId: Option[ByteStr]): ByteStr =
override def lastStateHash(refId: Option[ByteStr]): ByteStr = readLock {
ngState
.map { ng =>
refId.filter(ng.contains).fold(ng.bestLiquidComputedStateHash)(id => ng.snapshotFor(id)._4)
}
.getOrElse(rocksdb.lastStateHash(None))
}

def snapshotBlockchain: SnapshotBlockchain =
def snapshotBlockchain: SnapshotBlockchain = readLock {
ngState.fold[SnapshotBlockchain](SnapshotBlockchain(rocksdb, StateSnapshot.empty))(SnapshotBlockchain(rocksdb, _))
}

// noinspection ScalaStyle,TypeAnnotation
private[this] object metrics {
Original file line number Diff line number Diff line change
@@ -36,8 +36,8 @@ object BlockAppender extends ScorexLogging {
)(newBlock: Block, snapshot: Option[BlockSnapshotResponse]): Task[Either[ValidationError, BlockApplyResult]] =
Task {
if (
blockchainUpdater
.isLastBlockId(newBlock.header.reference) || blockchainUpdater.lastBlockHeader.exists(_.header.reference == newBlock.header.reference)
blockchainUpdater.isLastBlockId(newBlock.header.reference) ||
blockchainUpdater.lastBlockHeader.exists(_.header.reference == newBlock.header.reference)
) {
if (newBlock.header.challengedHeader.isDefined) {
appendChallengeBlock(blockchainUpdater, utxStorage, pos, time, log, verify, txSignParCheck)(newBlock, snapshot)
Original file line number Diff line number Diff line change
@@ -284,19 +284,19 @@ object BlockDiffer {
}

def createInitialBlockSnapshot(
blockchain: BlockchainUpdater & Blockchain,
blockchainUpdater: BlockchainUpdater & Blockchain,
reference: ByteStr,
miner: Address
): Either[ValidationError, StateSnapshot] = {
val fullReward = blockchain.computeNextReward.fold(Portfolio.empty)(Portfolio.waves)
val blockchain = blockchainUpdater.referencedBlockchain(reference)
val feeFromPreviousBlock = Portfolio.waves(blockchain.carryFee(Some(reference)))

val daoAddress = blockchain.settings.functionalitySettings.daoAddressParsed.toOption.flatten
val xtnBuybackAddress = blockchain.settings.functionalitySettings.xtnBuybackAddressParsed.toOption.flatten

val rewardShares = BlockRewardCalculator.getBlockRewardShares(
blockchain.height + 1,
fullReward.balance,
blockchainUpdater.computeNextReward.getOrElse(0),
daoAddress,
xtnBuybackAddress,
blockchain
@@ -497,6 +497,6 @@ object BlockDiffer {
Either.cond(
!blockchain.supportsLightNodeBlockFields() || blockStateHash.contains(computedStateHash),
(),
InvalidStateHash(blockStateHash)
InvalidStateHash(blockStateHash, Some(computedStateHash))
)
}
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import com.wavesplatform.block.Block.BlockId
import com.wavesplatform.block.{Block, BlockSnapshot, MicroBlock, MicroBlockSnapshot}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.state.Blockchain
import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult
import monix.reactive.Observable

@@ -20,6 +21,7 @@ trait BlockchainUpdater {
def removeAfter(blockId: ByteStr): Either[ValidationError, DiscardedBlocks]
def lastBlockInfo: Observable[LastBlockInfo]
def isLastBlockId(id: ByteStr): Boolean
def referencedBlockchain(reference: ByteStr): Blockchain
def shutdown(): Unit
}

Original file line number Diff line number Diff line change
@@ -49,7 +49,7 @@ object TxValidationError {
override def toString: String = s"InvalidSignature(${entity.toString + " reason: " + details})"
}

case class InvalidStateHash(blockStateHash: Option[ByteStr]) extends ValidationError
case class InvalidStateHash(blockStateHash: Option[ByteStr], computedStateHash: Option[ByteStr]) extends ValidationError

sealed trait WithLog extends Product with Serializable {
def log: Log[Id]
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ import com.wavesplatform.block.{Block, BlockHeader, SignedBlockHeader}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.crypto.DigestLength
import com.wavesplatform.db.WithDomain
import com.wavesplatform.features.BlockchainFeatures
import com.wavesplatform.history.{chainBaseAndMicro, defaultSigner}
import com.wavesplatform.lagonaki.mocks.TestBlock
@@ -28,7 +27,8 @@ import org.scalatest.enablers.Length

import java.util.concurrent.atomic.AtomicReference
import scala.concurrent.duration.*
class BlockV5Test extends FlatSpec with WithDomain with OptionValues with EitherValues with BlocksTransactionsHelpers {

class BlockV5Test extends FlatSpec with WithMiner with OptionValues with EitherValues with BlocksTransactionsHelpers {
import BlockV5Test.*

private val testTime = TestTime(1)
Loading
Loading