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

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

The newest version!
package com.wavesplatform.state

import cats.data.Ior
import cats.implicits.{catsSyntaxEitherId, catsSyntaxSemigroup, toBifunctorOps, toTraverseOps}
import cats.kernel.Monoid
import com.wavesplatform.account.{Address, Alias, PublicKey}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.database.protobuf.EthereumTransactionMeta
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.TxValidationError.GenericError
import com.wavesplatform.transaction.{Asset, ERC20Address, Transaction}

import scala.collection.immutable.VectorMap

case class StateSnapshot(
    transactions: VectorMap[ByteStr, NewTransactionInfo] = VectorMap(),
    balances: VectorMap[(Address, Asset), Long] = VectorMap(), // VectorMap is used to preserve the order of NFTs for a given address
    leaseBalances: Map[Address, LeaseBalance] = Map(),
    assetStatics: Map[IssuedAsset, (AssetStaticInfo, Int)] = Map(),
    assetVolumes: Map[IssuedAsset, AssetVolumeInfo] = Map(),
    assetNamesAndDescriptions: Map[IssuedAsset, AssetInfo] = Map(),
    assetScripts: Map[IssuedAsset, AssetScriptInfo] = Map(),
    sponsorships: Map[IssuedAsset, SponsorshipValue] = Map(),
    newLeases: Map[ByteStr, LeaseStaticInfo] = Map(),
    cancelledLeases: Map[ByteStr, LeaseDetails.Status.Inactive] = Map.empty,
    aliases: Map[Alias, Address] = Map(),
    orderFills: Map[ByteStr, VolumeAndFee] = Map(),
    accountScripts: Map[PublicKey, Option[AccountScriptInfo]] = Map(),
    accountData: Map[Address, Map[String, DataEntry[?]]] = Map(),
    scriptResults: Map[ByteStr, InvokeScriptResult] = Map(),
    ethereumTransactionMeta: Map[ByteStr, EthereumTransactionMeta] = Map(),
    scriptsComplexity: Long = 0,
    erc20Addresses: Map[ERC20Address, IssuedAsset] = Map()
) {

  // ignores lease balances from portfolios
  def addBalances(portfolios: Map[Address, Portfolio], blockchain: Blockchain): Either[String, StateSnapshot] =
    StateSnapshot
      .balances(portfolios, SnapshotBlockchain(blockchain, this))
      .map(b => copy(balances = balances ++ b))

  def withTransaction(tx: NewTransactionInfo): StateSnapshot =
    copy(transactions + (tx.transaction.id() -> tx))

  def addScriptsComplexity(scriptsComplexity: Long): StateSnapshot =
    copy(scriptsComplexity = this.scriptsComplexity + scriptsComplexity)

  def setScriptsComplexity(newScriptsComplexity: Long): StateSnapshot =
    copy(scriptsComplexity = newScriptsComplexity)

  def setScriptResults(newScriptResults: Map[ByteStr, InvokeScriptResult]): StateSnapshot =
    copy(scriptResults = newScriptResults)

  def errorMessage(txId: ByteStr): Option[InvokeScriptResult.ErrorMessage] =
    scriptResults.get(txId).flatMap(_.error)

  def bindElidedTransaction(blockchain: Blockchain, tx: Transaction): StateSnapshot =
    copy(
      transactions = transactions + (tx.id() -> NewTransactionInfo.create(tx, TxMeta.Status.Elided, StateSnapshot.empty, blockchain))
    )

  lazy val accountScriptsByAddress: Map[Address, Option[AccountScriptInfo]] =
    accountScripts.map { case (pk, script) => (pk.toAddress, script) }

  lazy val hashString: String =
    Integer.toHexString(hashCode())
}

object StateSnapshot {

  def build(
      blockchain: Blockchain,
      portfolios: Map[Address, Portfolio] = Map(),
      orderFills: Map[ByteStr, VolumeAndFee] = Map(),
      issuedAssets: Seq[(IssuedAsset, NewAssetInfo)] = Seq(),
      updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]] = Map(),
      assetScripts: Map[IssuedAsset, AssetScriptInfo] = Map(),
      sponsorships: Map[IssuedAsset, Sponsorship] = Map(),
      newLeases: Map[ByteStr, LeaseStaticInfo] = Map(),
      cancelledLeases: Map[ByteStr, LeaseDetails.Status.Inactive] = Map.empty,
      aliases: Map[Alias, Address] = Map(),
      accountData: Map[Address, Map[String, DataEntry[?]]] = Map(),
      accountScripts: Map[PublicKey, Option[AccountScriptInfo]] = Map(),
      scriptResults: Map[ByteStr, InvokeScriptResult] = Map(),
      ethereumTransactionMeta: Map[ByteStr, EthereumTransactionMeta] = Map(),
      scriptsComplexity: Long = 0,
      transactions: VectorMap[ByteStr, NewTransactionInfo] = VectorMap()
  ): Either[ValidationError, StateSnapshot] = {
    val r =
      for {
        b  <- balances(portfolios, blockchain)
        lb <- leaseBalances(portfolios, blockchain)
        of <- this.orderFills(orderFills, blockchain)
      } yield StateSnapshot(
        transactions,
        b,
        lb,
        assetStatics(issuedAssets),
        assetVolumes(blockchain, issuedAssets, updatedAssets),
        assetNamesAndDescriptions(issuedAssets, updatedAssets),
        assetScripts,
        sponsorships.collect { case (asset, value: SponsorshipValue) => (asset, value) },
        newLeases,
        cancelledLeases,
        aliases,
        of,
        accountScripts,
        accountData,
        scriptResults,
        ethereumTransactionMeta,
        scriptsComplexity,
        issuedAssets.view.map { case (id, _) => ERC20Address(id) -> id }.toMap
      )
    r.leftMap(GenericError(_))
  }

  // ignores lease balances from portfolios
  private def balances(portfolios: Map[Address, Portfolio], blockchain: Blockchain): Either[String, VectorMap[(Address, Asset), Long]] =
    flatTraverse(portfolios) { case (address, Portfolio(wavesAmount, _, assets)) =>
      val assetBalancesE = flatTraverse(assets) {
        case (_, 0) =>
          Right(VectorMap[(Address, Asset), Long]())
        case (assetId, balance) =>
          safeSum(blockchain.balance(address, assetId), balance, s"$address -> Asset balance")
            .map(newBalance => VectorMap((address, assetId: Asset) -> newBalance))
      }
      if (wavesAmount != 0)
        for {
          assetBalances   <- assetBalancesE
          newWavesBalance <- safeSum(blockchain.balance(address), wavesAmount, s"$address -> Waves balance")
        } yield assetBalances + ((address, Waves) -> newWavesBalance)
      else
        assetBalancesE
    }

  private def flatTraverse[E, K1, V1, K2, V2](m: Map[K1, V1])(f: (K1, V1) => Either[E, VectorMap[K2, V2]]): Either[E, VectorMap[K2, V2]] =
    m.foldLeft(VectorMap[K2, V2]().asRight[E]) {
      case (e @ Left(_), _) =>
        e
      case (Right(acc), (k, v)) =>
        f(k, v).map(acc ++ _)
    }

  def ofLeaseBalances(balances: Map[Address, LeaseBalance], blockchain: Blockchain): Either[String, StateSnapshot] =
    balances.toSeq
      .traverse { case (address, leaseBalance) =>
        leaseBalance.combineF[Either[String, *]](blockchain.leaseBalance(address)).map(address -> _)
      }
      .map(newBalances => StateSnapshot(leaseBalances = newBalances.toMap))

  private def leaseBalances(portfolios: Map[Address, Portfolio], blockchain: Blockchain): Either[String, Map[Address, LeaseBalance]] =
    portfolios.toSeq
      .flatTraverse {
        case (address, Portfolio(_, lease, _)) if lease.out != 0 || lease.in != 0 =>
          val bLease = blockchain.leaseBalance(address)
          for {
            newIn  <- safeSum(bLease.in, lease.in, s"$address -> Lease")
            newOut <- safeSum(bLease.out, lease.out, s"$address -> Lease")
          } yield Seq(address -> LeaseBalance(newIn, newOut))
        case _ =>
          Seq().asRight[String]
      }
      .map(_.toMap)

  def assetStatics(issuedAssets: Seq[(IssuedAsset, NewAssetInfo)]): Map[IssuedAsset, (AssetStaticInfo, Int)] =
    issuedAssets.view.zipWithIndex.map { case ((asset, info), idx) =>
      asset -> (info.static, idx + 1)
    }.toMap

  private def assetVolumes(
      blockchain: Blockchain,
      issuedAssets: Seq[(IssuedAsset, NewAssetInfo)],
      updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]]
  ): Map[IssuedAsset, AssetVolumeInfo] = {
    val issued = issuedAssets.view.map { case (id, nai) => id -> nai.volume }.toMap
    val updated = updatedAssets.collect {
      case (asset, Ior.Right(volume))   => (asset, volume)
      case (asset, Ior.Both(_, volume)) => (asset, volume)
    }
    (issued |+| updated).map { case (asset, volume) =>
      val blockchainAsset = blockchain.assetDescription(asset)
      val newIsReissuable = blockchainAsset.map(_.reissuable && volume.isReissuable).getOrElse(volume.isReissuable)
      val newVolume       = blockchainAsset.map(_.totalVolume + volume.volume).getOrElse(volume.volume)
      asset -> AssetVolumeInfo(newIsReissuable, newVolume)
    }
  }

  private def assetNamesAndDescriptions(
      issuedAssets: Seq[(IssuedAsset, NewAssetInfo)],
      updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]]
  ): Map[IssuedAsset, AssetInfo] = {
    val issued = issuedAssets.view.map { case (id, nai) => id -> nai.dynamic }.toMap
    val updated = updatedAssets.collect {
      case (asset, Ior.Left(info))    => (asset, info)
      case (asset, Ior.Both(info, _)) => (asset, info)
    }
    issued ++ updated
  }

  private def orderFills(volumeAndFees: Map[ByteStr, VolumeAndFee], blockchain: Blockchain): Either[String, Map[ByteStr, VolumeAndFee]] =
    volumeAndFees.toSeq
      .traverse { case (orderId, value) =>
        value.combineE(blockchain.filledVolumeAndFee(orderId)).map(orderId -> _)
      }
      .map(_.toMap)

  implicit val monoid: Monoid[StateSnapshot] = new Monoid[StateSnapshot] {
    override val empty: StateSnapshot =
      StateSnapshot()

    override def combine(s1: StateSnapshot, s2: StateSnapshot): StateSnapshot =
      StateSnapshot(
        s1.transactions ++ s2.transactions,
        s1.balances ++ s2.balances,
        s1.leaseBalances ++ s2.leaseBalances,
        s1.assetStatics ++ s2.assetStatics.map { case (id, (asi, idx)) => (id, (asi, idx + s1.assetStatics.size)) },
        s1.assetVolumes ++ s2.assetVolumes,
        s1.assetNamesAndDescriptions ++ s2.assetNamesAndDescriptions,
        s1.assetScripts ++ s2.assetScripts,
        s1.sponsorships ++ s2.sponsorships,
        s1.newLeases ++ s2.newLeases,
        s1.cancelledLeases ++ s2.cancelledLeases,
        s1.aliases ++ s2.aliases,
        s1.orderFills ++ s2.orderFills,
        s1.accountScripts ++ s2.accountScripts,
        combineDataEntries(s1.accountData, s2.accountData),
        s1.scriptResults |+| s2.scriptResults,
        s1.ethereumTransactionMeta ++ s2.ethereumTransactionMeta,
        s1.scriptsComplexity + s2.scriptsComplexity,
        s1.erc20Addresses ++ s2.erc20Addresses
      )

    private def combineDataEntries(
        entries1: Map[Address, Map[String, DataEntry[?]]],
        entries2: Map[Address, Map[String, DataEntry[?]]]
    ): Map[Address, Map[String, DataEntry[?]]] =
      entries2.foldLeft(entries1) { case (result, (address, addressEntries2)) =>
        val resultAddressEntries =
          result
            .get(address)
            .fold(address -> addressEntries2)(addressEntries1 => address -> (addressEntries1 ++ addressEntries2))
        result + resultAddressEntries
      }
  }

  val empty: StateSnapshot = StateSnapshot()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy