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

com.wavesplatform.db.WithState.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.db

import cats.syntax.traverse.*
import com.google.common.primitives.Shorts
import com.wavesplatform.account.{Address, KeyPair}
import com.wavesplatform.block.Block
import com.wavesplatform.block.Block.{GenesisBlockVersion, GenesisGenerationSignature, GenesisGenerator, GenesisReference}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.database.{KeyTags, RDB, RocksDBWriter, TestStorageFactory, loadActiveLeases}
import com.wavesplatform.db.WithState.AddrWithBalance
import com.wavesplatform.events.BlockchainUpdateTriggers
import com.wavesplatform.features.BlockchainFeatures
import com.wavesplatform.history.Domain
import com.wavesplatform.lagonaki.mocks.TestBlock
import com.wavesplatform.lagonaki.mocks.TestBlock.BlockWithSigner
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.lang.directives.DirectiveDictionary
import com.wavesplatform.lang.directives.values.*
import com.wavesplatform.mining.MiningConstraint
import com.wavesplatform.settings.{TestFunctionalitySettings as TFS, *}
import com.wavesplatform.state.diffs.{BlockDiffer, ENOUGH_AMT}
import com.wavesplatform.state.utils.TestRocksDB
import com.wavesplatform.state.{Blockchain, BlockchainUpdaterImpl, NgState, SnapshotBlockchain, StateSnapshot, TxStateSnapshotHashBuilder}
import com.wavesplatform.test.*
import com.wavesplatform.transaction.Asset.Waves
import com.wavesplatform.transaction.TxHelpers.defaultAddress
import com.wavesplatform.transaction.smart.script.trace.TracedResult
import com.wavesplatform.transaction.{BlockchainUpdater, GenesisTransaction, Transaction, TxHelpers}
import com.wavesplatform.{NTPTime, TestHelpers}
import org.rocksdb.RocksDB
import org.scalatest.matchers.should.Matchers
import org.scalatest.{BeforeAndAfterAll, Suite}

import java.nio.file.Files
import scala.concurrent.duration.*
import scala.util.Using

trait WithState extends BeforeAndAfterAll with DBCacheSettings with Matchers with NTPTime { _: Suite =>
  protected val ignoreBlockchainUpdateTriggers: BlockchainUpdateTriggers = BlockchainUpdateTriggers.noop

  private val path  = Files.createTempDirectory(s"rocks-temp-${getClass.getSimpleName}").toAbsolutePath
  protected val rdb = RDB.open(dbSettings.copy(directory = path.toAbsolutePath.toString))

  private val MaxKey = Shorts.toByteArray(KeyTags.maxId.toShort)
  private val MinKey = new Array[Byte](2)

  protected def tempDb[A](f: RDB => A): A = {
    val path = Files.createTempDirectory(s"rocks-temp-tmp-${getClass.getSimpleName}").toAbsolutePath
    val rdb  = RDB.open(dbSettings.copy(directory = path.toAbsolutePath.toString))
    try {
      f(rdb)
    } finally {
      rdb.close()
      TestHelpers.deleteRecursively(path)
    }
  }

  override protected def afterAll(): Unit = {
    super.afterAll()
    rdb.close()
    TestHelpers.deleteRecursively(path)
  }

  protected def withRocksDBWriter[A](ws: WavesSettings)(test: RocksDBWriter => A): A = {
    try {
      val (_, rdw) = TestStorageFactory(
        ws,
        rdb,
        ntpTime,
        ignoreBlockchainUpdateTriggers
      )
      Using.resource(rdw)(test)
    } finally {
      Seq(rdb.db.getDefaultColumnFamily, rdb.txHandle.handle, rdb.txMetaHandle.handle, rdb.apiHandle.handle).foreach { cfh =>
        rdb.db.deleteRange(cfh, MinKey, MaxKey)
      }
    }
  }

  protected def withRocksDBWriter[A](bs: BlockchainSettings)(test: RocksDBWriter => A): A =
    withRocksDBWriter(TestSettings.Default.copy(blockchainSettings = bs))(test)

  def withRocksDBWriter[A](fs: FunctionalitySettings)(test: RocksDBWriter => A): A =
    withRocksDBWriter(TestRocksDB.createTestBlockchainSettings(fs))(test)

  protected def withTestState[A](ws: WavesSettings)(test: (BlockchainUpdaterImpl, RocksDBWriter) => A): A = {
    try {
      val (bcu, rdw) = TestStorageFactory(
        ws,
        rdb,
        ntpTime,
        ignoreBlockchainUpdateTriggers
      )
      Using.resource(rdw)(test(bcu, _))
    } finally {
      Seq(rdb.db.getDefaultColumnFamily, rdb.txHandle.handle, rdb.txMetaHandle.handle).foreach { cfh =>
        rdb.db.deleteRange(cfh, MinKey, MaxKey)
      }
    }
  }

  protected def withTestState[A](bs: BlockchainSettings)(test: (BlockchainUpdaterImpl, RocksDBWriter) => A): A =
    withTestState(TestSettings.Default.copy(blockchainSettings = bs))(test(_, _))

  def withTestState[A](fs: FunctionalitySettings)(test: (BlockchainUpdaterImpl, RocksDBWriter) => A): A =
    withTestState(TestRocksDB.createTestBlockchainSettings(fs))(test)

  def assertDiffEi(
      preconditions: Seq[BlockWithSigner],
      block: BlockWithSigner,
      fs: FunctionalitySettings = TFS.Enabled,
      enableExecutionLog: Boolean = false
  )(
      assertion: Either[ValidationError, StateSnapshot] => Unit
  ): Unit = withTestState(fs) { (bcu, state) =>
    assertDiffEi(preconditions, block, bcu, state, enableExecutionLog)(assertion)
  }

  def assertDiffEi(
      preconditions: Seq[BlockWithSigner],
      block: BlockWithSigner,
      bcu: BlockchainUpdaterImpl,
      state: RocksDBWriter,
      enableExecutionLog: Boolean
  )(
      assertion: Either[ValidationError, StateSnapshot] => Unit
  ): Unit = {
    def differ(blockchain: Blockchain, b: Block) =
      BlockDiffer.fromBlock(
        blockchain,
        None,
        b,
        None,
        MiningConstraint.Unlimited,
        b.header.generationSignature,
        enableExecutionLog = enableExecutionLog
      )

    preconditions.foreach { precondition =>
      val preconditionBlock = blockWithComputedStateHash(precondition.block, precondition.signer, bcu).resultE.explicitGet()
      val BlockDiffer.Result(preconditionDiff, preconditionFees, totalFee, _, _, computedStateHash) = differ(state, preconditionBlock).explicitGet()
      state.append(
        preconditionDiff,
        preconditionFees,
        totalFee,
        None,
        preconditionBlock.header.generationSignature,
        computedStateHash,
        preconditionBlock
      )
    }
    val snapshot =
      blockWithComputedStateHash(block.block, block.signer, bcu).resultE
        .flatMap(differ(state, _))
        .map(_.snapshot)
    assertion(snapshot)
  }

  def assertDiffEiTraced(
      preconditions: Seq[BlockWithSigner],
      block: BlockWithSigner,
      fs: FunctionalitySettings = TFS.Enabled,
      enableExecutionLog: Boolean = false
  )(
      assertion: TracedResult[ValidationError, StateSnapshot] => Unit
  ): Unit = withTestState(fs) { (bcu, state) =>
    def getCompBlockchain(blockchain: Blockchain) = {
      val reward = if (blockchain.height > 0) bcu.computeNextReward else None
      SnapshotBlockchain(blockchain, reward)
    }

    def differ(blockchain: Blockchain, prevBlock: Option[Block], b: Block) =
      BlockDiffer.fromBlockTraced(
        getCompBlockchain(blockchain),
        prevBlock,
        b,
        None,
        MiningConstraint.Unlimited,
        b.header.generationSignature,
        (_, _) => (),
        verify = true,
        enableExecutionLog = enableExecutionLog,
        txSignParCheck = true
      )

    preconditions.foreach { precondition =>
      val preconditionBlock = blockWithComputedStateHash(precondition.block, precondition.signer, bcu).resultE.explicitGet()
      val BlockDiffer.Result(preconditionDiff, preconditionFees, totalFee, _, _, computedStateHash) =
        differ(state, state.lastBlock, preconditionBlock).resultE.explicitGet()
      state.append(
        preconditionDiff,
        preconditionFees,
        totalFee,
        None,
        preconditionBlock.header.generationSignature,
        computedStateHash,
        preconditionBlock
      )
    }

    val snapshot1 =
      (blockWithComputedStateHash(block.block, block.signer, bcu) match {
        case right @ TracedResult(Right(_), _, _) => right.copy(trace = Nil)
        case err                                  => err
      }).flatMap(differ(state, state.lastBlock, _))

    assertion(snapshot1.map(_.snapshot))
  }

  private def assertDiffAndState(preconditions: Seq[BlockWithSigner], block: BlockWithSigner, fs: FunctionalitySettings, withNg: Boolean)(
      assertion: (StateSnapshot, Blockchain) => Unit
  ): Unit = withTestState(fs) { (bcu, state) =>
    def getCompBlockchain(blockchain: Blockchain) =
      if (withNg && fs.preActivatedFeatures.get(BlockchainFeatures.BlockReward.id).exists(_ <= blockchain.height)) {
        val reward = if (blockchain.height > 0) bcu.computeNextReward else None
        SnapshotBlockchain(blockchain, reward)
      } else blockchain

    def differ(blockchain: Blockchain, prevBlock: Option[Block], b: Block): Either[ValidationError, BlockDiffer.Result] =
      BlockDiffer.fromBlock(
        getCompBlockchain(blockchain),
        if (withNg) prevBlock else None,
        b,
        None,
        MiningConstraint.Unlimited,
        b.header.generationSignature
      )

    preconditions.foldLeft[Option[Block]](None) { (prevBlock, curBlock) =>
      val preconditionBlock = blockWithComputedStateHash(curBlock.block, curBlock.signer, bcu).resultE.explicitGet()
      val BlockDiffer.Result(snapshot, fees, totalFee, _, _, computedStateHash) = differ(state, prevBlock, preconditionBlock).explicitGet()
      state.append(snapshot, fees, totalFee, None, preconditionBlock.header.generationSignature, computedStateHash, preconditionBlock)
      Some(preconditionBlock)
    }

    val checkedBlock = blockWithComputedStateHash(block.block, block.signer, bcu).resultE.explicitGet()
    val BlockDiffer.Result(snapshot, fees, totalFee, _, _, computedStateHash) = differ(state, state.lastBlock, checkedBlock).explicitGet()
    val ngState =
      NgState(
        checkedBlock,
        snapshot,
        fees,
        totalFee,
        computedStateHash,
        fs.preActivatedFeatures.keySet,
        None,
        checkedBlock.header.generationSignature,
        Map()
      )
    val cb = SnapshotBlockchain(state, ngState)
    assertion(snapshot, cb)
    state.append(snapshot, fees, totalFee, None, checkedBlock.header.generationSignature, computedStateHash, checkedBlock)
    assertion(snapshot, state)
  }

  def assertNgDiffState(preconditions: Seq[BlockWithSigner], block: BlockWithSigner, fs: FunctionalitySettings = TFS.Enabled)(
      assertion: (StateSnapshot, Blockchain) => Unit
  ): Unit =
    assertDiffAndState(preconditions, block, fs, withNg = true)(assertion)

  def assertDiffAndState(preconditions: Seq[BlockWithSigner], block: BlockWithSigner, fs: FunctionalitySettings = TFS.Enabled)(
      assertion: (StateSnapshot, Blockchain) => Unit
  ): Unit =
    assertDiffAndState(preconditions, block, fs, withNg = false)(assertion)

  def assertDiffAndState(fs: FunctionalitySettings)(test: (Seq[Transaction] => Either[ValidationError, Unit]) => Unit): Unit =
    withTestState(fs) { (bcu, state) =>
      def getCompBlockchain(blockchain: Blockchain) = {
        val reward = if (blockchain.height > 0) bcu.computeNextReward else None
        SnapshotBlockchain(blockchain, reward)
      }

      def differ(blockchain: Blockchain, b: Block) =
        BlockDiffer.fromBlock(
          getCompBlockchain(blockchain),
          state.lastBlock,
          b,
          None,
          MiningConstraint.Unlimited,
          b.header.generationSignature
        )

      test(txs => {
        val nextHeight   = state.height + 1
        val isProto      = state.activatedFeatures.get(BlockchainFeatures.BlockV5.id).exists(nextHeight > 1 && nextHeight >= _)
        val block        = TestBlock.create(txs, if (isProto) Block.ProtoBlockVersion else Block.PlainBlockVersion)
        val checkedBlock = blockWithComputedStateHash(block.block, block.signer, bcu).resultE.explicitGet()

        differ(state, checkedBlock).map { result =>
          state.append(
            result.snapshot,
            result.carry,
            result.totalFee,
            None,
            checkedBlock.header.generationSignature.take(Block.HitSourceLength),
            result.computedStateHash,
            checkedBlock
          )
        }
      })
    }

  def assertBalanceInvariant(snapshot: StateSnapshot, db: RocksDBWriter, rewardAndFee: Long = 0): Unit = {
    snapshot.balances.toSeq
      .map {
        case ((`defaultAddress`, Waves), balance) => Waves -> (balance - db.balance(defaultAddress, Waves) - rewardAndFee)
        case ((address, asset), balance)          => asset -> (balance - db.balance(address, asset))
      }
      .groupMap(_._1)(_._2)
      .foreach { case (_, balances) => balances.sum shouldBe 0 }
    snapshot.leaseBalances.foreach { case (address, balance) => balance shouldBe db.leaseBalance(address) }
  }

  def assertLeft(preconditions: Seq[BlockWithSigner], block: BlockWithSigner, fs: FunctionalitySettings = TFS.Enabled)(errorMessage: String): Unit =
    assertDiffEi(preconditions, block, fs)(_ should produce(errorMessage))

  def blockWithComputedStateHash(
      blockWithoutStateHash: Block,
      signer: KeyPair,
      blockchain: BlockchainUpdater & Blockchain
  ): TracedResult[ValidationError, Block] = {
    (if (blockchain.supportsLightNodeBlockFields(blockchain.height + 1)) {
       val compBlockchain =
         SnapshotBlockchain(blockchain, StateSnapshot.empty, blockWithoutStateHash, ByteStr.empty, 0, blockchain.computeNextReward, None)
       val prevStateHash = blockchain.lastStateHash(Some(blockWithoutStateHash.header.reference))
       TracedResult(
         BlockDiffer
           .createInitialBlockSnapshot(
             blockchain,
             blockWithoutStateHash.header.reference,
             blockWithoutStateHash.header.generator.toAddress
           )
       )
         .flatMap { initSnapshot =>
           val initStateHash = BlockDiffer.computeInitialStateHash(compBlockchain, initSnapshot, prevStateHash)

           TxStateSnapshotHashBuilder
             .computeStateHash(
               blockWithoutStateHash.transactionData,
               initStateHash,
               initSnapshot,
               signer,
               blockchain.lastBlockTimestamp,
               blockWithoutStateHash.header.timestamp,
               blockWithoutStateHash.header.challengedHeader.isDefined,
               compBlockchain
             )
             .map(Some(_))
         }
     } else TracedResult(Right(None))).flatMap { stateHash =>
      TracedResult(
        Block
          .buildAndSign(
            version = blockWithoutStateHash.header.version,
            timestamp = blockWithoutStateHash.header.timestamp,
            reference = blockWithoutStateHash.header.reference,
            baseTarget = blockWithoutStateHash.header.baseTarget,
            generationSignature = blockWithoutStateHash.header.generationSignature,
            txs = blockWithoutStateHash.transactionData,
            featureVotes = blockWithoutStateHash.header.featureVotes,
            rewardVote = blockWithoutStateHash.header.rewardVote,
            signer = signer,
            stateHash = stateHash,
            challengedHeader = None
          )
      )
    }
  }
}

trait WithDomain extends WithState { _: Suite =>
  val DomainPresets = com.wavesplatform.test.DomainPresets
  import DomainPresets.*

  def domainSettingsWithFS(fs: FunctionalitySettings): WavesSettings =
    DomainPresets.domainSettingsWithFS(fs)

  def withDomain[A](
      settings: WavesSettings =
        DomainPresets.SettingsFromDefaultConfig.addFeatures(BlockchainFeatures.SmartAccounts), // SmartAccounts to allow V2 transfers by default
      balances: Seq[AddrWithBalance] = Seq.empty,
      wrapDB: RocksDB => RocksDB = identity
  )(test: Domain => A): A =
    withRocksDBWriter(settings) { blockchain =>
      var domain: Domain = null
      val bcu = new BlockchainUpdaterImpl(
        blockchain,
        settings,
        ntpTime,
        BlockchainUpdateTriggers.combined(domain.triggers),
        loadActiveLeases(rdb, _, _)
      )

      try {
        val wrappedDb = wrapDB(rdb.db)
        assert(wrappedDb.getNativeHandle == rdb.db.getNativeHandle, "wrap function should not create new database instance")
        domain = Domain(new RDB(wrappedDb, rdb.txMetaHandle, rdb.txHandle, rdb.txSnapshotHandle, rdb.apiHandle, Seq.empty), bcu, blockchain, settings)
        val genesis = balances.map { case AddrWithBalance(address, amount) =>
          TxHelpers.genesis(address, amount)
        }
        if (genesis.nonEmpty) {
          domain.appendBlock(
            createGenesisWithStateHash(
              genesis,
              fillStateHash = blockchain.supportsLightNodeBlockFields(),
              Some(settings.blockchainSettings.genesisSettings.initialBaseTarget)
            )
          )
        }
        test(domain)
      } finally {
        domain.utxPool.close()
        bcu.shutdown()
      }
    }

  private val allVersions = DirectiveDictionary[StdLibVersion].all
  private val lastVersion = allVersions.last

  def testDomain(
      balances: Seq[AddrWithBalance] = Nil,
      from: StdLibVersion = V3,
      to: StdLibVersion = lastVersion
  )(assertion: (StdLibVersion, Domain) => Unit): Unit =
    allVersions
      .filter(v => v >= from && v <= to)
      .foreach(v => withDomain(DomainPresets.settingsForRide(v), balances)(assertion(v, _)))

  def createGenesisWithStateHash(txs: Seq[GenesisTransaction], fillStateHash: Boolean, baseTarget: Option[Long] = None): Block = {
    val timestamp = txs.map(_.timestamp).max
    val genesisSettings = GenesisSettings(
      timestamp,
      timestamp,
      txs.map(_.amount.value).sum,
      None,
      txs.map { tx =>
        GenesisTransactionSettings(tx.recipient.toString, tx.amount.value)
      },
      baseTarget.getOrElse(2L),
      60.seconds
    )

    (for {
      txs <- genesisSettings.transactions.toList.map { gts =>
        for {
          address <- Address.fromString(gts.recipient)
          tx      <- GenesisTransaction.create(address, gts.amount, genesisSettings.timestamp)
        } yield tx
      }.sequence
      baseTarget = genesisSettings.initialBaseTarget
      timestamp  = genesisSettings.blockTimestamp
      block <- Block.buildAndSign(
        GenesisBlockVersion,
        timestamp,
        GenesisReference,
        baseTarget,
        GenesisGenerationSignature,
        txs,
        GenesisGenerator,
        Seq.empty,
        -1,
        Option.when(fillStateHash)(TxStateSnapshotHashBuilder.createGenesisStateHash(txs)),
        None
      )
    } yield block).explicitGet()
  }
}

object WithState {
  case class AddrWithBalance(address: Address, balance: Long = ENOUGH_AMT)

  object AddrWithBalance {
    def enoughBalances(accs: KeyPair*): Seq[AddrWithBalance] =
      accs.map(acc => AddrWithBalance(acc.toAddress))

    implicit def toAddrWithBalance(v: (KeyPair, Long)): AddrWithBalance = AddrWithBalance(v._1.toAddress, v._2)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy