Skip to content

Commit

Permalink
Attestations implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
nzpr committed Aug 12, 2022
1 parent 4486a06 commit 4a14c5e
Showing 4 changed files with 192 additions and 216 deletions.
Original file line number Diff line number Diff line change
@@ -1,228 +1,109 @@
package coop.rchain.casper.blocks.proposer

import cats.effect.Concurrent
import cats.effect.{Concurrent, Sync}
import cats.syntax.all._
import coop.rchain.models.syntax._
import com.google.protobuf.ByteString
import coop.rchain.blockstorage.BlockStore.BlockStore
import coop.rchain.blockstorage.dag.BlockDagStorage
import coop.rchain.blockstorage.syntax._
import coop.rchain.casper.merging.ParentsMergedState
import coop.rchain.casper.protocol._
import coop.rchain.blockstorage.dag.BlockDagStorage.DeployId
import coop.rchain.casper.protocol.{ProcessedDeploy, ProcessedSystemDeploy, RholangState}
import coop.rchain.casper.rholang.RuntimeManager.StateHash
import coop.rchain.casper.rholang.sysdeploys.{CloseBlockDeploy, SlashDeploy}
import coop.rchain.casper.rholang.{BlockRandomSeed, InterpreterUtil, RuntimeManager}
import coop.rchain.casper.util.{ConstructDeploy, ProtoUtil}
import coop.rchain.casper.{MultiParentCasper, PrettyPrinter, ValidatorIdentity}
import coop.rchain.crypto.PrivateKey
import coop.rchain.crypto.hash.Blake2b512Random
import coop.rchain.crypto.signatures.Signed
import coop.rchain.crypto.PublicKey
import coop.rchain.casper.util.ProtoUtil
import coop.rchain.casper.{PrettyPrinter, ValidatorIdentity}
import coop.rchain.metrics.{Metrics, Span}
import coop.rchain.models.BlockHash.BlockHash
import coop.rchain.models.BlockVersion
import coop.rchain.models.Validator.Validator
import coop.rchain.models.syntax._
import coop.rchain.rholang.interpreter.SystemProcesses.BlockData
import coop.rchain.rspace.hashing.Blake2b256Hash
import coop.rchain.shared.{Log, Stopwatch, Time}

object BlockCreator {
private[this] val ProcessDeploysAndCreateBlockMetricsSource =
Metrics.Source(Metrics.BaseSource, "create-block")
import coop.rchain.shared.Log

/*
* Overview of createBlock
*
* 1. Rank each of the block cs's latest messages (blocks) via the LMD GHOST estimator.
* 2. Let each latest message have a score of 2^(-i) where i is the index of that latest message in the ranking.
* Take a subset S of the latest messages such that the sum of scores is the greatest and
* none of the blocks in S conflicts with each other. S will become the parents of the
* about-to-be-created block.
* 3. Extract all valid deploys that aren't already in all ancestors of S (the parents).
* 4. Create a new block that contains the deploys from the previous step.
*/
def create[F[_]: Concurrent: Time: RuntimeManager: BlockDagStorage: BlockStore: Log: Metrics: Span](
preState: ParentsMergedState,
validatorIdentity: ValidatorIdentity,
shardId: String,
dummyDeployOpt: Option[(PrivateKey, String)] = None
): F[BlockCreatorResult] =
Span[F].trace(ProcessDeploysAndCreateBlockMetricsSource) {
val selfId = ByteString.copyFrom(validatorIdentity.publicKey.bytes)
val nextSeqNum = preState.maxSeqNums.get(selfId).map(_ + 1L).getOrElse(0L)
val nextBlockNum = preState.maxBlockNum + 1
val justifications = preState.justifications.map(_.blockHash).toList
final case class BlockCreator(id: ValidatorIdentity, shardId: String) {
type StateTransitionResult = (StateHash, Seq[ProcessedDeploy], Seq[ProcessedSystemDeploy])

def prepareUserDeploys(blockNumber: Long): F[Set[Signed[DeployData]]] =
for {
unfinalized <- BlockDagStorage[F].pooledDeploys.map(_.values.toSet)
earliestBlockNumber = blockNumber - MultiParentCasper.deployLifespan
valid = unfinalized.filter { d =>
notFutureDeploy(blockNumber, d.data) &&
notExpiredDeploy(earliestBlockNumber, d.data)
}
// this is required to prevent resending the same deploy several times by validator
// validUnique = valid -- s.deploysInScope
// TODO: temp solution to filter duplicated deploys
validUnique <- valid.toList.filterA { d =>
BlockDagStorage[F].lookupByDeployId(d.sig).map(_.isEmpty)
}
} yield validUnique.toSet
def create[F[_]: Concurrent: RuntimeManager: BlockDagStorage: BlockStore: Log: Metrics: Span](
preStateHash: Blake2b256Hash,
parents: Set[BlockHash],
bondsMap: Map[Validator, Long],
finalization: Set[DeployId], // deploys that are rejected on finalization done by the block being created
blockNum: Long,
seqNum: Long,
deploys: Seq[DeployId],
toSlash: Set[Validator],
changeEpoch: Boolean,
suppressAttestation: Boolean
): F[BlockCreatorResult] = {
val creatorsPk = id.publicKey
val blockData = BlockData(blockNum, creatorsPk, seqNum)
val shouldPropose = deploys.nonEmpty || toSlash.nonEmpty || changeEpoch

def prepareSlashingDeploys(
ilmFromBonded: Seq[(Validator, BlockHash)],
rand: Blake2b512Random,
startIndex: Int
): F[List[SlashDeploy]] = {
val slashingDeploysWithBlocks = ilmFromBonded.zipWithIndex.map {
case ((slashedValidator, invalidBlock), i) =>
(SlashDeploy(slashedValidator, rand.splitByte((i + startIndex).toByte)), invalidBlock)
}
slashingDeploysWithBlocks.toList.traverse {
case (sd, invalidBlock) =>
Log[F]
.info(
s"Issuing slashing deploy justified by block ${PrettyPrinter.buildString(invalidBlock)}"
)
.as(sd)
}
}
def propose: F[StateTransitionResult] = {
val rand = BlockRandomSeed.randomGenerator(shardId, blockNum, creatorsPk, preStateHash)
// seeds from 0 to deploys.size are used in deploys execution, so system deploy seeds start from the next index
val slashSeeds =
(0 until toSlash.size).map(_ + deploys.size).map(i => rand.splitByte(i.toByte))
val closeSeed = rand.splitByte((deploys.size + toSlash.size).toByte)

def prepareDummyDeploy(blockNumber: Long, shardId: String): Seq[Signed[DeployData]] =
dummyDeployOpt match {
case Some((privateKey, term)) =>
Seq(
ConstructDeploy.sourceDeployNow(
source = term,
sec = privateKey,
vabn = blockNumber - 1,
shardId = shardId
)
)
case None => Seq.empty[Signed[DeployData]]
}
val slashDeploys = toSlash.toList.sorted.zip(slashSeeds).map(SlashDeploy.tupled)
val closeDeploy = CloseBlockDeploy(closeSeed)

val createBlockProcess = for {
_ <- Log[F].info(
s"Creating block #${nextBlockNum} (seqNum ${nextSeqNum})"
)
userDeploys <- prepareUserDeploys(nextBlockNum)
dummyDeploys = prepareDummyDeploy(nextBlockNum, shardId)
// TODO: fix invalid blocks from non-finalized scope
ilm <- Seq[(Validator, BlockHash)]().pure[F]
ilmFromBonded = ilm.filter {
case (validator, _) => preState.fringeBondsMap.getOrElse(validator, 0L) > 0L
}
deploys = userDeploys ++ dummyDeploys
r <- if (deploys.nonEmpty || ilmFromBonded.nonEmpty) {
val blockData = BlockData(nextBlockNum, validatorIdentity.publicKey, nextSeqNum)
val rand = BlockRandomSeed.randomGenerator(
shardId,
nextBlockNum,
validatorIdentity.publicKey,
preState.preStateHash
)
for {
slashingDeploys <- prepareSlashingDeploys(ilmFromBonded, rand, deploys.size)
// make sure closeBlock is the last system Deploy
systemDeploys = slashingDeploys :+ CloseBlockDeploy(
rand.splitByte((deploys.size + slashingDeploys.size).toByte)
)
preStateHash = preState.preStateHash.toByteString
checkpointData <- InterpreterUtil.computeDeploysCheckpoint(
deploys.toSeq,
systemDeploys,
rand,
blockData,
preStateHash
)
(postStateHash, processedDeploys, processedSystemDeploys) = checkpointData
BlockDagStorage[F].pooledDeploys
.map(_.filterKeys(deploys.toSet).values.toSeq)
.flatMap(
InterpreterUtil.computeDeploysCheckpoint[F](
_,
slashDeploys :+ closeDeploy,
rand,
blockData,
preStateHash.toByteString
)
)
}

_ <- Span[F].mark("before-packing-block")
/** Create attestation. */
def attest: F[StateTransitionResult] = Sync[F].delay {
val postStateHash = preStateHash.toByteString
val processedDeploys = Seq()
val processedSystemDeploys = Seq()
(postStateHash, processedDeploys, processedSystemDeploys)
}

// Create block and calculate block hash
unsignedBlock = packageBlock(
validatorIdentity.publicKey,
blockData,
justifications,
preStateHash,
postStateHash,
processedDeploys,
processedSystemDeploys,
shardId,
BlockVersion.Current,
// Bonds data in the block is referring to finalized fringe
// not bonds in conflict scope
preState.fringeBondsMap,
// Rejected data in the block is referring to finalized fringe
// not rejections in conflict scope
preState.fringeRejectedDeploys
)
_ <- Span[F].mark("block-created")
val postState =
if (shouldPropose) propose.map(_.some)
else (!suppressAttestation).guard[Option].traverse(_ => attest)

// Sign a block (hash should not be changed)
signedBlock = validatorIdentity.signBlock(unsignedBlock)
_ <- Span[F].mark("block-signed")
postState.map {
case None => BlockCreatorResult.noNewDeploys
case Some((postStateHash, processedDeploys, processedSystemDeploys)) =>
// Create block and calculate block hash
val state = RholangState(processedDeploys.toList, processedSystemDeploys.toList)
val unsignedBlock = ProtoUtil.unsignedBlockProto(
version = BlockVersion.Current,
shardId,
blockData.blockNumber,
creatorsPk,
blockData.seqNum,
preStateHash.toByteString,
postStateHash,
parents.toList,
bondsMap,
finalization,
state
)

// This check is temporary until signing function will re-hash the block
unsignedHash = PrettyPrinter.buildString(unsignedBlock.blockHash)
signedHash = PrettyPrinter.buildString(signedBlock.blockHash)
_ = assert(
unsignedBlock.blockHash == signedBlock.blockHash,
s"Signed block has different block hash unsigned: $unsignedHash, signed: $signedHash."
)
} yield BlockCreatorResult.created(signedBlock)
} else
BlockCreatorResult.noNewDeploys.pure[F]
} yield r
// Sign a block (hash should not be changed)
val signedBlock = id.signBlock(unsignedBlock)

for {
// Create block and measure duration
r <- Stopwatch.duration(createBlockProcess)
(blockStatus, elapsed) = r
_ <- blockStatus match {
case Created(block) =>
val blockInfo = PrettyPrinter.buildString(block, short = true)
val deployCount = block.state.deploys.size
Log[F].info(s"Block created: $blockInfo (${deployCount}d) [$elapsed]")
case _ => ().pure[F]
}
} yield blockStatus
// This check is temporary until signing function will re-hash the block
val unsignedHash = PrettyPrinter.buildString(unsignedBlock.blockHash)
val signedHash = PrettyPrinter.buildString(signedBlock.blockHash)
assert(
unsignedBlock.blockHash == signedBlock.blockHash,
s"Signed block has different block hash unsigned: $unsignedHash, signed: $signedHash."
)
BlockCreatorResult.created(signedBlock)
}

private def packageBlock(
sender: PublicKey,
blockData: BlockData,
justifications: List[BlockHash],
preStateHash: StateHash,
postStateHash: StateHash,
deploys: Seq[ProcessedDeploy],
systemDeploys: Seq[ProcessedSystemDeploy],
shardId: String,
version: Int,
// Fringe data
bondsMap: Map[Validator, Long],
rejectedDeploys: Set[ByteString]
): BlockMessage = {
val state = RholangState(deploys.toList, systemDeploys.toList)
ProtoUtil.unsignedBlockProto(
version,
shardId,
blockData.blockNumber,
sender,
blockData.seqNum,
preStateHash,
postStateHash,
justifications,
bondsMap,
rejectedDeploys,
state
)
}

private def notExpiredDeploy(earliestBlockNumber: Long, d: DeployData): Boolean =
d.validAfterBlockNumber > earliestBlockNumber

private def notFutureDeploy(currentBlockNumber: Long, d: DeployData): Boolean =
d.validAfterBlockNumber < currentBlockNumber
}
Loading

0 comments on commit 4a14c5e

Please sign in to comment.