All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavesplatform.block.Block.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.block

import com.wavesplatform.account.{Address, KeyPair, PublicKey}
import com.wavesplatform.block.serialization.BlockSerializer
import com.wavesplatform.common.merkle.Merkle.{hash, mkProofs, verify}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.crypto
import com.wavesplatform.crypto.*
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.protobuf.block.PBBlocks
import com.wavesplatform.protobuf.transaction.PBTransactions
import com.wavesplatform.settings.GenesisSettings
import com.wavesplatform.state.*
import com.wavesplatform.transaction.*
import com.wavesplatform.transaction.TxValidationError.GenericError
import monix.eval.Coeval
import play.api.libs.json.*

import scala.util.Try

case class BlockHeader(
    version: Byte,
    timestamp: Long,
    reference: ByteStr,
    baseTarget: Long,
    generationSignature: ByteStr,
    generator: PublicKey,
    featureVotes: Seq[Short],
    rewardVote: Long,
    transactionsRoot: ByteStr,
    stateHash: Option[ByteStr],
    challengedHeader: Option[ChallengedHeader]
) {
  val score: Coeval[BigInt] = Coeval.evalOnce((BigInt("18446744073709551616") / baseTarget).ensuring(_ > 0))
}

case class ChallengedHeader(
    timestamp: Long,
    baseTarget: Long,
    generationSignature: ByteStr,
    featureVotes: Seq[Short],
    generator: PublicKey,
    rewardVote: Long,
    stateHash: Option[ByteStr],
    headerSignature: ByteStr
)

case class Block(
    header: BlockHeader,
    signature: ByteStr,
    transactionData: Seq[Transaction]
) {
  import Block.*

  val id: Coeval[ByteStr] = Coeval.evalOnce(Block.idFromHeader(header, signature))

  val sender: PublicKey = header.generator

  val bytes: Coeval[Array[Byte]] = Coeval.evalOnce(BlockSerializer.toBytes(this))
  val json: Coeval[JsObject]     = Coeval.evalOnce(BlockSerializer.toJson(this))

  val blockScore: Coeval[BigInt] = header.score

  val bodyBytes: Coeval[Array[Byte]] = Coeval.evalOnce {
    if (header.version < Block.ProtoBlockVersion) copy(signature = ByteStr.empty).bytes()
    else PBBlocks.protobuf(this).header.get.toByteArray
  }

  protected val signedDescendants: Coeval[Seq[Signed]] = Coeval.evalOnce(transactionData.flatMap(_.cast[Signed]))

  private[block] val transactionsMerkleTree: Coeval[TransactionsMerkleTree] = Coeval.evalOnce(mkMerkleTree(transactionData))

  private[block] val originalHeader: Coeval[BlockHeader] =
    Coeval.evalOnce(
      header.challengedHeader
        .map { ch =>
          header.copy(
            baseTarget = ch.baseTarget,
            timestamp = ch.timestamp,
            generationSignature = ch.generationSignature,
            generator = ch.generator,
            featureVotes = ch.featureVotes,
            rewardVote = ch.rewardVote,
            stateHash = ch.stateHash,
            challengedHeader = None
          )
        }
        .getOrElse(header)
    )

  val signatureValid: Coeval[Boolean] = Coeval.evalOnce {
    crypto.verify(signature, bodyBytes(), header.generator, checkWeakPk = true) &&
    (header.version < Block.ProtoBlockVersion || transactionsMerkleTree().transactionsRoot == header.transactionsRoot) &&
    header.challengedHeader.forall { ch =>
      crypto.verify(
        ch.headerSignature,
        PBBlocks.protobuf(originalHeader()).toByteArray,
        ch.generator,
        checkWeakPk = true
      )
    }
  }

  def toOriginal: Block =
    header.challengedHeader match {
      case Some(ch) => copy(header = originalHeader(), signature = ch.headerSignature)
      case _        => this
    }

  override def toString: String =
    s"Block(${id()},${header.reference},${header.generator.toAddress}," +
      s"${header.timestamp},${header.featureVotes.mkString("[", ",", "]")}${if (header.rewardVote >= 0) s",${header.rewardVote}" else ""})"
}

object Block {
  def idFromHeader(h: BlockHeader, signature: ByteStr): ByteStr =
    if (h.version >= ProtoBlockVersion) protoHeaderHash(h)
    else signature

  def protoHeaderHash(h: BlockHeader): ByteStr = {
    require(h.version >= ProtoBlockVersion)
    ByteStr(crypto.fastHash(PBBlocks.protobuf(h).toByteArray))
  }

  def referenceLength(version: Byte): Int =
    if (version >= ProtoBlockVersion) DigestLength
    else SignatureLength

  def validateReferenceLength(length: Int): Boolean =
    length == DigestLength || length == SignatureLength

  def create(
      version: Byte,
      timestamp: Long,
      reference: ByteStr,
      baseTarget: Long,
      generationSignature: ByteStr,
      generator: PublicKey,
      featureVotes: Seq[Short],
      rewardVote: Long,
      transactionData: Seq[Transaction],
      stateHash: Option[ByteStr],
      challengedHeader: Option[ChallengedHeader]
  ): Block = {
    val transactionsRoot = mkTransactionsRoot(version, transactionData)
    Block(
      BlockHeader(
        version,
        timestamp,
        reference,
        baseTarget,
        generationSignature,
        generator,
        featureVotes,
        rewardVote,
        transactionsRoot,
        stateHash,
        challengedHeader
      ),
      ByteStr.empty,
      transactionData
    )
  }

  def create(base: Block, transactionData: Seq[Transaction], signature: ByteStr, stateHash: Option[ByteStr]): Block =
    base.copy(
      signature = signature,
      transactionData = transactionData,
      header = base.header.copy(transactionsRoot = mkTransactionsRoot(base.header.version, transactionData), stateHash = stateHash)
    )

  def buildAndSign(
      version: Byte,
      timestamp: Long,
      reference: ByteStr,
      baseTarget: Long,
      generationSignature: ByteStr,
      txs: Seq[Transaction],
      signer: KeyPair,
      featureVotes: Seq[Short],
      rewardVote: Long,
      stateHash: Option[ByteStr],
      challengedHeader: Option[ChallengedHeader]
  ): Either[GenericError, Block] =
    create(
      version,
      timestamp,
      reference,
      baseTarget,
      generationSignature,
      signer.publicKey,
      featureVotes,
      rewardVote,
      txs,
      stateHash,
      challengedHeader
    ).validate
      .map(_.sign(signer.privateKey))

  def parseBytes(bytes: Array[Byte]): Try[Block] =
    BlockSerializer
      .parseBytes(bytes)
      .flatMap(_.validateToTry)

  def genesis(genesisSettings: GenesisSettings, rideV6Activated: Boolean, txStateSnapshotActivated: Boolean): Either[ValidationError, Block] = {
    import cats.instances.either.*
    import cats.instances.list.*
    import cats.syntax.traverse.*

    for {
      txs <- genesisSettings.transactions.toList.map { gts =>
        for {
          address <- Address.fromString(gts.recipient)
          tx      <- GenesisTransaction.create(address, gts.amount, genesisSettings.timestamp)
        } yield tx
      }.sequence
      baseTarget = genesisSettings.initialBaseTarget
      timestamp  = genesisSettings.blockTimestamp
      block = create(
        GenesisBlockVersion,
        timestamp,
        GenesisReference,
        baseTarget,
        GenesisGenerationSignature,
        GenesisGenerator.publicKey,
        Seq(),
        -1L,
        txs,
        None,
        None
      )
      signedBlock = genesisSettings.signature match {
        case None             => block.sign(GenesisGenerator.privateKey)
        case Some(predefined) => block.copy(signature = predefined)
      }
      signedBlockWithStateHash = signedBlock.copy(header =
        signedBlock.header.copy(stateHash = Option.when(txStateSnapshotActivated)(TxStateSnapshotHashBuilder.createGenesisStateHash(txs)))
      )
      validBlock <- signedBlockWithStateHash.validateGenesis(genesisSettings, rideV6Activated)
    } yield validBlock
  }

  type BlockId                = ByteStr
  type TransactionsMerkleTree = Seq[Seq[Array[Byte]]]
  case class TransactionProof(id: ByteStr, transactionIndex: Int, digests: Seq[Array[Byte]])

  val MaxTransactionsPerBlockVer1Ver2: Int = 100
  val MaxTransactionsPerBlockVer3: Int     = 6000
  val MaxFeaturesInBlock: Int              = 64
  val BaseTargetLength: Int                = 8
  val GenerationSignatureLength: Int       = 32
  val GenerationVRFSignatureLength: Int    = 96
  val BlockIdLength: Int                   = SignatureLength
  val TransactionSizeLength                = 4
  val HitSourceLength                      = 32

  val GenesisReference: BlockId           = ByteStr(Array.fill(SignatureLength)(-1: Byte))
  val GenesisGenerator: KeyPair           = KeyPair(ByteStr.empty)
  val GenesisGenerationSignature: BlockId = ByteStr(new Array[Byte](crypto.DigestLength))

  val GenesisBlockVersion: Byte = 1
  val PlainBlockVersion: Byte   = 2
  val NgBlockVersion: Byte      = 3
  val RewardBlockVersion: Byte  = 4
  val ProtoBlockVersion: Byte   = 5

  // Merkle
  implicit class BlockTransactionsRootOps(private val block: Block) extends AnyVal {
    def transactionProof(transaction: Transaction): Option[TransactionProof] =
      block.transactionData.indexWhere(transaction.id() == _.id()) match {
        case -1  => None
        case idx => Some(TransactionProof(transaction.id(), idx, mkProofs(idx, block.transactionsMerkleTree()).reverse))
      }

    def verifyTransactionProof(transactionProof: TransactionProof): Boolean =
      block.transactionData
        .lift(transactionProof.transactionIndex)
        .filter(tx => tx.id() == transactionProof.id)
        .exists(tx =>
          verify(
            hash(PBTransactions.protobuf(tx).toByteArray),
            transactionProof.transactionIndex,
            transactionProof.digests.reverse,
            block.header.transactionsRoot.arr
          )
        )
  }
}

case class SignedBlockHeader(header: BlockHeader, signature: ByteStr) {
  val id: Coeval[ByteStr] = Coeval.evalOnce(Block.idFromHeader(header, signature))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy