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

com.wavesplatform.state.TxStateSnapshotHashBuilder.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.state

import cats.implicits.catsSyntaxSemigroup
import cats.syntax.either.*
import com.google.common.primitives.{Ints, Longs, UnsignedBytes}
import com.wavesplatform.account.{Address, KeyPair}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.crypto
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.state.TxMeta.Status
import com.wavesplatform.state.diffs.BlockDiffer.{CurrentBlockFeePart, maybeApplySponsorship}
import com.wavesplatform.state.diffs.TransactionDiffer
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.smart.script.trace.TracedResult
import com.wavesplatform.transaction.{GenesisTransaction, Transaction}
import org.bouncycastle.crypto.digests.Blake2bDigest

import java.nio.charset.StandardCharsets
import scala.collection.mutable

object TxStateSnapshotHashBuilder {
  private implicit val ByteArrayOrdering: Ordering[Array[Byte]] = (x, y) => UnsignedBytes.lexicographicalComparator().compare(x, y)

  val InitStateHash: ByteStr = ByteStr(crypto.fastHash(""))

  final case class Result(txStateSnapshotHash: ByteStr) {
    def createHash(prevHash: ByteStr): ByteStr =
      TxStateSnapshotHashBuilder.createHash(Seq(prevHash.arr, txStateSnapshotHash.arr))
  }

  case class TxStatusInfo(id: ByteStr, status: TxMeta.Status)

  def createHashFromSnapshot(snapshot: StateSnapshot, txStatusOpt: Option[TxStatusInfo]): Result = {
    val changedKeys = mutable.SortedSet.empty[Array[Byte]]

    snapshot.balances.foreach { case ((address, asset), balance) =>
      asset match {
        case Waves              => changedKeys += address.bytes ++ Longs.toByteArray(balance)
        case asset: IssuedAsset => changedKeys += address.bytes ++ asset.id.arr ++ Longs.toByteArray(balance)
      }
    }

    snapshot.leaseBalances.foreach { case (address, balance) =>
      changedKeys += address.bytes ++ Longs.toByteArray(balance.in) ++ Longs.toByteArray(balance.out)
    }

    for {
      (address, data) <- snapshot.accountData
      entry           <- data.values
    } changedKeys += address.bytes ++ entry.key.getBytes(StandardCharsets.UTF_8) ++ entry.valueBytes

    snapshot.aliases.foreach { case (alias, address) =>
      changedKeys += address.bytes ++ alias.name.getBytes(StandardCharsets.UTF_8)
    }

    snapshot.accountScripts.foreach { case (pk, sv) =>
      changedKeys += pk.arr ++ (sv match {
        case Some(s) => s.script.bytes().arr ++ Longs.toByteArray(s.verifierComplexity)
        case None    => Array.emptyByteArray
      })
    }

    for {
      (asset, scriptInfo) <- snapshot.assetScripts
    } changedKeys += asset.id.arr ++ scriptInfo.script.bytes().arr

    snapshot.newLeases.foreach { case (leaseId, details) =>
      changedKeys += leaseId.arr ++ booleanToBytes(true)
      changedKeys += leaseId.arr ++ details.sender.arr ++ details.recipientAddress.bytes ++ Longs.toByteArray(details.amount.value)
    }

    snapshot.cancelledLeases.keys.foreach { leaseId =>
      changedKeys += leaseId.arr ++ booleanToBytes(false)
    }

    snapshot.sponsorships.foreach { case (asset, sponsorship) =>
      changedKeys += asset.id.arr ++ Longs.toByteArray(sponsorship.minFee)
    }

    snapshot.orderFills.foreach { case (orderId, fillInfo) =>
      changedKeys += orderId.arr ++ Longs.toByteArray(fillInfo.volume) ++ Longs.toByteArray(fillInfo.fee)
    }

    snapshot.assetStatics.foreach { case (asset, (assetInfo, _)) =>
      changedKeys += asset.id.arr ++ assetInfo.issuer.arr ++ Array(assetInfo.decimals.toByte) ++ booleanToBytes(assetInfo.nft)
    }

    snapshot.assetVolumes.foreach { case (asset, volume) =>
      changedKeys += asset.id.arr ++ booleanToBytes(volume.isReissuable) ++ volume.volume.toByteArray
    }

    snapshot.assetNamesAndDescriptions.foreach { case (asset, assetInfo) =>
      changedKeys += asset.id.arr ++
        assetInfo.name.toByteArray ++
        assetInfo.description.toByteArray ++
        Ints.toByteArray(assetInfo.lastUpdatedAt.toInt)
    }

    txStatusOpt.foreach(txInfo =>
      txInfo.status match {
        case Status.Failed    => changedKeys += txInfo.id.arr ++ Array(1: Byte)
        case Status.Elided    => changedKeys += txInfo.id.arr ++ Array(2: Byte)
        case Status.Succeeded =>
      }
    )

    Result(createHash(changedKeys))
  }

  def createGenesisStateHash(txs: Seq[GenesisTransaction]): ByteStr =
    ByteStr(
      txs
        .foldLeft(InitStateHash.arr -> Map.empty[Address, Long]) { case ((prevStateHash, balances), tx) =>
          val newBalance = balances.getOrElse(tx.recipient, 0L) + tx.amount.value
          val tsh =
            crypto.fastHash(tx.recipient.bytes ++ Longs.toByteArray(newBalance))
          val newStateHash = crypto.fastHash(prevStateHash ++ tsh)
          newStateHash -> balances.updated(tx.recipient, newBalance)
        }
        ._1
    )

  def computeStateHash(
      txs: Seq[Transaction],
      initStateHash: ByteStr,
      initSnapshot: StateSnapshot,
      signer: KeyPair,
      prevBlockTimestamp: Option[Long],
      currentBlockTimestamp: Long,
      isChallenging: Boolean,
      blockchain: Blockchain
  ): TracedResult[ValidationError, ByteStr] = {
    val txDiffer = TransactionDiffer(prevBlockTimestamp, currentBlockTimestamp) _

    txs
      .foldLeft[TracedResult[ValidationError, (ByteStr, StateSnapshot)]](TracedResult.wrapValue(initStateHash -> initSnapshot)) {
        case (TracedResult(Right((prevStateHash, accSnapshot)), _, _), tx) =>
          val accBlockchain  = SnapshotBlockchain(blockchain, accSnapshot)
          val txDifferResult = txDiffer(accBlockchain, tx)
          txDifferResult.resultE match {
            case Right(txSnapshot) =>
              val (feeAsset, feeAmount) =
                maybeApplySponsorship(accBlockchain, accBlockchain.height >= Sponsorship.sponsoredFeesSwitchHeight(blockchain), tx.assetFee)
              val minerPortfolio = Map(signer.toAddress -> Portfolio.build(feeAsset, feeAmount).multiply(CurrentBlockFeePart))

              val txSnapshotWithBalances = txSnapshot.addBalances(minerPortfolio, accBlockchain).explicitGet()
              val txInfo                 = txSnapshot.transactions.head._2
              val stateHash =
                TxStateSnapshotHashBuilder
                  .createHashFromSnapshot(txSnapshotWithBalances, Some(TxStatusInfo(txInfo.transaction.id(), txInfo.status)))
                  .createHash(prevStateHash)

              txDifferResult.copy(resultE = Right((stateHash, accSnapshot |+| txSnapshotWithBalances)))
            case Left(_) if isChallenging =>
              txDifferResult.copy(resultE =
                Right(
                  (
                    TxStateSnapshotHashBuilder
                      .createHashFromSnapshot(StateSnapshot.empty, Some(TxStatusInfo(tx.id(), TxMeta.Status.Elided)))
                      .createHash(prevStateHash),
                    accSnapshot.bindElidedTransaction(accBlockchain, tx)
                  )
                )
              )

            case Left(err) => txDifferResult.copy(resultE = err.asLeft[(ByteStr, StateSnapshot)])
          }
        case (err @ TracedResult(Left(_), _, _), _) => err
      }
      .map(_._1)
  }

  private def booleanToBytes(flag: Boolean): Array[Byte] =
    if (flag) Array(1: Byte) else Array(0: Byte)

  private def createHash(bs: Iterable[Array[Byte]]): ByteStr = {
    val digestFn: Blake2bDigest = new Blake2bDigest(crypto.DigestLength * 8)
    bs.foreach(bs => digestFn.update(bs, 0, bs.length))
    val result = new Array[Byte](crypto.DigestLength)
    digestFn.doFinal(result, 0)
    ByteStr(result)
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy