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

com.wavesplatform.history.Domain.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.history

import cats.implicits.catsSyntaxOption
import cats.syntax.traverse.*
import com.wavesplatform.account.{Address, KeyPair}
import com.wavesplatform.api.BlockMeta
import com.wavesplatform.api.common.*
import com.wavesplatform.block.Block.BlockId
import com.wavesplatform.block.{Block, BlockSnapshot, ChallengedHeader, MicroBlock}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.consensus.nxt.NxtLikeConsensusBlockData
import com.wavesplatform.consensus.{PoSCalculator, PoSSelector}
import com.wavesplatform.database.{DBExt, Keys, RDB, RocksDBWriter}
import com.wavesplatform.events.BlockchainUpdateTriggers
import com.wavesplatform.features.BlockchainFeatures
import com.wavesplatform.features.BlockchainFeatures.{BlockV5, RideV6}
import com.wavesplatform.lagonaki.mocks.TestBlock
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.lang.script.Script
import com.wavesplatform.mining.{BlockChallenger, BlockChallengerImpl}
import com.wavesplatform.settings.WavesSettings
import com.wavesplatform.state.*
import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult
import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult.{Applied, Ignored}
import com.wavesplatform.state.appender.BlockAppender
import com.wavesplatform.state.diffs.{BlockDiffer, TransactionDiffer}
import com.wavesplatform.test.TestTime
import com.wavesplatform.transaction.*
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.smart.script.trace.TracedResult
import com.wavesplatform.utils.{EthEncoding, SystemTime}
import com.wavesplatform.utx.UtxPoolImpl
import com.wavesplatform.wallet.Wallet
import com.wavesplatform.{Application, TestValues, crypto}
import io.netty.channel.group.DefaultChannelGroup
import io.netty.util.concurrent.GlobalEventExecutor
import monix.eval.Task
import monix.execution.Scheduler
import monix.execution.Scheduler.Implicits.global
import org.rocksdb.RocksDB
import org.scalatest.matchers.should.Matchers.*
import play.api.libs.json.{JsNull, JsValue, Json}

import scala.collection.immutable.SortedMap
import scala.concurrent.Future
import scala.concurrent.duration.*
import scala.util.Try
import scala.util.control.NonFatal

case class Domain(rdb: RDB, blockchainUpdater: BlockchainUpdaterImpl, rocksDBWriter: RocksDBWriter, settings: WavesSettings) {
  import Domain.*

  val blockchain: BlockchainUpdaterImpl = blockchainUpdater

  @volatile
  var triggers: Seq[BlockchainUpdateTriggers] = Nil

  val posSelector: PoSSelector = PoSSelector(blockchainUpdater, None)

  val transactionDiffer: Transaction => TracedResult[ValidationError, StateSnapshot] =
    TransactionDiffer(blockchain.lastBlockTimestamp, System.currentTimeMillis())(blockchain, _)

  val transactionDifferWithLog: Transaction => TracedResult[ValidationError, StateSnapshot] =
    TransactionDiffer(blockchain.lastBlockTimestamp, System.currentTimeMillis(), enableExecutionLog = true)(blockchain, _)

  def createDiffE(tx: Transaction): Either[ValidationError, StateSnapshot] = transactionDiffer(tx).resultE
  def createDiff(tx: Transaction): StateSnapshot                           = createDiffE(tx).explicitGet()

  lazy val utxPool: UtxPoolImpl =
    new UtxPoolImpl(SystemTime, blockchain, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable)
  lazy val wallet: Wallet = Wallet(settings.walletSettings.copy(file = None))

  lazy val testTime: TestTime = TestTime()
  lazy val blockAppender: Block => Task[Either[ValidationError, BlockApplyResult]] =
    BlockAppender(blockchain, testTime, utxPool, posSelector, Scheduler.singleThread("appender"))(_, None)
  lazy val blockChallenger: Option[BlockChallenger] =
    if (!settings.enableLightMode)
      Some(
        new BlockChallengerImpl(
          blockchain,
          new DefaultChannelGroup(GlobalEventExecutor.INSTANCE),
          wallet,
          settings,
          testTime,
          posSelector,
          blockAppender
        )
      )
    else None

  object commonApi {

    /** @return
      *   Tuple of (asset, feeInAsset, feeInWaves)
      * @see
      *   [[com.wavesplatform.state.diffs.FeeValidation#getMinFee(com.wavesplatform.state.Blockchain, com.wavesplatform.transaction.Transaction)]]
      */
    def calculateFee(tx: Transaction): (Asset, Long, Long) =
      transactions.calculateFee(tx).explicitGet()

    def calculateWavesFee(tx: Transaction): Long = {
      val (Waves, _, feeInWaves) = calculateFee(tx): @unchecked
      feeInWaves
    }

    def transactionMeta(transactionId: ByteStr): TransactionMeta =
      transactions
        .transactionById(transactionId)
        .getOrElse(throw new NoSuchElementException(s"No meta for $transactionId"))

    def invokeScriptResult(transactionId: ByteStr): InvokeScriptResult =
      transactionMeta(transactionId) match {
        case hsc: TransactionMeta.HasStateChanges => hsc.invokeScriptResult.get
        case _                                    => ???
      }

    def addressTransactions(address: Address): Seq[TransactionMeta] =
      transactions.transactionsByAddress(address, None, Set.empty, None).toListL.runSyncUnsafe()

    def commonTransactionsApi(challenger: Option[BlockChallenger]): CommonTransactionsApi =
      CommonTransactionsApi(
        blockchainUpdater.bestLiquidSnapshot.map(Height(blockchainUpdater.height) -> _),
        rdb,
        blockchain,
        utxPool,
        challenger,
        tx => Future.successful(utxPool.putIfNew(tx)),
        Application.loadBlockAt(rdb, blockchain)
      )

    lazy val transactions: CommonTransactionsApi = commonTransactionsApi(blockChallenger)
  }

  def liquidState: Option[NgState] = {
    val cls   = classOf[BlockchainUpdaterImpl]
    val field = cls.getDeclaredFields.find(_.getName.endsWith("ngState")).get
    field.setAccessible(true)
    field.get(blockchain).asInstanceOf[Option[NgState]]
  }

  def liquidAndSolidAssert(doCheck: () => Unit): Unit = {
    require(liquidState.isDefined, "No liquid state is present")
    try doCheck()
    catch { case NonFatal(err) => throw new RuntimeException("Liquid check failed", err) }
    makeStateSolid()
    try doCheck()
    catch { case NonFatal(err) => throw new RuntimeException("Solid check failed", err) }
  }

  def makeStateSolid(): (Int, SortedMap[String, String]) = {
    if (liquidState.isDefined) appendBlock() // Just append empty block
    (solidStateHeight, solidStateSnapshot())
  }

  def solidStateHeight: Int = {
    rdb.db.get(Keys.height)
  }

  def solidStateSnapshot(): SortedMap[String, String] = {
    val builder = SortedMap.newBuilder[String, String]
    rdb.db.iterateOver(Array.emptyByteArray, None)(e =>
      builder.addOne(EthEncoding.toHexString(e.getKey).drop(2) -> EthEncoding.toHexString(e.getValue).drop(2))
    )
    builder.result()
  }

  def lastBlock: Block = {
    blockchainUpdater.lastBlockId
      .flatMap(blockchainUpdater.liquidBlock)
      .orElse(rocksDBWriter.lastBlock)
      .getOrElse(TestBlock.create(Nil).block)
  }

  def liquidSnapshot: StateSnapshot =
    blockchainUpdater.bestLiquidSnapshot.orEmpty

  def microBlocks: Vector[MicroBlock] = blockchain.microblockIds.reverseIterator.flatMap(blockchain.microBlock).to(Vector)

  def effBalance(a: Address): Long = blockchainUpdater.effectiveBalance(a, 1000)

  def appendBlock(b: Block): BlockApplyResult = blockchainUpdater.processBlock(b).explicitGet()

  def appendBlockE(b: Block, snapshot: Option[BlockSnapshot] = None): Either[ValidationError, BlockApplyResult] =
    blockchainUpdater.processBlock(b, snapshot)

  def rollbackTo(blockId: ByteStr): DiscardedBlocks = blockchainUpdater.removeAfter(blockId).explicitGet()

  def appendMicroBlock(b: MicroBlock): BlockId = blockchainUpdater.processMicroBlock(b, None).explicitGet()

  def appendMicroBlockE(b: MicroBlock): Either[ValidationError, BlockId] = blockchainUpdater.processMicroBlock(b, None)

  def lastBlockId: ByteStr = blockchainUpdater.lastBlockId.getOrElse(randomSig)

  def carryFee(refId: Option[ByteStr]): Long = blockchainUpdater.carryFee(refId)

  def balance(address: Address): Long               = blockchainUpdater.balance(address)
  def balance(address: Address, asset: Asset): Long = blockchainUpdater.balance(address, asset)

  def nftList(address: Address): Seq[(IssuedAsset, AssetDescription)] = rdb.db.withResource(rdb.apiHandle.handle) { resource =>
    AddressPortfolio
      .nftIterator(resource, address, blockchainUpdater.bestLiquidSnapshot.orEmpty, None, blockchainUpdater.assetDescription)
      .toSeq
      .flatten
  }

  def addressTransactions(address: Address, from: Option[ByteStr] = None): Seq[(Height, Transaction)] =
    AddressTransactions
      .allAddressTransactions(
        rdb,
        blockchainUpdater.bestLiquidSnapshot.map(Height(blockchainUpdater.height) -> _),
        address,
        None,
        Set.empty,
        from
      )
      .map { case (m, tx, _) => m.height -> tx }
      .toListL
      .runSyncUnsafe()

  def portfolio(address: Address): Seq[(IssuedAsset, Long)] = Domain.portfolio(address, rdb.db, blockchainUpdater)

  def appendAndAssertSucceed(txs: Transaction*): Block = {
    val block = createBlock(Block.PlainBlockVersion, txs)
    appendBlock(block)
    txs.foreach { tx =>
      if (!blockchain.transactionSucceeded(tx.id())) {
        val stateChanges = Try(commonApi.invokeScriptResult(tx.id())).toOption.flatMap(_.error).fold(JsNull: JsValue)(Json.toJson(_))
        throw new AssertionError(s"Should succeed: ${tx.id()}, script error: ${Json.prettyPrint(stateChanges)}")
      }
    }
    lastBlock
  }

  def appendAndCatchError(txs: Transaction*): ValidationError = {
    val block  = createBlock(Block.PlainBlockVersion, txs)
    val result = appendBlockE(block)
    txs.foreach { tx =>
      assert(blockchain.transactionInfo(tx.id()).isEmpty, s"should not pass: $tx")
    }
    result.left.getOrElse(throw new RuntimeException(s"Block appended successfully: $txs"))
  }

  def appendAndAssertFailed(txs: Transaction*): Block = {
    val block = createBlock(Block.PlainBlockVersion, txs)
    appendBlockE(block) match {
      case Left(err) =>
        throw new RuntimeException(s"Should be success: $err")

      case Right(_) =>
        txs.foreach(tx => assert(!blockchain.transactionSucceeded(tx.id()), s"should fail: $tx"))
        lastBlock
    }
  }

  def appendAndAssertFailed(tx: Transaction, message: String): Block = {
    appendBlock(tx)
    assert(!blockchain.transactionSucceeded(tx.id()), s"should fail: $tx")
    liquidSnapshot.errorMessage(tx.id()).get.text should include(message)
    lastBlock
  }

  def appendBlockE(txs: Transaction*): Either[ValidationError, BlockApplyResult] =
    createBlockE(Block.PlainBlockVersion, txs).flatMap(appendBlockE(_))

  def appendBlock(version: Byte, txs: Transaction*): Block = {
    val block = createBlock(version, txs)
    appendBlock(block)
    lastBlock
  }

  def appendBlock(txs: Transaction*): Block =
    appendBlock(Block.PlainBlockVersion, txs*)

  def appendKeyBlock(signer: KeyPair = defaultSigner, ref: Option[ByteStr] = None): Block = {
    val block = createBlock(
      Block.NgBlockVersion,
      Nil,
      ref.orElse(Some(lastBlockId)),
      generator = signer
    )
    appendBlock(block) match {
      case Applied(discardedSnapshots, _) =>
        utxPool.setPrioritySnapshots(discardedSnapshots)
        utxPool.cleanUnconfirmed()
      case Ignored => ()
    }

    lastBlock
  }

  def appendMicroBlockE(txs: Transaction*): Either[Throwable, BlockId] =
    Try(appendMicroBlock(txs*)).toEither

  def createMicroBlockE(stateHash: Option[ByteStr] = None, signer: Option[KeyPair] = None, ref: Option[ByteStr] = None)(
      txs: Transaction*
  ): Either[ValidationError, MicroBlock] = {
    val lastBlock   = this.lastBlock
    val blockSigner = signer.getOrElse(defaultSigner)
    val stateHashE = if (blockchain.supportsLightNodeBlockFields()) {
      stateHash
        .map(Right(_))
        .getOrElse(
          TxStateSnapshotHashBuilder
            .computeStateHash(
              txs,
              lastBlock.header.stateHash.get,
              StateSnapshot.empty,
              blockSigner,
              rocksDBWriter.lastBlockTimestamp,
              blockchain.lastBlockTimestamp.get,
              isChallenging = false,
              blockchain
            )
            .resultE
        )
        .map(Some(_))
    } else Right(None)

    for {
      sh <- stateHashE
      block <- Block
        .buildAndSign(
          lastBlock.header.version,
          lastBlock.header.timestamp,
          lastBlock.header.reference,
          lastBlock.header.baseTarget,
          lastBlock.header.generationSignature,
          lastBlock.transactionData ++ txs,
          blockSigner,
          lastBlock.header.featureVotes,
          lastBlock.header.rewardVote,
          sh,
          None
        )
      microblock <- MicroBlock
        .buildAndSign(
          lastBlock.header.version,
          blockSigner,
          txs,
          ref.getOrElse(blockchainUpdater.lastBlockId.get),
          block.signature,
          block.header.stateHash
        )
    } yield microblock
  }

  def createMicroBlock(stateHash: Option[ByteStr] = None, signer: Option[KeyPair] = None, ref: Option[ByteStr] = None)(
      txs: Transaction*
  ): MicroBlock =
    createMicroBlockE(stateHash, signer, ref)(txs*).explicitGet()

  def appendMicroBlock(txs: Transaction*): BlockId = {
    val mb = createMicroBlock()(txs*)
    blockchainUpdater.processMicroBlock(mb, None).explicitGet()
  }

  def rollbackTo(height: Int): Unit = {
    val blockId = blockchain.blockId(height).get
    blockchainUpdater.removeAfter(blockId).explicitGet()
  }

  def rollbackMicros(offset: Int = 1): Unit = {
    val blockId =
      blockchainUpdater.microblockIds
        .drop(offset)
        .headOption
        .getOrElse(throw new IllegalStateException("Insufficient count of microblocks"))

    blockchainUpdater.removeAfter(blockId).explicitGet()
  }

  def createBlock(
      version: Byte,
      txs: Seq[Transaction],
      ref: Option[ByteStr] = blockchainUpdater.lastBlockId,
      strictTime: Boolean = false,
      generator: KeyPair = defaultSigner,
      stateHash: Option[Option[ByteStr]] = None,
      challengedHeader: Option[ChallengedHeader] = None,
      rewardVote: Long = -1L,
      timestamp: Option[Long] = None
  ): Block = createBlockE(version, txs, ref, strictTime, generator, stateHash, challengedHeader, rewardVote, timestamp).explicitGet()

  def createBlockE(
      version: Byte,
      txs: Seq[Transaction],
      ref: Option[ByteStr] = blockchainUpdater.lastBlockId,
      strictTime: Boolean = false,
      generator: KeyPair = defaultSigner,
      stateHash: Option[Option[ByteStr]] = None,
      challengedHeader: Option[ChallengedHeader] = None,
      rewardVote: Long = -1L,
      timestamp: Option[Long] = None
  ): Either[ValidationError, Block] = {
    val reference = ref.getOrElse(randomSig)

    val parentHeight     = ref.flatMap(blockchain.heightOf).getOrElse(blockchain.height)
    val parent           = blockchain.blockHeader(parentHeight).map(_.header).getOrElse(lastBlock.header)
    val greatGrandParent = blockchain.blockHeader(parentHeight - 2).map(_.header)

    for {
      resultTimestamp <-
        if (blockchain.height > 0) {
          timestamp
            .map(Right(_))
            .getOrElse(
              posSelector
                .getValidBlockDelay(blockchain.height, generator, parent.baseTarget, blockchain.balance(generator.toAddress) max 1e11.toLong)
                .map(_ + parent.timestamp)
            )
        } else
          Right(System.currentTimeMillis() - (1 hour).toMillis)
      consensus <-
        if (blockchain.height > 0)
          posSelector
            .consensusData(
              generator,
              parentHeight,
              settings.blockchainSettings.genesisSettings.averageBlockDelay,
              parent.baseTarget,
              parent.timestamp,
              greatGrandParent.map(_.timestamp),
              resultTimestamp
            )
        else Right(NxtLikeConsensusBlockData(60, generationSignature))
      resultBt =
        if (blockchain.isFeatureActivated(BlockchainFeatures.FairPoS, parentHeight)) {
          consensus.baseTarget
        } else if (parentHeight % 2 != 0) parent.baseTarget
        else consensus.baseTarget.max(PoSCalculator.MinBaseTarget)
      blockWithoutStateHash <- Block
        .buildAndSign(
          version = if (consensus.generationSignature.size == 96) Block.ProtoBlockVersion else version,
          timestamp = if (strictTime) resultTimestamp else SystemTime.getTimestamp(),
          reference = reference,
          baseTarget = resultBt,
          generationSignature = consensus.generationSignature,
          txs = txs,
          featureVotes = Nil,
          rewardVote = rewardVote,
          signer = generator,
          stateHash = None,
          challengedHeader = challengedHeader
        )
      resultStateHash <- stateHash.map(Right(_)).getOrElse {
        if (blockchain.supportsLightNodeBlockFields(blockchain.height + 1)) {
          val hitSource = posSelector.validateGenerationSignature(blockWithoutStateHash).getOrElse(blockWithoutStateHash.header.generationSignature)
          val blockchainWithNewBlock =
            SnapshotBlockchain(blockchain, StateSnapshot.empty, blockWithoutStateHash, hitSource, 0, blockchain.computeNextReward, None)
          val prevStateHash = blockchain.lastStateHash(Some(blockWithoutStateHash.header.reference))

          BlockDiffer
            .createInitialBlockSnapshot(blockchain, blockWithoutStateHash.header.reference, generator.toAddress)
            .flatMap { initSnapshot =>
              val initStateHash = BlockDiffer.computeInitialStateHash(blockchainWithNewBlock, initSnapshot, prevStateHash)

              TxStateSnapshotHashBuilder
                .computeStateHash(
                  txs,
                  initStateHash,
                  initSnapshot,
                  generator,
                  blockchain.lastBlockTimestamp,
                  blockWithoutStateHash.header.timestamp,
                  challengedHeader.nonEmpty,
                  blockchainWithNewBlock
                )
                .resultE
                .map(Some(_))
            }
        } else Right(None)
      }
      resultBlock <- Block
        .buildAndSign(
          version = if (consensus.generationSignature.size == 96) Block.ProtoBlockVersion else version,
          timestamp = if (strictTime) resultTimestamp else SystemTime.getTimestamp(),
          reference = reference,
          baseTarget = resultBt,
          generationSignature = consensus.generationSignature,
          txs = txs,
          featureVotes = Nil,
          rewardVote = rewardVote,
          signer = generator,
          stateHash = resultStateHash,
          challengedHeader = challengedHeader
        )
    } yield resultBlock
  }

  def createChallengingBlock(
      challengingMiner: KeyPair,
      challengedBlock: Block,
      strictTime: Boolean = false,
      stateHash: Option[Option[ByteStr]] = None,
      ref: Option[ByteStr] = None,
      txs: Option[Seq[Transaction]] = None,
      challengedHeader: Option[ChallengedHeader] = None,
      timestamp: Option[Long] = None
  ): Block = {
    createBlock(
      Block.ProtoBlockVersion,
      txs.getOrElse(challengedBlock.transactionData),
      ref.orElse(blockchain.lastBlockId),
      strictTime = strictTime,
      generator = challengingMiner,
      stateHash = stateHash,
      challengedHeader = Some(
        challengedHeader.getOrElse(
          ChallengedHeader(
            challengedBlock.header.timestamp,
            challengedBlock.header.baseTarget,
            challengedBlock.header.generationSignature,
            Seq.empty,
            challengedBlock.sender,
            -1,
            challengedBlock.header.stateHash,
            challengedBlock.signature
          )
        )
      ),
      timestamp = timestamp
    )
  }

  val blocksApi: CommonBlocksApi = {
    def loadBlockMetaAt(db: RocksDB, blockchainUpdater: BlockchainUpdaterImpl)(height: Int): Option[BlockMeta] =
      Application.loadBlockMetaAt(db, blockchainUpdater)(height)

    def loadBlockInfoAt(db: RDB, blockchainUpdater: BlockchainUpdaterImpl)(
        height: Int
    ): Option[(BlockMeta, Seq[(TxMeta, Transaction)])] =
      Application.loadBlockInfoAt(db, blockchainUpdater)(height)

    CommonBlocksApi(blockchainUpdater, loadBlockMetaAt(rdb.db, blockchainUpdater), loadBlockInfoAt(rdb, blockchainUpdater))
  }

  // noinspection ScalaStyle
  object helpers {
    def creditWavesToDefaultSigner(amount: Long = 10_0000_0000): Unit = {
      import com.wavesplatform.transaction.utils.EthConverters.*
      appendBlock(TxHelpers.genesis(TxHelpers.defaultAddress, amount), TxHelpers.genesis(TxHelpers.defaultSigner.toEthWavesAddress, amount))
    }

    def creditWavesFromDefaultSigner(to: Address, amount: Long = 1_0000_0000): Unit = {
      appendBlock(TxHelpers.transfer(to = to, amount = amount))
    }

    def issueAsset(issuer: KeyPair = defaultSigner, script: Script = null, amount: Long = 1000): IssuedAsset = {
      val transaction = TxHelpers.issue(issuer, script = Option(script), amount = amount)
      appendBlock(transaction)
      IssuedAsset(transaction.id())
    }

    def setScript(account: KeyPair, script: Script): Unit = {
      appendBlock(TxHelpers.setScript(account, script))
    }

    def setData(account: KeyPair, entries: DataEntry[?]*): Unit = {
      appendBlock(entries.map(TxHelpers.dataEntry(account, _))*)
    }

    def transfer(account: KeyPair, to: Address, amount: Long, asset: Asset): Unit = {
      appendBlock(TxHelpers.transfer(account, to, amount, asset))
    }

    def transferAll(account: KeyPair, to: Address, asset: Asset): Unit = {
      val balanceMinusFee = {
        val balance = blockchain.balance(account.toAddress, asset)
        if (asset == Waves) balance - TestValues.fee else balance
      }
      transfer(account, to, balanceMinusFee, asset)
    }
  }

  val transactionsApi: CommonTransactionsApi = CommonTransactionsApi(
    blockchainUpdater.bestLiquidSnapshot.map(Height(blockchainUpdater.height) -> _),
    rdb,
    blockchain,
    utxPool,
    blockChallenger,
    _ => Future.successful(TracedResult(Right(true))),
    h => blocksApi.blockAtHeight(h)
  )

  val accountsApi: CommonAccountsApi = CommonAccountsApi(
    () => blockchainUpdater.snapshotBlockchain,
    rdb,
    blockchain
  )

  val assetsApi: CommonAssetsApi = CommonAssetsApi(
    () => blockchainUpdater.bestLiquidSnapshot.orEmpty,
    rdb.db,
    blockchain
  )
}

object Domain {
  implicit class BlockchainUpdaterExt[A <: BlockchainUpdater & Blockchain](bcu: A) {
    def processBlock(block: Block, snapshot: Option[BlockSnapshot] = None): Either[ValidationError, BlockApplyResult] = {
      val hitSourcesE =
        if (bcu.height == 0 || !bcu.activatedFeaturesAt(bcu.height + 1).contains(BlockV5.id))
          Right(block.header.generationSignature -> block.header.challengedHeader.map(_.generationSignature))
        else {
          val parentHeight = bcu.heightOf(block.header.reference).getOrElse(bcu.height)

          val prevHs =
            if (bcu.isFeatureActivated(BlockchainFeatures.FairPoS, parentHeight) && parentHeight > 100)
              bcu.hitSource(parentHeight - 100).get
            else bcu.hitSource(parentHeight).get

          for {
            hs <- crypto
              .verifyVRF(block.header.generationSignature, prevHs.arr, block.header.generator, bcu.isFeatureActivated(RideV6, parentHeight))
            challengedHs <- block.header.challengedHeader.traverse(ch =>
              crypto.verifyVRF(ch.generationSignature, prevHs.arr, ch.generator, bcu.isFeatureActivated(RideV6, parentHeight))
            )
          } yield hs -> challengedHs
        }

      hitSourcesE.flatMap { case (hitSource, challengedHitSource) =>
        bcu.processBlock(block, hitSource, snapshot, challengedHitSource)
      }
    }
  }

  def portfolio(address: Address, db: RocksDB, blockchainUpdater: BlockchainUpdaterImpl): Seq[(IssuedAsset, Long)] = db.withResource { resource =>
    AddressPortfolio
      .assetBalanceIterator(
        resource,
        address,
        blockchainUpdater.bestLiquidSnapshot.orEmpty,
        id => blockchainUpdater.assetDescription(id).exists(!_.nft)
      )
      .toSeq
      .flatten
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy