com.wavesplatform.mining.microblocks.MicroBlockMinerImpl.scala Maven / Gradle / Ivy
The newest version!
package com.wavesplatform.mining.microblocks
import cats.syntax.applicativeError.*
import cats.syntax.bifunctor.*
import cats.syntax.either.*
import com.wavesplatform.account.KeyPair
import com.wavesplatform.block.Block.BlockId
import com.wavesplatform.block.{Block, MicroBlock}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.metrics.*
import com.wavesplatform.mining.*
import com.wavesplatform.mining.microblocks.MicroBlockMinerImpl.*
import com.wavesplatform.network.{MicroBlockInv, *}
import com.wavesplatform.settings.MinerSettings
import com.wavesplatform.state.Blockchain
import com.wavesplatform.state.appender.MicroblockAppender
import com.wavesplatform.transaction.{BlockchainUpdater, Transaction}
import com.wavesplatform.utils.ScorexLogging
import com.wavesplatform.utx.UtxPool
import com.wavesplatform.utx.UtxPool.PackStrategy
import io.netty.channel.group.ChannelGroup
import kamon.Kamon
import monix.eval.Task
import monix.execution.schedulers.SchedulerService
import monix.reactive.Observable
import scala.concurrent.duration.*
class MicroBlockMinerImpl(
setDebugState: MinerDebugInfo.State => Unit,
allChannels: ChannelGroup,
blockchainUpdater: BlockchainUpdater & Blockchain,
utx: UtxPool,
settings: MinerSettings,
minerScheduler: SchedulerService,
appenderScheduler: SchedulerService,
transactionAdded: Observable[Unit],
nextMicroBlockSize: Int => Int
) extends MicroBlockMiner
with ScorexLogging {
private val microBlockBuildTimeStats = Kamon.timer("miner.forge-microblock-time").withoutTags()
def generateMicroBlockSequence(
account: KeyPair,
accumulatedBlock: Block,
restTotalConstraint: MiningConstraint,
lastMicroBlock: Long
): Task[Unit] =
generateOneMicroBlockTask(account, accumulatedBlock, restTotalConstraint, lastMicroBlock)
.flatMap {
case res @ Success(newBlock, newConstraint) =>
Task.defer(generateMicroBlockSequence(account, newBlock, newConstraint, res.nanoTime))
case Retry =>
Task
.defer(generateMicroBlockSequence(account, accumulatedBlock, restTotalConstraint, lastMicroBlock))
.delayExecution(1 second)
case Stop =>
setDebugState(MinerDebugInfo.MiningBlocks)
Task(log.debug("MicroBlock mining completed, block is full"))
}
.recover { case e => log.error("Error mining microblock", e) }
private[mining] def generateOneMicroBlockTask(
account: KeyPair,
accumulatedBlock: Block,
restTotalConstraint: MiningConstraint,
lastMicroBlock: Long
): Task[MicroBlockMiningResult] = {
val packTask = Task.cancelable[(Option[Seq[Transaction]], MiningConstraint, Option[ByteStr])] { cb =>
@volatile var cancelled = false
minerScheduler.execute { () =>
val mdConstraint = MultiDimensionalMiningConstraint(
restTotalConstraint,
OneDimensionalMiningConstraint(
nextMicroBlockSize(settings.maxTransactionsInMicroBlock),
TxEstimators.one,
"MaxTxsInMicroBlock"
)
)
val packStrategy =
if (accumulatedBlock.transactionData.isEmpty) PackStrategy.Limit(settings.microBlockInterval)
else PackStrategy.Estimate(settings.microBlockInterval)
log.trace(s"Starting pack for ${accumulatedBlock.id()} with $packStrategy, initial constraint is $mdConstraint")
val (unconfirmed, updatedMdConstraint, stateHash) =
concurrent.blocking(
Instrumented.logMeasure(log, "packing unconfirmed transactions for microblock")(
utx.packUnconfirmed(
mdConstraint,
accumulatedBlock.header.stateHash,
packStrategy,
() => cancelled
)
)
)
log.trace(s"Finished pack for ${accumulatedBlock.id()}")
val updatedTotalConstraint = updatedMdConstraint.head
cb.onSuccess((unconfirmed, updatedTotalConstraint, stateHash))
}
Task.eval {
cancelled = true
}
}
packTask.flatMap {
case (Some(unconfirmed), updatedTotalConstraint, stateHash) if unconfirmed.nonEmpty =>
val delay = {
val delay = System.nanoTime() - lastMicroBlock
val requiredDelay = settings.microBlockInterval.toNanos
if (delay >= requiredDelay) Duration.Zero else (requiredDelay - delay).nanos
}
for {
_ <- Task.now(if (delay > Duration.Zero) log.trace(s"Sleeping ${delay.toMillis} ms before applying microBlock"))
_ <- Task.sleep(delay)
_ = log.trace(s"Generating microBlock for ${account.toAddress}, constraints: $updatedTotalConstraint")
blocks <- forgeBlocks(account, accumulatedBlock, unconfirmed, stateHash)
.leftWiden[Throwable]
.liftTo[Task]
(signedBlock, microBlock) = blocks
blockId <- appendMicroBlock(microBlock)
_ = BlockStats.mined(microBlock, blockId)
_ <- broadcastMicroBlock(account, microBlock, blockId)
} yield {
if (updatedTotalConstraint.isFull) Stop
else Success(signedBlock, updatedTotalConstraint)
}
case (_, updatedTotalConstraint, _) =>
if (updatedTotalConstraint.isFull) {
log.trace(s"Stopping forging microBlocks, the block is full: $updatedTotalConstraint")
Task.now(Stop)
} else {
log.trace("UTX is empty, waiting for new transactions")
Task
.race(
transactionAdded.headL.map(_ => Retry),
if (utx.size > 0) Task.now(Retry) else Task.never
)
.map(_.merge)
}
}
}
private def broadcastMicroBlock(account: KeyPair, microBlock: MicroBlock, blockId: BlockId): Task[Unit] =
Task(if (allChannels != null) allChannels.broadcast(MicroBlockInv(account, blockId, microBlock.reference)))
private def appendMicroBlock(microBlock: MicroBlock): Task[BlockId] =
MicroblockAppender(blockchainUpdater, utx, appenderScheduler)(microBlock, None)
.flatMap {
case Left(err) => Task.raiseError(MicroBlockAppendError(microBlock, err))
case Right(v) => Task.now(v)
}
private def forgeBlocks(
account: KeyPair,
accumulatedBlock: Block,
unconfirmed: Seq[Transaction],
stateHash: Option[ByteStr]
): Either[MicroBlockMiningError, (Block, MicroBlock)] =
microBlockBuildTimeStats.measureSuccessful {
for {
signedBlock <- Block
.buildAndSign(
version = blockchainUpdater.currentBlockVersion,
timestamp = accumulatedBlock.header.timestamp,
reference = accumulatedBlock.header.reference,
baseTarget = accumulatedBlock.header.baseTarget,
generationSignature = accumulatedBlock.header.generationSignature,
txs = accumulatedBlock.transactionData ++ unconfirmed,
signer = account,
featureVotes = accumulatedBlock.header.featureVotes,
rewardVote = accumulatedBlock.header.rewardVote,
stateHash = if (blockchainUpdater.supportsLightNodeBlockFields()) stateHash else None,
challengedHeader = None
)
.leftMap(BlockBuildError.apply)
microBlock <- MicroBlock
.buildAndSign(signedBlock.header.version, account, unconfirmed, accumulatedBlock.id(), signedBlock.signature, stateHash)
.leftMap(MicroBlockBuildError.apply)
} yield (signedBlock, microBlock)
}
}
object MicroBlockMinerImpl {
sealed trait MicroBlockMiningResult
case object Stop extends MicroBlockMiningResult
case object Retry extends MicroBlockMiningResult
final case class Success(b: Block, totalConstraint: MiningConstraint) extends MicroBlockMiningResult {
val nanoTime: Long = System.nanoTime()
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy