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

com.wavesplatform.api.common.CommonAccountsApi.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.api.common

import com.google.common.base.Charsets
import com.google.common.collect.AbstractIterator
import com.wavesplatform.account.{Address, Alias}
import com.wavesplatform.api.common.AddressPortfolio.{assetBalanceIterator, nftIterator}
import com.wavesplatform.api.common.lease.AddressLeaseInfo
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.database.{AddressId, DBExt, DBResource, KeyTags, Keys, RDB}
import com.wavesplatform.features.BlockchainFeatures
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.state.{AccountScriptInfo, AssetDescription, Blockchain, DataEntry, SnapshotBlockchain}
import com.wavesplatform.transaction.Asset.IssuedAsset
import monix.eval.Task
import monix.reactive.Observable
import org.rocksdb.RocksIterator

import java.util.regex.Pattern
import scala.annotation.tailrec
import scala.jdk.CollectionConverters.*

trait CommonAccountsApi {
  import CommonAccountsApi.*

  def balance(address: Address, confirmations: Int = 0): Long

  def effectiveBalance(address: Address, confirmations: Int = 0): Long

  def balanceDetails(address: Address): Either[String, BalanceDetails]

  def assetBalance(address: Address, asset: IssuedAsset): Long

  def portfolio(address: Address): Observable[Seq[(IssuedAsset, Long)]]

  def nftList(address: Address, after: Option[IssuedAsset]): Observable[Seq[(IssuedAsset, AssetDescription)]]

  def script(address: Address): Option[AccountScriptInfo]

  def data(address: Address, key: String): Option[DataEntry[?]]

  def dataStream(address: Address, regex: Option[String]): Observable[DataEntry[?]]

  def activeLeases(address: Address): Observable[LeaseInfo]

  def leaseInfo(leaseId: ByteStr): Option[LeaseInfo]

  def resolveAlias(alias: Alias): Either[ValidationError, Address]
}

object CommonAccountsApi {
  final case class BalanceDetails(regular: Long, generating: Long, available: Long, effective: Long, leaseIn: Long, leaseOut: Long)

  def apply(
      compositeBlockchain: () => SnapshotBlockchain,
      rdb: RDB,
      blockchain: Blockchain
  ): CommonAccountsApi = new CommonAccountsApi {

    override def balance(address: Address, confirmations: Int = 0): Long =
      blockchain.balance(address, blockchain.height, confirmations)

    override def effectiveBalance(address: Address, confirmations: Int = 0): Long = {
      blockchain.effectiveBalance(address, confirmations)
    }

    override def balanceDetails(address: Address): Either[String, BalanceDetails] = {
      val portfolio = blockchain.wavesPortfolio(address)
      val isBanned  = blockchain.hasBannedEffectiveBalance(address)
      portfolio
        .effectiveBalance(isBanned)
        .map(effectiveBalance =>
          BalanceDetails(
            portfolio.balance,
            blockchain.generatingBalance(address),
            portfolio.balance - portfolio.lease.out,
            effectiveBalance,
            portfolio.lease.in,
            portfolio.lease.out
          )
        )
    }

    override def assetBalance(address: Address, asset: IssuedAsset): Long = blockchain.balance(address, asset)

    override def portfolio(address: Address): Observable[Seq[(IssuedAsset, Long)]] = {
      val featureNotActivated = !blockchain.isFeatureActivated(BlockchainFeatures.ReduceNFTFee)
      val compBlockchain      = compositeBlockchain()
      def includeNft(assetId: IssuedAsset): Boolean =
        featureNotActivated || !compBlockchain.assetDescription(assetId).exists(_.nft)

      rdb.db.resourceObservable.flatMap { resource =>
        Observable.fromIterator(Task(assetBalanceIterator(resource, address, compBlockchain.snapshot, includeNft)))
      }
    }

    override def nftList(address: Address, after: Option[IssuedAsset]): Observable[Seq[(IssuedAsset, AssetDescription)]] = {
      rdb.db.resourceObservable(rdb.apiHandle.handle).flatMap { resource =>
        Observable
          .fromIterator(Task(nftIterator(resource, address, compositeBlockchain().snapshot, after, blockchain.assetDescription)))
      }
    }

    override def script(address: Address): Option[AccountScriptInfo] = blockchain.accountScript(address)

    override def data(address: Address, key: String): Option[DataEntry[?]] =
      blockchain.accountData(address, key)

    override def dataStream(address: Address, regex: Option[String]): Observable[DataEntry[?]] = Observable.defer {
      val pattern = regex.map(_.r.pattern)
      val entriesFromDiff = compositeBlockchain().snapshot.accountData
        .get(address)
        .fold(Array.empty[DataEntry[?]])(_.filter { case (k, _) => pattern.forall(_.matcher(k).matches()) }.values.toArray.sortBy(_.key))

      rdb.db.resourceObservable.flatMap { dbResource =>
        dbResource.get(Keys.addressId(address)).fold(Observable.fromIterable(entriesFromDiff)) { addressId =>
          Observable
            .fromIterator(
              Task(new AddressDataIterator(dbResource, addressId, entriesFromDiff, pattern).asScala)
            )
            .filterNot(_.isEmpty)
        }
      }
    }

    override def resolveAlias(alias: Alias): Either[ValidationError, Address] = blockchain.resolveAlias(alias)

    override def activeLeases(address: Address): Observable[LeaseInfo] =
      AddressLeaseInfo.activeLeases(rdb, compositeBlockchain().snapshot, address)

    def leaseInfo(leaseId: ByteStr): Option[LeaseInfo] =
      blockchain.leaseDetails(leaseId).map(LeaseInfo.fromLeaseDetails(leaseId, _))
  }

  private class AddressDataIterator(
      db: DBResource,
      addressId: AddressId,
      entriesFromDiff: Array[DataEntry[?]],
      pattern: Option[Pattern]
  ) extends AbstractIterator[DataEntry[?]] {
    private val prefix: Array[Byte] = KeyTags.Data.prefixBytes ++ addressId.toByteArray

    private val length: Int = entriesFromDiff.length

    db.withSafePrefixIterator(_.seek(prefix))(())

    private var nextIndex                         = 0
    private var nextDbEntry: Option[DataEntry[?]] = None

    private def matches(dataKey: String): Boolean = pattern.forall(_.matcher(dataKey).matches())

    @tailrec
    private def doComputeNext(iter: RocksIterator): DataEntry[?] =
      nextDbEntry match {
        case Some(dbEntry) =>
          if (nextIndex < length) {
            val entryFromDiff = entriesFromDiff(nextIndex)
            if (entryFromDiff.key < dbEntry.key) {
              nextIndex += 1
              entryFromDiff
            } else if (entryFromDiff.key == dbEntry.key) {
              nextIndex += 1
              nextDbEntry = None
              entryFromDiff
            } else {
              nextDbEntry = None
              dbEntry
            }
          } else {
            nextDbEntry = None
            dbEntry
          }
        case None =>
          if (!iter.isValid) {
            if (nextIndex < length) {
              nextIndex += 1
              entriesFromDiff(nextIndex - 1)
            } else {
              endOfData()
            }
          } else {
            val dataKey = new String(iter.key().drop(prefix.length), Charsets.UTF_8)
            if (matches(dataKey)) {
              nextDbEntry = Option(iter.value()).map { arr =>
                Keys.data(addressId, dataKey).parse(arr).entry
              }
            }
            iter.next()
            doComputeNext(iter)
          }
      }

    override def computeNext(): DataEntry[?] =
      db.withSafePrefixIterator(doComputeNext)(endOfData())
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy