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

com.wavesplatform.block.serialization.BlockSerializer.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.block.serialization

import java.nio.ByteBuffer
import com.google.common.io.ByteStreams.newDataOutput
import com.google.common.primitives.{Bytes, Ints, Longs, Shorts}
import com.wavesplatform.account.PublicKey
import com.wavesplatform.block.Block.{NgBlockVersion, ProtoBlockVersion, RewardBlockVersion}
import com.wavesplatform.block.{Block, BlockHeader}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.crypto.SignatureLength
import com.wavesplatform.protobuf.block.PBBlocks
import com.wavesplatform.protobuf.utils.PBUtils
import com.wavesplatform.serialization.ByteBufferOps
import com.wavesplatform.transaction.Asset.Waves
import com.wavesplatform.transaction.Transaction
import play.api.libs.json.{JsArray, JsNumber, JsObject, Json}

import scala.util.Try

object BlockHeaderSerializer {
  def toBytes(header: BlockHeader): Array[Byte] =
    if (header.version >= Block.ProtoBlockVersion) {
      PBUtils.encodeDeterministic(PBBlocks.protobuf(header))
    } else {
      val ndo = newDataOutput()

      ndo.writeByte(header.version)
      ndo.write(header.reference.arr)
      ndo.writeLong(header.baseTarget)
      ndo.write(header.generationSignature.arr)
      ndo.writeLong(header.timestamp)
      ndo.write(header.generator.arr)

      ndo.writeInt(header.featureVotes.size)
      header.featureVotes.foreach(s => ndo.writeShort(s))

      if (header.version > Block.NgBlockVersion)
        ndo.writeLong(header.rewardVote)

      ndo.toByteArray
    }

  def toJson(blockHeader: BlockHeader): JsObject = {
    def createFeaturesJson(featureVotes: Seq[Short]): JsObject =
      if (blockHeader.version < NgBlockVersion) JsObject.empty
      else Json.obj("features" -> JsArray(featureVotes.map(id => JsNumber(id.toInt))))

    def createGeneratorJson(generator: PublicKey): JsObject =
      Json.obj("generator" -> generator.toAddress.toString, "generatorPublicKey" -> generator)

    def createRewardVoteJson(rewardVote: Long): JsObject =
      if (blockHeader.version < RewardBlockVersion) JsObject.empty
      else Json.obj("desiredReward" -> JsNumber(rewardVote))

    def createStateHashJson(stateHash: Option[ByteStr]): JsObject =
      stateHash match {
        case Some(sh) => Json.obj("stateHash" -> sh.toString)
        case None     => JsObject.empty
      }

    val consensusJson =
      Json.obj(
        "nxt-consensus" -> Json.obj(
          "base-target"          -> blockHeader.baseTarget,
          "generation-signature" -> blockHeader.generationSignature.toString
        )
      ) ++ (if (blockHeader.version >= ProtoBlockVersion)
              Json.obj("transactionsRoot" -> blockHeader.transactionsRoot.toString, "id" -> Block.protoHeaderHash(blockHeader).toString)
            else Json.obj())

    val featuresJson  = createFeaturesJson(blockHeader.featureVotes)
    val rewardJson    = createRewardVoteJson(blockHeader.rewardVote)
    val generatorJson = createGeneratorJson(blockHeader.generator)
    val stateHashJson = createStateHashJson(blockHeader.stateHash)

    val challengedHeaderJson =
      blockHeader.challengedHeader match {
        case Some(ch) =>
          Json.obj(
            "challengedHeader" -> (Json.obj(
              "headerSignature" -> ch.headerSignature.toString
            ) ++ createFeaturesJson(ch.featureVotes) ++ createGeneratorJson(ch.generator) ++ createRewardVoteJson(
              ch.rewardVote
            ) ++ createStateHashJson(ch.stateHash))
          )
        case None => JsObject.empty
      }

    Json.obj(
      "version"   -> blockHeader.version,
      "timestamp" -> blockHeader.timestamp,
      "reference" -> blockHeader.reference.toString
    ) ++ consensusJson ++ featuresJson ++ rewardJson ++ generatorJson ++ stateHashJson ++ challengedHeaderJson
  }

  def toJson(header: BlockHeader, blockSize: Int, transactionCount: Int, signature: ByteStr): JsObject =
    toJson(header) ++ Json.obj(
      "signature"        -> signature.toString,
      "blocksize"        -> blockSize,
      "transactionCount" -> transactionCount
    ) ++ (if (header.version < ProtoBlockVersion) Json.obj("id" -> signature.toString) else Json.obj())
}

object BlockSerializer {
  def toBytes(block: Block): Array[Byte] =
    if (block.header.version >= ProtoBlockVersion)
      PBUtils.encodeDeterministic(PBBlocks.protobuf(block))
    else
      Bytes.concat(
        mkPrefixBytes(block.header),
        mkTxsDataBytes(block.header, block.transactionData),
        mkSuffixBytes(block.header, block.signature)
      )

  def mkPrefixBytes(header: BlockHeader): Array[Byte] = {
    val consensusBytes = writeConsensusBytes(header.baseTarget, header.generationSignature)
    Bytes.concat(
      Array(header.version),
      Longs.toByteArray(header.timestamp),
      header.reference.arr,
      Ints.toByteArray(consensusBytes.length),
      consensusBytes
    )
  }

  def mkTxsDataBytes(header: BlockHeader, transactions: Seq[Transaction]): Array[Byte] = {
    val transactionsDataBytes = writeTransactionData(header.version, transactions)
    Bytes.concat(
      Ints.toByteArray(transactionsDataBytes.length),
      transactionsDataBytes
    )
  }

  def mkSuffixBytes(header: BlockHeader, signature: ByteStr): Array[Byte] = {
    val featureVotesBytes = header.version match {
      case v if v < NgBlockVersion => Array.empty[Byte]
      case _ =>
        val featuresBuf = ByteBuffer.allocate(Ints.BYTES + header.featureVotes.size * Shorts.BYTES)
        featuresBuf.putInt(header.featureVotes.size).asShortBuffer().put(header.featureVotes.toArray)
        featuresBuf.array
    }

    val rewardVoteBytes = header.version match {
      case v if v < RewardBlockVersion => Array.empty[Byte]
      case _                           => Longs.toByteArray(header.rewardVote)
    }

    Bytes.concat(
      featureVotesBytes,
      rewardVoteBytes,
      header.generator.arr,
      signature.arr
    )
  }

  def parseBytes(bytes: Array[Byte]): Try[Block] =
    Try {
      val buf = ByteBuffer.wrap(bytes).asReadOnlyBuffer()

      val Prefix(version, timestamp, reference, baseTarget, generationSignature)   = parsePrefix(buf)
      val transactionData                                                          = parseTxs(buf, version)
      val Suffix(generator, featureVotes, rewardVote, transactionsRoot, signature) = parseSuffix(buf, version)

      val header =
        BlockHeader(version, timestamp, reference, baseTarget, generationSignature, generator, featureVotes, rewardVote, transactionsRoot, None, None)

      Block(header, signature, transactionData)
    }

  def parsePrefix(buf: ByteBuffer): Prefix = {
    val version   = buf.getByte
    val timestamp = buf.getLong
    val reference = ByteStr(buf.getByteArray(SignatureLength))

    val consensusBytesLength = buf.getInt
    val baseTarget           = Longs.fromByteArray(buf.getByteArray(Block.BaseTargetLength))
    val generationSignature  = ByteStr(buf.getByteArray(consensusBytesLength - Longs.BYTES))
    Prefix(version, timestamp, reference, baseTarget, generationSignature)
  }

  def parseTxs(buf: ByteBuffer, version: Byte): Seq[Transaction] = {
    buf.getInt // transactions data size
    readTransactionData(version, buf)
  }

  def parseSuffix(buf: ByteBuffer, version: Byte): Suffix = {
    val featureVotes     = if (version > Block.PlainBlockVersion) buf.getShortArray(buf.getInt).toSeq else Seq.empty[Short]
    val rewardVote       = if (version > Block.NgBlockVersion) buf.getLong else -1L
    val generator        = buf.getPublicKey
    val transactionsRoot = ByteStr.empty
    val signature        = ByteStr(buf.getByteArray(SignatureLength))
    Suffix(generator, featureVotes, rewardVote, transactionsRoot, signature)
  }

  case class Prefix(
      version: Byte,
      timestamp: Long,
      reference: ByteStr,
      baseTarget: Long,
      generationSignature: ByteStr
  )

  case class Suffix(
      generator: PublicKey,
      featureVotes: Seq[Short],
      rewardVote: Long,
      transactionsRoot: ByteStr,
      signature: ByteStr
  )

  def transactionField(transactions: Seq[Transaction]): JsObject = Json.obj(
    "fee"          -> transactions.map(_.assetFee).collect { case (Waves, feeAmt) => feeAmt }.sum,
    "transactions" -> JsArray(transactions.map(_.json()))
  )

  def toJson(block: Block): JsObject =
    BlockHeaderSerializer.toJson(block.header, block.bytes().length, block.transactionData.length, block.signature) ++
      transactionField(block.transactionData)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy