From 6b2b03561b55eb5460629c71b96bc44712c3a364 Mon Sep 17 00:00:00 2001 From: Sergey Nazarov Date: Tue, 18 Oct 2022 19:23:13 +0400 Subject: [PATCH] Bloom filter in diff (#3765) --- .../com/wavesplatform/events/events.scala | 2 +- .../com/wavesplatform/lang/Testing.scala | 20 -- .../v1/evaluator/ctx/impl/CryptoContext.scala | 14 +- .../com/wavesplatform/lang/Testing.scala | 18 ++ .../api/common/AddressTransactions.scala | 2 +- .../com/wavesplatform/database/Caches.scala | 8 +- .../state/BlockchainUpdaterImpl.scala | 2 +- .../scala/com/wavesplatform/state/Diff.scala | 160 +++++++++++++--- .../state/diffs/EthereumTransactionDiff.scala | 4 +- .../state/diffs/LeaseTransactionsDiff.scala | 8 +- .../state/diffs/TransactionDiffer.scala | 6 +- .../state/diffs/TransferDiff.scala | 2 +- .../diffs/invoke/InvokeDiffsCommon.scala | 23 +-- .../state/diffs/invoke/InvokeScript.scala | 12 +- .../state/diffs/invoke/InvokeScriptDiff.scala | 2 +- .../invoke/InvokeScriptTransactionDiff.scala | 4 +- .../state/reader/CompositeBlockchain.scala | 11 +- .../transaction/ABIConverter.scala | 172 +++++++++--------- .../transaction/EthereumTransaction.scala | 2 - .../impl/InvokeScriptTxSerializer.scala | 14 +- .../transaction/smart/WavesEnvironment.scala | 7 +- .../wavesplatform/utx/UtxPriorityPool.scala | 14 +- .../state/BlockchainUpdaterImplSpec.scala | 2 +- .../diffs/AssetTransactionsDiffTest.scala | 2 +- .../diffs/ci/InvokeAffectedAddressTest.scala | 6 +- .../diffs/ci/InvokeAssetChecksTest.scala | 14 +- .../ci/InvokeScriptTransactionDiffTest.scala | 10 +- .../diffs/ci/MultiPaymentInvokeDiffTest.scala | 4 +- .../diffs/ci/sync/SyncDAppTransferTest.scala | 6 +- .../ci/sync/SyncInvokeValidationTest.scala | 2 +- .../wavesplatform/utils/DiffMatchers.scala | 2 +- 31 files changed, 326 insertions(+), 229 deletions(-) delete mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/Testing.scala create mode 100644 lang/testkit/src/main/scala/com/wavesplatform/lang/Testing.scala diff --git a/grpc-server/src/main/scala/com/wavesplatform/events/events.scala b/grpc-server/src/main/scala/com/wavesplatform/events/events.scala index ed250b44af2..7439cd6fd48 100644 --- a/grpc-server/src/main/scala/com/wavesplatform/events/events.scala +++ b/grpc-server/src/main/scala/com/wavesplatform/events/events.scala @@ -421,7 +421,7 @@ object StateUpdate { ) } - diff.transactions.values.map { tx => + diff.transactions.map { tx => TransactionMetadata( tx.transaction match { case a: Authorized => a.sender.toAddress.toByteString diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/Testing.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/Testing.scala deleted file mode 100644 index 983410a07e8..00000000000 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/Testing.scala +++ /dev/null @@ -1,20 +0,0 @@ -package com.wavesplatform.lang -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.lang.v1.compiler.Terms._ - -import scala.util.Right - -object Testing { - - def evaluated(i: Any): Either[ExecutionError, EVALUATED] = i match { - case s: String => CONST_STRING(s) - case s: Long => Right(CONST_LONG(s)) - case s: Int => Right(CONST_LONG(s)) - case s: ByteStr => CONST_BYTESTR(s) - case s: CaseObj => Right(s) - case s: Boolean => Right(CONST_BOOLEAN(s)) - case a: Seq[_] => ARR(a.map(x => evaluated(x).explicitGet()).toIndexedSeq, false) - case _ => Left("Bad Assert: unexpected type") - } -} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala index a94b86ee568..fdb836504ae 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala @@ -362,14 +362,12 @@ object CryptoContext { ("index", LONG) ) { case xs @ ARR(proof) :: CONST_BYTESTR(value) :: CONST_LONG(index) :: Nil => - if (value.size == 32 && proof.length <= 16 && proof.forall({ - case CONST_BYTESTR(v) => v.size == 32 - case _ => false - })) { - CONST_BYTESTR(ByteStr(createRoot(value.arr, Math.toIntExact(index), proof.reverse.map({ - case CONST_BYTESTR(v) => v.arr - case _ => throw new Exception("Expect ByteStr") - })))) + val filteredProofs = proof.collect { + case bs@CONST_BYTESTR(v) if v.size == 32 => bs + } + + if (value.size == 32 && proof.length <= 16 && filteredProofs.size == proof.size) { + CONST_BYTESTR(ByteStr(createRoot(value.arr, Math.toIntExact(index), filteredProofs.reverse.map(_.bs.arr)))) } else { notImplemented[Id, EVALUATED](s"createMerkleRoot(merkleProof: ByteVector, valueBytes: ByteVector)", xs) } diff --git a/lang/testkit/src/main/scala/com/wavesplatform/lang/Testing.scala b/lang/testkit/src/main/scala/com/wavesplatform/lang/Testing.scala new file mode 100644 index 00000000000..bb9e39eb1c5 --- /dev/null +++ b/lang/testkit/src/main/scala/com/wavesplatform/lang/Testing.scala @@ -0,0 +1,18 @@ +package com.wavesplatform.lang +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.lang.v1.compiler.Terms.* + +object Testing { + + def evaluated(i: Any): Either[ExecutionError, EVALUATED] = i match { + case s: String => CONST_STRING(s) + case s: Long => Right(CONST_LONG(s)) + case s: Int => Right(CONST_LONG(s)) + case s: ByteStr => CONST_BYTESTR(s) + case s: CaseObj => Right(s) + case s: Boolean => Right(CONST_BOOLEAN(s)) + case a: Seq[?] => ARR(a.map(x => evaluated(x).explicitGet()).toIndexedSeq, false) + case _ => Left("Bad Assert: unexpected type") + } +} diff --git a/node/src/main/scala/com/wavesplatform/api/common/AddressTransactions.scala b/node/src/main/scala/com/wavesplatform/api/common/AddressTransactions.scala index 1142f62b1f3..95ec7458642 100644 --- a/node/src/main/scala/com/wavesplatform/api/common/AddressTransactions.scala +++ b/node/src/main/scala/com/wavesplatform/api/common/AddressTransactions.scala @@ -88,7 +88,7 @@ object AddressTransactions { ): Seq[(TxMeta, Transaction)] = (for { (height, diff) <- maybeDiff.toSeq - nti <- diff.transactions.values.toSeq.reverse + nti <- diff.transactions.toSeq.reverse if nti.affected(subject) } yield (TxMeta(height, nti.applied, nti.spentComplexity), nti.transaction)) .dropWhile { case (_, tx) => fromId.isDefined && !fromId.contains(tx.id()) } diff --git a/node/src/main/scala/com/wavesplatform/database/Caches.scala b/node/src/main/scala/com/wavesplatform/database/Caches.scala index 41c72032e71..af51b7a2b7d 100644 --- a/node/src/main/scala/com/wavesplatform/database/Caches.scala +++ b/node/src/main/scala/com/wavesplatform/database/Caches.scala @@ -190,7 +190,7 @@ abstract class Caches(spendableBalanceChanged: Observer[(Address, Asset)]) exten val newAddresses = Set.newBuilder[Address] newAddresses ++= diff.portfolios.keys.filter(addressIdCache.get(_).isEmpty) - for (NewTransactionInfo(_, addresses, _, _) <- diff.transactions.values; address <- addresses if addressIdCache.get(address).isEmpty) { + for (NewTransactionInfo(_, addresses, _, _) <- diff.transactions; address <- addresses if addressIdCache.get(address).isEmpty) { newAddresses += address } @@ -210,11 +210,11 @@ abstract class Caches(spendableBalanceChanged: Observer[(Address, Asset)]) exten val transactionMeta = Seq.newBuilder[(TxMeta, Transaction)] val addressTransactions = ArrayListMultimap.create[AddressId, TransactionId]() - for (((id, nti), _) <- diff.transactions.zipWithIndex) { - transactionIds.put(id, newHeight) + for (nti <- diff.transactions) { + transactionIds.put(nti.transaction.id(), newHeight) transactionMeta += (TxMeta(Height(newHeight), nti.applied, nti.spentComplexity) -> nti.transaction) for (addr <- nti.affected) { - addressTransactions.put(addressIdWithFallback(addr, newAddressIds), TransactionId(id)) + addressTransactions.put(addressIdWithFallback(addr, newAddressIds), TransactionId(nti.transaction.id())) } } diff --git a/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala b/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala index 99cbe6e296a..cc5566aca6d 100644 --- a/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala +++ b/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala @@ -81,7 +81,7 @@ class BlockchainUpdaterImpl( ngState .flatMap(_.totalDiffOf(id)) .map { case (_, diff, _, _, _) => - diff.transactions.values.toSeq.map(info => (TxMeta(Height(height), info.applied, info.spentComplexity), info.transaction)) + diff.transactions.toSeq.map(info => (TxMeta(Height(height), info.applied, info.spentComplexity), info.transaction)) } ) diff --git a/node/src/main/scala/com/wavesplatform/state/Diff.scala b/node/src/main/scala/com/wavesplatform/state/Diff.scala index 1f197676119..9b062c4e907 100755 --- a/node/src/main/scala/com/wavesplatform/state/Diff.scala +++ b/node/src/main/scala/com/wavesplatform/state/Diff.scala @@ -5,8 +5,9 @@ import cats.data.Ior import cats.implicits.{catsSyntaxSemigroup, toFlatMapOps, toFunctorOps} import cats.kernel.{Monoid, Semigroup} import cats.syntax.either.* +import com.google.common.hash.{BloomFilter, Funnels} import com.google.protobuf.ByteString -import com.wavesplatform.account.{Address, AddressOrAlias, Alias, PublicKey} +import com.wavesplatform.account.{Address, Alias, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.database.protobuf.EthereumTransactionMeta import com.wavesplatform.features.BlockchainFeatures @@ -19,7 +20,7 @@ import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.smart.InvokeTransaction import com.wavesplatform.transaction.{Asset, EthereumTransaction, Transaction} -import scala.collection.immutable.VectorMap +import scala.util.chaining.* case class LeaseBalance(in: Long, out: Long) { def combineF[F[_]: Monad](that: LeaseBalance)(implicit s: Summarizer[F]): F[LeaseBalance] = @@ -145,34 +146,54 @@ case class NewTransactionInfo(transaction: Transaction, affected: Set[Address], case class NewAssetInfo(static: AssetStaticInfo, dynamic: AssetInfo, volume: AssetVolumeInfo) -case class LeaseActionInfo(invokeId: ByteStr, dAppPublicKey: PublicKey, recipient: AddressOrAlias, amount: Long) - -case class Diff( - transactions: collection.Map[ByteStr, NewTransactionInfo] = VectorMap.empty, - portfolios: Map[Address, Portfolio] = Map.empty, - issuedAssets: Map[IssuedAsset, NewAssetInfo] = Map.empty, - updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]] = Map.empty, - aliases: Map[Alias, Address] = Map.empty, - orderFills: Map[ByteStr, VolumeAndFee] = Map.empty, - leaseState: Map[ByteStr, LeaseDetails] = Map.empty, - scripts: Map[Address, Option[AccountScriptInfo]] = Map.empty, - assetScripts: Map[IssuedAsset, Option[AssetScriptInfo]] = Map.empty, - accountData: Map[Address, AccountDataInfo] = Map.empty, - sponsorship: Map[IssuedAsset, Sponsorship] = Map.empty, - scriptsRun: Int = 0, - scriptsComplexity: Long = 0, - scriptResults: Map[ByteStr, InvokeScriptResult] = Map.empty, - ethereumTransactionMeta: Map[ByteStr, EthereumTransactionMeta] = Map.empty +case class Diff private ( + transactions: Vector[NewTransactionInfo], + portfolios: Map[Address, Portfolio], + issuedAssets: Map[IssuedAsset, NewAssetInfo], + updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]], + aliases: Map[Alias, Address], + orderFills: Map[ByteStr, VolumeAndFee], + leaseState: Map[ByteStr, LeaseDetails], + scripts: Map[Address, Option[AccountScriptInfo]], + assetScripts: Map[IssuedAsset, Option[AssetScriptInfo]], + accountData: Map[Address, AccountDataInfo], + sponsorship: Map[IssuedAsset, Sponsorship], + scriptsRun: Int, + scriptsComplexity: Long, + scriptResults: Map[ByteStr, InvokeScriptResult], + ethereumTransactionMeta: Map[ByteStr, EthereumTransactionMeta], + transactionFilter: Option[BloomFilter[Array[Byte]]] ) { @inline final def combineE(newer: Diff): Either[ValidationError, Diff] = combineF(newer).leftMap(GenericError(_)) + def transaction(txId: ByteStr): Option[NewTransactionInfo] = + if (transactions.nonEmpty && transactionFilter.exists(_.mightContain(txId.arr))) + transactions.find(_.transaction.id() == txId) + else None + + def withScriptsComplexity(newScriptsComplexity: Long): Diff = copy(scriptsComplexity = newScriptsComplexity) + + def withScriptResults(newScriptResults: Map[ByteStr, InvokeScriptResult]): Diff = copy(scriptResults = newScriptResults) + + def withScriptRuns(newScriptRuns: Int): Diff = copy(scriptsRun = newScriptRuns) + + def withPortfolios(newPortfolios: Map[Address, Portfolio]): Diff = copy(portfolios = newPortfolios) + def combineF(newer: Diff): Either[String, Diff] = Diff .combine(portfolios, newer.portfolios) - .map(portfolios => + .map { portfolios => + val newTransactions = if (transactions.isEmpty) newer.transactions else transactions ++ newer.transactions + val newFilter = transactionFilter match { + case Some(bf) => + newer.transactions.foreach(nti => bf.put(nti.transaction.id().arr)) + Some(bf) + case None => newer.transactionFilter + } + Diff( - transactions = transactions ++ newer.transactions, + transactions = newTransactions, portfolios = portfolios, issuedAssets = issuedAssets ++ newer.issuedAssets, updatedAssets = updatedAssets |+| newer.updatedAssets, @@ -186,12 +207,84 @@ case class Diff( scriptsRun = scriptsRun + newer.scriptsRun, scriptResults = scriptResults.combine(newer.scriptResults), scriptsComplexity = scriptsComplexity + newer.scriptsComplexity, - ethereumTransactionMeta = ethereumTransactionMeta ++ newer.ethereumTransactionMeta + ethereumTransactionMeta = ethereumTransactionMeta ++ newer.ethereumTransactionMeta, + transactionFilter = newFilter ) - ) + } } object Diff { + def apply( + portfolios: Map[Address, Portfolio] = Map.empty, + issuedAssets: Map[IssuedAsset, NewAssetInfo] = Map.empty, + updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]] = Map.empty, + aliases: Map[Alias, Address] = Map.empty, + orderFills: Map[ByteStr, VolumeAndFee] = Map.empty, + leaseState: Map[ByteStr, LeaseDetails] = Map.empty, + scripts: Map[Address, Option[AccountScriptInfo]] = Map.empty, + assetScripts: Map[IssuedAsset, Option[AssetScriptInfo]] = Map.empty, + accountData: Map[Address, AccountDataInfo] = Map.empty, + sponsorship: Map[IssuedAsset, Sponsorship] = Map.empty, + scriptsRun: Int = 0, + scriptsComplexity: Long = 0, + scriptResults: Map[ByteStr, InvokeScriptResult] = Map.empty, + ethereumTransactionMeta: Map[ByteStr, EthereumTransactionMeta] = Map.empty + ): Diff = + new Diff( + Vector.empty, + portfolios, + issuedAssets, + updatedAssets, + aliases, + orderFills, + leaseState, + scripts, + assetScripts, + accountData, + sponsorship, + scriptsRun, + scriptsComplexity, + scriptResults, + ethereumTransactionMeta, + None + ) + + def withTransactions( + nti: Vector[NewTransactionInfo], + portfolios: Map[Address, Portfolio] = Map.empty, + issuedAssets: Map[IssuedAsset, NewAssetInfo] = Map.empty, + updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]] = Map.empty, + aliases: Map[Alias, Address] = Map.empty, + orderFills: Map[ByteStr, VolumeAndFee] = Map.empty, + leaseState: Map[ByteStr, LeaseDetails] = Map.empty, + scripts: Map[Address, Option[AccountScriptInfo]] = Map.empty, + assetScripts: Map[IssuedAsset, Option[AssetScriptInfo]] = Map.empty, + accountData: Map[Address, AccountDataInfo] = Map.empty, + sponsorship: Map[IssuedAsset, Sponsorship] = Map.empty, + scriptsRun: Int = 0, + scriptsComplexity: Long = 0, + scriptResults: Map[ByteStr, InvokeScriptResult] = Map.empty, + ethereumTransactionMeta: Map[ByteStr, EthereumTransactionMeta] = Map.empty + ): Diff = + new Diff( + nti, + portfolios, + issuedAssets, + updatedAssets, + aliases, + orderFills, + leaseState, + scripts, + assetScripts, + accountData, + sponsorship, + scriptsRun, + scriptsComplexity, + scriptResults, + ethereumTransactionMeta, + mkFilterForTransactions(nti.map(_.transaction)*) + ) + val empty: Diff = Diff() def combine(portfolios1: Map[Address, Portfolio], portfolios2: Map[Address, Portfolio]): Either[String, Map[Address, Portfolio]] = @@ -211,6 +304,17 @@ object Diff { case (r, _) => r } + private def mkFilter() = + BloomFilter.create[Array[Byte]](Funnels.byteArrayFunnel(), 10000, 0.01f) + private def mkFilterForTransactions(tx: Transaction*) = + Some( + mkFilter().tap(bf => + tx.foreach { t => + bf.put(t.id().arr) + } + ) + ) + implicit class DiffExt(private val d: Diff) extends AnyVal { def errorMessage(txId: ByteStr): Option[InvokeScriptResult.ErrorMessage] = d.scriptResults.get(txId).flatMap(_.error) @@ -229,13 +333,17 @@ object Diff { case et: EthereumTransaction => et.payload match { case EthereumTransaction.Invocation(dApp, _) => Some(dApp) - case _ => None + case _ => None } case _ => None } val affectedAddresses = d.portfolios.keySet ++ d.accountData.keySet ++ calledScripts ++ maybeDApp - d.copy(transactions = VectorMap(tx.id() -> NewTransactionInfo(tx, affectedAddresses, applied, d.scriptsComplexity))) + + d.copy( + transactions = Vector(NewTransactionInfo(tx, affectedAddresses, applied, d.scriptsComplexity)), + transactionFilter = mkFilterForTransactions(tx) + ) } } } diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/EthereumTransactionDiff.scala b/node/src/main/scala/com/wavesplatform/state/diffs/EthereumTransactionDiff.scala index bc09dd21e30..c705c55f7e1 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/EthereumTransactionDiff.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/EthereumTransactionDiff.scala @@ -15,7 +15,7 @@ object EthereumTransactionDiff { val resultEi = e.payload match { case et: EthereumTransaction.Transfer => for (assetId <- et.tryResolveAsset(blockchain)) - yield Diff.empty.copy( + yield Diff( ethereumTransactionMeta = Map( e.id() -> EthereumTransactionMeta( EthereumTransactionMeta.Payload.Transfer( @@ -31,7 +31,7 @@ object EthereumTransactionDiff { case ei: EthereumTransaction.Invocation => for { invocation <- ei.toInvokeScriptLike(e, blockchain) - } yield Diff.empty.copy( + } yield Diff( ethereumTransactionMeta = Map( e.id() -> EthereumTransactionMeta( EthereumTransactionMeta.Payload.Invocation( diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/LeaseTransactionsDiff.scala b/node/src/main/scala/com/wavesplatform/state/diffs/LeaseTransactionsDiff.scala index 3b419cc0867..54a31e1a501 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/LeaseTransactionsDiff.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/LeaseTransactionsDiff.scala @@ -1,17 +1,17 @@ package com.wavesplatform.state.diffs import com.wavesplatform.lang.ValidationError -import com.wavesplatform.state._ -import com.wavesplatform.transaction.lease._ +import com.wavesplatform.state.* +import com.wavesplatform.transaction.lease.* object LeaseTransactionsDiff { def lease(blockchain: Blockchain)(tx: LeaseTransaction): Either[ValidationError, Diff] = DiffsCommon .processLease(blockchain, tx.amount.value, tx.sender, tx.recipient, tx.fee.value, tx.id(), tx.id()) - .map(_.copy(scriptsRun = DiffsCommon.countScriptRuns(blockchain, tx))) + .map(_.withScriptRuns(DiffsCommon.countScriptRuns(blockchain, tx))) def leaseCancel(blockchain: Blockchain, time: Long)(tx: LeaseCancelTransaction): Either[ValidationError, Diff] = DiffsCommon .processLeaseCancel(blockchain, tx.sender, tx.fee.value, time, tx.leaseId, tx.id()) - .map(_.copy(scriptsRun = DiffsCommon.countScriptRuns(blockchain, tx))) + .map(_.withScriptRuns(DiffsCommon.countScriptRuns(blockchain, tx))) } diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/TransactionDiffer.scala b/node/src/main/scala/com/wavesplatform/state/diffs/TransactionDiffer.scala index 3a8e9633892..80125f52d94 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/TransactionDiffer.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/TransactionDiffer.scala @@ -26,8 +26,6 @@ import com.wavesplatform.transaction.smart.script.trace.{TraceStep, TracedResult import com.wavesplatform.transaction.transfer.{MassTransferTransaction, TransferTransaction} import play.api.libs.json.Json -import scala.collection.immutable.VectorMap - object TransactionDiffer { def apply(prevBlockTs: Option[Long], currentBlockTs: Long, verify: Boolean = true)( blockchain: Blockchain, @@ -298,8 +296,8 @@ object TransactionDiffer { case e: EthereumTransaction => EthereumTransactionDiff.meta(blockchain)(e) case _ => Diff.empty } - Diff( - transactions = VectorMap((tx.id(), NewTransactionInfo(tx, affectedAddresses, applied = false, spentComplexity))), + Diff.withTransactions( + Vector(NewTransactionInfo(tx, affectedAddresses, applied = false, spentComplexity)), portfolios = portfolios, scriptResults = scriptResult.fold(Map.empty[ByteStr, InvokeScriptResult])(sr => Map(tx.id() -> sr)), scriptsComplexity = spentComplexity diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/TransferDiff.scala b/node/src/main/scala/com/wavesplatform/state/diffs/TransferDiff.scala index 35ac40531d4..9bbd439a939 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/TransferDiff.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/TransferDiff.scala @@ -15,7 +15,7 @@ import scala.util.control.NonFatal object TransferTransactionDiff { def apply(blockchain: Blockchain)(tx: TransferTransaction): Either[ValidationError, Diff] = { TransferDiff(blockchain)(tx.sender.toAddress, tx.recipient, tx.amount.value, tx.assetId, tx.fee.value, tx.feeAssetId) - .map(_.copy(scriptsRun = DiffsCommon.countScriptRuns(blockchain, tx))) + .map(_.withScriptRuns(DiffsCommon.countScriptRuns(blockchain, tx))) } } diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeDiffsCommon.scala b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeDiffsCommon.scala index 3a699ca72fe..b06be2aa705 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeDiffsCommon.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeDiffsCommon.scala @@ -272,11 +272,10 @@ object InvokeDiffsCommon { leaseCancelList ) - resultDiff = compositeDiff.copy( - scriptsRun = if (isSyncCall) 0 else additionalScriptsCount + 1, - scriptResults = Map(tx.txId -> isr), - scriptsComplexity = storingComplexity + compositeDiff.scriptsComplexity - ) + resultDiff = compositeDiff + .withScriptRuns(if (isSyncCall) 0 else additionalScriptsCount + 1) + .withScriptResults(Map(tx.txId -> isr)) + .withScriptsComplexity(storingComplexity + compositeDiff.scriptsComplexity) } yield resultDiff } @@ -465,7 +464,10 @@ object InvokeDiffsCommon { case a @ IssuedAsset(id) => TracedResult( Diff - .combine(Map(address -> Portfolio(assets = Map(a -> amount))), Map(dAppAddress -> Portfolio(assets = Map(a -> -amount)))) + .combine( + Map(address -> Portfolio(assets = Map(a -> amount))), + Map(dAppAddress -> Portfolio(assets = Map(a -> -amount))) + ) .bimap(GenericError(_), p => Diff(portfolios = p)) ).flatMap(nextDiff => blockchain @@ -479,8 +481,8 @@ object InvokeDiffsCommon { val assetVerifierDiff = if (blockchain.disallowSelfPayment) nextDiff else - nextDiff.copy( - portfolios = Map( + nextDiff.withPortfolios( + Map( address -> Portfolio(assets = Map(a -> amount)), dAppAddress -> Portfolio(assets = Map(a -> -amount)) ) @@ -512,7 +514,7 @@ object InvokeDiffsCommon { } yield assetValidationDiff val errorOpt = assetValidationDiff.fold(Some(_), _ => None) TracedResult( - assetValidationDiff.map(d => nextDiff.copy(scriptsComplexity = d.scriptsComplexity)), + assetValidationDiff.map(d => nextDiff.withScriptsComplexity(d.scriptsComplexity)), List(AssetVerifierTrace(id, errorOpt, AssetContext.Transfer)) ) } @@ -682,7 +684,7 @@ object InvokeDiffsCommon { result match { case Left(error) => Left(FailedTransactionError.assetExecutionInAction(error.message, complexity, log, assetId)) case Right(FALSE) => Left(FailedTransactionError.notAllowedByAssetInAction(complexity, log, assetId)) - case Right(TRUE) => Right(nextDiff.copy(scriptsComplexity = nextDiff.scriptsComplexity + complexity)) + case Right(TRUE) => Right(nextDiff.withScriptsComplexity(nextDiff.scriptsComplexity + complexity)) case Right(x) => Left(FailedTransactionError.assetExecutionInAction(s"Script returned not a boolean result, but $x", complexity, log, assetId)) } @@ -725,7 +727,6 @@ object InvokeDiffsCommon { blockchain.isFeatureActivated( SynchronousCalls ) && blockchain.height >= blockchain.settings.functionalitySettings.enforceTransferValidationAfter - ) { TracedResult(Left(FailOrRejectError(message))) } else diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScript.scala b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScript.scala index dbd79b4c2aa..55b045ce3fb 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScript.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScript.scala @@ -1,14 +1,12 @@ package com.wavesplatform.state.diffs.invoke -import com.wavesplatform.account._ +import com.wavesplatform.account.* import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.lang.v1.compiler.Terms._ +import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.transaction.Asset.IssuedAsset -import com.wavesplatform.transaction.serialization.impl.InvokeScriptTxSerializer import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.{Authorized, TransactionBase, TxTimestamp} -import play.api.libs.json.{JsObject, Json} trait InvokeScriptLike { def dApp: AddressOrAlias @@ -32,12 +30,6 @@ object InvokeScriptLike { def txId: ByteStr = isl.root.id() def timestamp: TxTimestamp = isl.root.timestamp - - def toJson: JsObject = - Json.obj( - "dApp" -> isl.dApp.toString, - "payment" -> isl.payments - ) ++ Json.obj("call" -> InvokeScriptTxSerializer.functionCallToJson(isl.funcCall)) } val IssuedAssets: PartialFunction[Payment, IssuedAsset] = { case Payment(_, assetId: IssuedAsset) => assetId } diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptDiff.scala b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptDiff.scala index 9657346f16a..ca1ce4ab41d 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptDiff.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptDiff.scala @@ -367,7 +367,7 @@ object InvokeScriptDiff { } resultDiff <- traced( diff - .copy(scriptsComplexity = 0) + .withScriptsComplexity(0) .combineE(actionsDiff) .flatMap(_.combineE(Diff(scriptsComplexity = paymentsComplexity))) ) diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptTransactionDiff.scala b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptTransactionDiff.scala index 0952ca3f6f5..50f680ae82b 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptTransactionDiff.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeScriptTransactionDiff.scala @@ -41,8 +41,6 @@ import com.wavesplatform.transaction.validation.impl.DataTxValidator import monix.eval.Coeval import shapeless.Coproduct -import scala.util.Right - object InvokeScriptTransactionDiff { private[this] def allIssues(r: InvokeScriptResult): Seq[Issue] = { @@ -222,7 +220,7 @@ object InvokeScriptTransactionDiff { case i: IncompleteResult => TracedResult(Left(GenericError(s"Evaluation was uncompleted with unused complexity = ${i.unusedComplexity}"))) } - totalDiff <- TracedResult(invocationDiff.copy(scriptsComplexity = 0).combineF(resultDiff)).leftMap(GenericError(_)) + totalDiff <- TracedResult(invocationDiff.withScriptsComplexity(0).combineF(resultDiff)).leftMap(GenericError(_)) } yield totalDiff } diff --git a/node/src/main/scala/com/wavesplatform/state/reader/CompositeBlockchain.scala b/node/src/main/scala/com/wavesplatform/state/reader/CompositeBlockchain.scala index d2705775779..2dfe7c09c68 100644 --- a/node/src/main/scala/com/wavesplatform/state/reader/CompositeBlockchain.scala +++ b/node/src/main/scala/com/wavesplatform/state/reader/CompositeBlockchain.scala @@ -53,22 +53,19 @@ final class CompositeBlockchain private ( .orElse(diff.leaseState.get(leaseId)) override def transferById(id: ByteStr): Option[(Int, TransferTransactionLike)] = - diff.transactions - .get(id) + diff.transaction(id) .collect { case NewTransactionInfo(tx: TransferTransaction, _, true, _) => (height, tx) } .orElse(inner.transferById(id)) override def transactionInfo(id: ByteStr): Option[(TxMeta, Transaction)] = - diff.transactions - .get(id) + diff.transaction(id) .map(t => (TxMeta(Height(this.height), t.applied, t.spentComplexity), t.transaction)) .orElse(inner.transactionInfo(id)) override def transactionMeta(id: ByteStr): Option[TxMeta] = - diff.transactions - .get(id) + diff.transaction(id) .map(t => TxMeta(Height(this.height), t.applied, t.spentComplexity)) .orElse(inner.transactionMeta(id)) @@ -80,7 +77,7 @@ final class CompositeBlockchain private ( case Left(_) => diff.aliases.get(alias).toRight(AliasDoesNotExist(alias)) } - override def containsTransaction(tx: Transaction): Boolean = diff.transactions.contains(tx.id()) || inner.containsTransaction(tx) + override def containsTransaction(tx: Transaction): Boolean = diff.transaction(tx.id()).isDefined || inner.containsTransaction(tx) override def filledVolumeAndFee(orderId: ByteStr): VolumeAndFee = diff.orderFills.get(orderId).orEmpty.combine(inner.filledVolumeAndFee(orderId)) diff --git a/node/src/main/scala/com/wavesplatform/transaction/ABIConverter.scala b/node/src/main/scala/com/wavesplatform/transaction/ABIConverter.scala index b7debb09bc3..25fe7e6e7c6 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/ABIConverter.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/ABIConverter.scala @@ -1,24 +1,26 @@ package com.wavesplatform.transaction +import cats.instances.either.* +import cats.instances.vector.* import cats.syntax.either.* +import cats.syntax.traverse.* import com.esaulpaugh.headlong.abi.{Function, Tuple} import com.esaulpaugh.headlong.util.FastHex import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.lang.Global import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms.{EVALUATED, FUNCTION_CALL} -import com.wavesplatform.lang.v1.compiler.{Terms, Types} import com.wavesplatform.lang.v1.compiler.Types.TypeExt +import com.wavesplatform.lang.v1.compiler.{Terms, Types} +import com.wavesplatform.lang.{Global, ValidationError} import com.wavesplatform.transaction.ABIConverter.WavesByteRepr +import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.smart.InvokeScriptTransaction import org.web3j.abi.TypeReference import org.web3j.abi.datatypes.Type import play.api.libs.json.{JsArray, JsObject, JsString, Json} import scala.jdk.CollectionConverters.* -import scala.util.Try object ABIConverter { val WavesByteRepr: ByteStr = ByteStr(new Array[Byte](32)) @@ -73,22 +75,22 @@ object ABIConverter { } def ethFuncSignatureTypeName(argType: Types.FINAL): String = argType match { - case Types.BOOLEAN => "bool" - case Types.LONG => "int64" - case Types.BYTESTR => "bytes" - case Types.STRING => "string" - case Types.LIST(innerType) => s"${ethFuncSignatureTypeName(innerType)}[]" + case Types.BOOLEAN => "bool" + case Types.LONG => "int64" + case Types.BYTESTR => "bytes" + case Types.STRING => "string" + case Types.LIST(innerType) => s"${ethFuncSignatureTypeName(innerType)}[]" case Types.UNION(tpe :: Nil, _) => ethFuncSignatureTypeName(tpe) - case Types.TUPLE(types) => s"(${types.map(ethFuncSignatureTypeName).mkString(",")})" - case other => throw new IllegalArgumentException(s"ethFuncSignatureTypeName: Unexpected type: $other") + case Types.TUPLE(types) => s"(${types.map(ethFuncSignatureTypeName).mkString(",")})" + case other => throw new IllegalArgumentException(s"ethFuncSignatureTypeName: Unexpected type: $other") } - def toRideValue(ethArg: Any, rideType: Types.FINAL): EVALUATED = ethArg match { - case bool: Boolean => Terms.CONST_BOOLEAN(bool) - case i: Int => Terms.CONST_LONG(i) - case l: Long => Terms.CONST_LONG(l) - case byteArr: Array[Byte] => Terms.CONST_BYTESTR(ByteStr(byteArr)).explicitGet() //FastHex.encodeToString(byteArr, 0, byteArr.length) - case str: String => Terms.CONST_STRING(str).explicitGet() + def toRideValue(ethArg: Any, rideType: Types.FINAL): Either[ValidationError, EVALUATED] = ethArg match { + case bool: Boolean => Terms.CONST_BOOLEAN(bool).asRight + case i: Int => Terms.CONST_LONG(i).asRight + case l: Long => Terms.CONST_LONG(l).asRight + case byteArr: Array[Byte] => Terms.CONST_BYTESTR(ByteStr(byteArr)).leftMap(ce => GenericError(ce.message)) + case str: String => Terms.CONST_STRING(str).leftMap(ce => GenericError(ce.message)) case arr: Array[?] => val innerType = rideType match { @@ -97,20 +99,18 @@ object ABIConverter { case _ => Types.ANY } - Terms - .ARR( - arr.toVector.map(el => toRideValue(el, innerType)), - limited = true - ) - .explicitGet() + arr.toVector + .traverse(el => toRideValue(el, innerType)) + .flatMap[ValidationError, EVALUATED] { validArgs => + Terms.ARR(validArgs, limited = true).leftMap(ee => GenericError(ee.message)) + } case t: Tuple => - Terms - .ARR( - t.asScala.toVector.map(el => toRideValue(el, Types.ANY)), - limited = true - ) - .explicitGet() + t.asScala.toVector + .traverse(el => toRideValue(el, Types.ANY)) + .flatMap[ValidationError, EVALUATED] { validArgs => + Terms.ARR(validArgs, limited = true).leftMap(ee => GenericError(ee.message)) + } case _ => throw new UnsupportedOperationException(s"Type not supported: $ethArg") } @@ -118,39 +118,46 @@ object ABIConverter { final case class ABIConverter(script: Script) { case class FunctionArg(name: String, rideType: Types.FINAL) { - lazy val ethType: String = ABIConverter.ethType(rideType) + lazy val ethType: String = ABIConverter.ethType(rideType) + def ethTypeRef: TypeReference[Type[?]] = TypeReference.makeTypeReference(ethType).asInstanceOf[TypeReference[Type[?]]] } case class FunctionRef(name: String, args: Seq[FunctionArg]) { - def decodeArgs(data: String): (List[EVALUATED], Seq[InvokeScriptTransaction.Payment]) = { - val ethFunc = new Function(ethSignature) - val ethArgsList = ethFunc.decodeCall(FastHex.decode(data)).asScala.toList - - val result = - ethArgsList.zip(args.map(_.rideType) :+ ABIConverter.PaymentListType).map { case (ethArg, rideT) => ABIConverter.toRideValue(ethArg, rideT) } - - val payment = result.last match { - case Terms.ARR(xs) => - xs.map { - case Terms.ARR(fields) => - fields match { - case Seq(Terms.CONST_BYTESTR(assetId), Terms.CONST_LONG(amount)) => - InvokeScriptTransaction.Payment(amount, assetId match { - case `WavesByteRepr` => Asset.Waves - case assetId => Asset.IssuedAsset(assetId) - }) - - case other => throw new IllegalArgumentException(s"decodeArgs: unexpected term in payment: $other") + def decodeArgs(data: String): Either[ValidationError, (List[EVALUATED], Seq[InvokeScriptTransaction.Payment])] = + new Function(ethSignature) + .decodeCall(FastHex.decode(data)) + .asScala + .toList + .zip(args.map(_.rideType) :+ ABIConverter.PaymentListType) + .traverse { case (ethArg, rideT) => ABIConverter.toRideValue(ethArg, rideT) } + .flatMap { alldecodedArgs => + (alldecodedArgs.last match { + case Terms.ARR(xs) => + xs.toVector.traverse { + case Terms.ARR(fields) => + fields match { + case Seq(Terms.CONST_BYTESTR(assetId), Terms.CONST_LONG(amount)) if amount > 0 => + Right( + InvokeScriptTransaction.Payment( + amount, + assetId match { + case WavesByteRepr => Asset.Waves + case assetId => Asset.IssuedAsset(assetId) + } + ) + ) + + case other => Left(GenericError(s"decodeArgs: unexpected term in payment: $other")) + } + case other => Left(GenericError(s"decodeArgs: unexpected term in payment: $other")) } - case other => throw new IllegalArgumentException(s"decodeArgs: unexpected term in payment: $other") - } - case _ => Nil - } - (result.init, payment) - } + case _ => Right(Nil) + }).map(ps => (alldecodedArgs.init, ps)) + + } lazy val ethSignature: String = { val argTypes = args.map(_.rideType).map(ABIConverter.ethFuncSignatureTypeName) :+ ABIConverter.PaymentArgSignature @@ -161,9 +168,10 @@ final case class ABIConverter(script: Script) { } private[this] lazy val funcsWithTypes = - Global.dAppFuncTypes(script) + Global + .dAppFuncTypes(script) .map { signatures => - val filtered = signatures.argsWithFuncName.filter { case (_, args) => + val filtered = signatures.argsWithFuncName.filter { case (_, args) => !args.exists { case (_, tpe) => tpe.containsUnion } } signatures.copy(argsWithFuncName = filtered) @@ -178,38 +186,34 @@ final case class ABIConverter(script: Script) { lazy val funcByMethodId: Map[String, FunctionRef] = functionsWithArgs - .map { - case (funcName, args) => - FunctionRef(funcName, args.map { case (name, argType) => FunctionArg(name, argType) }) + .map { case (funcName, args) => + FunctionRef(funcName, args.map { case (name, argType) => FunctionArg(name, argType) }) } .map(func => func.ethMethodId -> func) .toMap def jsonABI: JsArray = - JsArray(functionsWithArgs.map { - case (funcName, args) => - val inputs = args.map { - case (argName, argType) => - Json.obj("name" -> argName) ++ ABIConverter.ethTypeObj(argType) - } :+ ABIConverter.PaymentArgJson - - Json.obj( - "name" -> funcName, - "type" -> "function", - "constant" -> false, - "payable" -> false, - "stateMutability" -> "nonpayable", - "inputs" -> inputs, - "outputs" -> JsArray.empty - ) + JsArray(functionsWithArgs.map { case (funcName, args) => + val inputs = args.map { case (argName, argType) => + Json.obj("name" -> argName) ++ ABIConverter.ethTypeObj(argType) + } :+ ABIConverter.PaymentArgJson + + Json.obj( + "name" -> funcName, + "type" -> "function", + "constant" -> false, + "payable" -> false, + "stateMutability" -> "nonpayable", + "inputs" -> inputs, + "outputs" -> JsArray.empty + ) }) - def decodeFunctionCall(data: String): Either[String, (FUNCTION_CALL, Seq[InvokeScriptTransaction.Payment])] = - Try { - val methodId = data.substring(0, 8) - val function = funcByMethodId.getOrElse("0x" + methodId, throw new NoSuchElementException(s"Function not defined: $methodId")) - val (args, payment) = function.decodeArgs(data) - (FUNCTION_CALL(FunctionHeader.User(function.name), args), payment) - }.toEither - .leftMap(_.getMessage) + def decodeFunctionCall(data: String): Either[ValidationError, (FUNCTION_CALL, Seq[InvokeScriptTransaction.Payment])] = { + val methodId = data.substring(0, 8) + for { + function <- funcByMethodId.get("0x" + methodId).toRight[ValidationError](GenericError(s"Function not defined: $methodId")) + argsAndPayments <- function.decodeArgs(data) + } yield (FUNCTION_CALL(FunctionHeader.User(function.name), argsAndPayments._1), argsAndPayments._2) + } } diff --git a/node/src/main/scala/com/wavesplatform/transaction/EthereumTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/EthereumTransaction.scala index c5eb8fe4fc3..474f95f9c22 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/EthereumTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/EthereumTransaction.scala @@ -2,7 +2,6 @@ package com.wavesplatform.transaction import java.math.BigInteger -import cats.syntax.either.* import com.wavesplatform.account.* import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto.EthereumKeyLength @@ -118,7 +117,6 @@ object EthereumTransaction { blockchain.accountScript(dApp).toRight(GenericError(s"No script at address $dApp")).flatMap { scriptInfo => ABIConverter(scriptInfo.script) .decodeFunctionCall(hexCallData) - .leftMap(GenericError(_)) .map { case (extractedCall, extractedPayments) => new InvokeScriptTransactionLike { override def funcCall: Terms.FUNCTION_CALL = extractedCall diff --git a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/InvokeScriptTxSerializer.scala b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/InvokeScriptTxSerializer.scala index e36788b5cf7..1e20830e51f 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/InvokeScriptTxSerializer.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/InvokeScriptTxSerializer.scala @@ -1,6 +1,7 @@ package com.wavesplatform.transaction.serialization.impl import java.nio.ByteBuffer + import com.google.common.primitives.{Bytes, Longs} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.* @@ -34,11 +35,18 @@ object InvokeScriptTxSerializer { case Terms.CONST_BOOLEAN(bool) => Json.obj("type" -> "boolean", "value" -> bool) case Terms.CONST_BYTESTR(bytes) => Json.obj("type" -> "binary", "value" -> bytes.base64) case Terms.CONST_STRING(str) => Json.obj("type" -> "string", "value" -> str) - case Terms.ARR(_) => Json.obj("type" -> "list", "value" -> "unsupported") // should not be shown on normal cases, added only to avoid NotImplementedError while constructing error for illegal callable argument type - case arg => throw new NotImplementedError(s"Not supported: $arg") + case Terms.ARR(_) => + Json.obj( + "type" -> "list", + "value" -> "unsupported" + ) // should not be shown on normal cases, added only to avoid NotImplementedError while constructing error for illegal callable argument type + case arg => throw new NotImplementedError(s"Not supported: $arg") } - def toJson(tx: InvokeScriptTransaction): JsObject = BaseTxJson.toJson(tx) ++ tx.toJson + def toJson(tx: InvokeScriptTransaction): JsObject = BaseTxJson.toJson(tx) ++ Json.obj( + "dApp" -> tx.dApp.toString, + "payment" -> tx.payments + ) ++ Json.obj("call" -> InvokeScriptTxSerializer.functionCallToJson(tx.funcCall)) def bodyBytes(tx: InvokeScriptTransaction): Array[Byte] = { import tx.* diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala index ed82c801931..74144d3884a 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala @@ -384,10 +384,9 @@ class DAppEnvironment( if (reentrant) calledAddresses else calledAddresses + invoke.sender.toAddress, invocationTracker )(invoke) - fixedDiff = diff.copy( - scriptResults = Map(txId -> InvokeScriptResult(invokes = Seq(invocation.copy(stateChanges = diff.scriptResults(txId))))), - scriptsRun = diff.scriptsRun + 1 - ) + fixedDiff = diff + .withScriptResults(Map(txId -> InvokeScriptResult(invokes = Seq(invocation.copy(stateChanges = diff.scriptResults(txId)))))) + .withScriptRuns(diff.scriptsRun + 1) newCurrentDiff <- traced(currentDiff.combineF(fixedDiff).leftMap(GenericError(_))) } yield { currentDiff = newCurrentDiff diff --git a/node/src/main/scala/com/wavesplatform/utx/UtxPriorityPool.scala b/node/src/main/scala/com/wavesplatform/utx/UtxPriorityPool.scala index 38a74612d54..a3fa621ae4e 100644 --- a/node/src/main/scala/com/wavesplatform/utx/UtxPriorityPool.scala +++ b/node/src/main/scala/com/wavesplatform/utx/UtxPriorityPool.scala @@ -22,7 +22,7 @@ final class UtxPriorityPool(realBlockchain: Blockchain) extends ScorexLogging wi @volatile private[this] var priorityDiffs = Seq.empty[PriorityData] @volatile private[this] var priorityDiffsCombined = Diff.empty - def validPriorityDiffs: Seq[Diff] = priorityDiffs.collect { case PriorityData(diff, true) => diff } + def validPriorityDiffs: Seq[Diff] = priorityDiffs.takeWhile(_.isValid).map(_.diff) def priorityTransactions: Seq[Transaction] = priorityDiffs.flatMap(_.diff.transactionsValues) def priorityTransactionIds: Seq[ByteStr] = priorityTransactions.map(_.id()) @@ -51,8 +51,8 @@ final class UtxPriorityPool(realBlockchain: Blockchain) extends ScorexLogging wi private[utx] def invalidateTxs(removed: Set[ByteStr]): Unit = updateDiffs(_.map { pd => if (pd.diff.transactionIds.exists(removed)) { - val keep = pd.diff.transactions.filterNot { case (id, _) => removed(id) } - pd.copy(pd.diff.copy(keep), isValid = false) + val keep = pd.diff.transactions.filterNot(nti => removed(nti.transaction.id())) + pd.copy(Diff.withTransactions(keep), isValid = false) } else pd }) @@ -87,7 +87,7 @@ final class UtxPriorityPool(realBlockchain: Blockchain) extends ScorexLogging wi } def transactionById(txId: ByteStr): Option[Transaction] = - priorityDiffsCombined.transactions.get(txId).map(_.transaction) + priorityDiffsCombined.transaction(txId).map(_.transaction) def contains(txId: ByteStr): Boolean = transactionById(txId).nonEmpty @@ -148,8 +148,8 @@ final class UtxPriorityPool(realBlockchain: Blockchain) extends ScorexLogging wi private object UtxPriorityPool { implicit class DiffExt(private val diff: Diff) extends AnyVal { - def contains(txId: ByteStr): Boolean = diff.transactions.contains(txId) - def transactionsValues: Seq[Transaction] = diff.transactions.values.map(_.transaction).toVector - def transactionIds: collection.Set[ByteStr] = diff.transactions.keySet + def contains(txId: ByteStr): Boolean = diff.transaction(txId).isDefined + def transactionsValues: Seq[Transaction] = diff.transactions.map(_.transaction) + def transactionIds: collection.Set[ByteStr] = transactionsValues.map(_.id()).toSet } } diff --git a/node/src/test/scala/com/wavesplatform/state/BlockchainUpdaterImplSpec.scala b/node/src/test/scala/com/wavesplatform/state/BlockchainUpdaterImplSpec.scala index 8f096aa9a73..754647c48da 100644 --- a/node/src/test/scala/com/wavesplatform/state/BlockchainUpdaterImplSpec.scala +++ b/node/src/test/scala/com/wavesplatform/state/BlockchainUpdaterImplSpec.scala @@ -109,7 +109,7 @@ class BlockchainUpdaterImplSpec extends FreeSpec with EitherMatchers with WithDo (triggersMock.onProcessBlock _) .expects(where { (block, diff, _, bc) => val txDiff = diff.transactionDiffs.head - val tx = txDiff.transactions.head._2.transaction.asInstanceOf[TransferTransaction] + val tx = txDiff.transactions.head.transaction.asInstanceOf[TransferTransaction] bc.height == 1 && block.transactionData.length == 5 && diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/AssetTransactionsDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/AssetTransactionsDiffTest.scala index 15833353864..ef22877b426 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/AssetTransactionsDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/AssetTransactionsDiffTest.scala @@ -249,7 +249,7 @@ class AssetTransactionsDiffTest extends PropSpec with BlocksTransactionsHelpers issue.decimals.value == 0 && issue.quantity.value == 1 && !issue.reissuable ) ) - blockDiff.transactions.contains(issue.id()) shouldBe true + blockDiff.transaction(issue.id()) shouldBe defined newState.transactionInfo(issue.id()).isDefined shouldBe true newState.transactionInfo(issue.id()).isDefined shouldEqual true } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeAffectedAddressTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeAffectedAddressTest.scala index 0b4fccc40e0..bf63d1d8991 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeAffectedAddressTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeAffectedAddressTest.scala @@ -28,7 +28,7 @@ class InvokeAffectedAddressTest extends PropSpec with WithDomain { d.appendAndAssertFailed(invoke(secondAddress)) else d.appendAndAssertSucceed(invoke(secondAddress)) - d.liquidDiff.transactions.head._2.affected shouldBe Set(defaultAddress, secondAddress) + d.liquidDiff.transactions.head.affected shouldBe Set(defaultAddress, secondAddress) } } } @@ -42,7 +42,7 @@ class InvokeAffectedAddressTest extends PropSpec with WithDomain { d.appendAndAssertFailed(invoke(Alias.create("alias").explicitGet())) else d.appendAndAssertSucceed(invoke(Alias.create("alias").explicitGet())) - d.liquidDiff.transactions.head._2.affected shouldBe Set(defaultAddress, secondAddress) + d.liquidDiff.transactions.head.affected shouldBe Set(defaultAddress, secondAddress) } } } @@ -58,7 +58,7 @@ class InvokeAffectedAddressTest extends PropSpec with WithDomain { d.liquidDiff.errorMessage(invokeTx.id()) shouldBe defined } else d.appendAndAssertSucceed(aliasTx, invokeTx) - d.liquidDiff.transactions(invokeTx.id()).affected shouldBe Set(defaultAddress, secondAddress) + d.liquidDiff.transaction(invokeTx.id()).get.affected shouldBe Set(defaultAddress, secondAddress) } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeAssetChecksTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeAssetChecksTest.scala index 2e099bb7126..3a89f945ec2 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeAssetChecksTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeAssetChecksTest.scala @@ -16,8 +16,6 @@ import com.wavesplatform.transaction.TxHelpers import com.wavesplatform.transaction.TxHelpers.{invoke, secondSigner, setScript} import org.scalatest.{EitherValues, Inside} -import scala.collection.immutable.VectorMap - class InvokeAssetChecksTest extends PropSpec with Inside with WithState with DBCacheSettings with WithDomain with EitherValues { import DomainPresets.* @@ -60,8 +58,8 @@ class InvokeAssetChecksTest extends PropSpec with Inside with WithState with DBC val dAppAddress = master.toAddress - def invokeInfo(succeeded: Boolean): VectorMap[ByteStr, NewTransactionInfo] = - VectorMap(invoke.id() -> NewTransactionInfo(invoke, Set(invoke.senderAddress, dAppAddress), succeeded, if (!succeeded) 8L else 18L)) + def invokeInfo(succeeded: Boolean): Vector[NewTransactionInfo] = + Vector(NewTransactionInfo(invoke, Set(invoke.senderAddress, dAppAddress), succeeded, if (!succeeded) 8L else 18L)) val expectedResult = if (activated) { @@ -70,8 +68,8 @@ class InvokeAssetChecksTest extends PropSpec with Inside with WithState with DBC lengthError else nonExistentError - Diff( - transactions = invokeInfo(false), + Diff.withTransactions( + invokeInfo(false), portfolios = Map( invoke.senderAddress -> Portfolio(-invoke.fee.value), miner -> Portfolio((setScriptTx.fee.value * 0.6 + invoke.fee.value * 0.4).toLong + 6.waves) @@ -81,8 +79,8 @@ class InvokeAssetChecksTest extends PropSpec with Inside with WithState with DBC ) } else { val asset = if (func == "invalidLength") invalidLengthAsset else nonExistentAsset - Diff( - transactions = invokeInfo(true), + Diff.withTransactions( + invokeInfo(true), portfolios = Map( invoke.senderAddress -> Portfolio(-invoke.fee.value, assets = Map(asset -> 0)), dAppAddress -> Portfolio.build(asset, 0), diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeScriptTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeScriptTransactionDiffTest.scala index 68aab172094..30ac5e4c9fd 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeScriptTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeScriptTransactionDiffTest.scala @@ -494,7 +494,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa newState.accountData(dAppAddress, "sender").get.value shouldBe ByteStr(ci.sender.toAddress.bytes) newState.accountData(dAppAddress, "argument").get.value shouldBe ci.funcCallOpt.get.args.head.asInstanceOf[CONST_BYTESTR].bs - blockDiff.transactions(ci.id()).affected.contains(setScript.sender.toAddress) shouldBe true + blockDiff.transaction(ci.id()).get.affected.contains(setScript.sender.toAddress) shouldBe true } } @@ -530,7 +530,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa blockDiff.scriptsRun shouldBe 1 blockDiff.portfolios(thirdAddress).balance shouldBe amount blockDiff.portfolios(setScript.sender.toAddress).balance shouldBe -amount - blockDiff.transactions should contain key ci.id() + blockDiff.transaction(ci.id()) shouldBe defined } } @@ -541,7 +541,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa blockDiff.scriptsRun shouldBe 1 blockDiff.portfolios(thirdAddress).balance shouldBe amount blockDiff.portfolios(setScript.sender.toAddress).balance shouldBe -amount - blockDiff.transactions should contain key ci.id() + blockDiff.transaction(ci.id()) shouldBe defined } } @@ -557,7 +557,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa ) { case (blockDiff, _) => blockDiff.scriptsRun shouldBe 1 blockDiff.portfolios(thirdAddress) shouldBe Portfolio.waves(amount) - blockDiff.transactions should contain key ci.id() + blockDiff.transaction(ci.id()) shouldBe defined } } @@ -608,7 +608,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa case (blockDiff, newState) => blockDiff.scriptsRun shouldBe 1 newState.balance(thirdAddress, Waves) shouldBe amount - blockDiff.transactions should contain key ci.id() + blockDiff.transaction(ci.id()) shouldBe defined } testDiff(Seq(TestBlock.create(genesis ++ Seq(setScript))), TestBlock.create(Seq(fakeCi), Block.ProtoBlockVersion)) { _ should produceRejectOrFailedDiff("does not exist") diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/MultiPaymentInvokeDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/MultiPaymentInvokeDiffTest.scala index a25fd513b4a..d95549969fa 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/MultiPaymentInvokeDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/MultiPaymentInvokeDiffTest.scala @@ -95,7 +95,7 @@ class MultiPaymentInvokeDiffTest extends PropSpec with WithState { TestBlock.create(Seq(ci)), features )(_ should matchPattern { - case Right(diff: Diff) if diff.transactions.exists(!_._2.applied) => + case Right(diff: Diff) if diff.transactions.exists(!_.applied) => }) } } @@ -264,7 +264,7 @@ class MultiPaymentInvokeDiffTest extends PropSpec with WithState { features ) { case Right(diff: Diff) => - val errMsg = diff.scriptResults(diff.transactions.keys.head).error.get.text + val errMsg = diff.scriptResults(diff.transactions.head.transaction.id()).error.get.text message(oldVersion.id, maybeFailedAssetId).r.findFirstIn(errMsg) shouldBe defined case l @ Left(_) => diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppTransferTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppTransferTest.scala index a5c5fc6cfcb..08808fc2856 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppTransferTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppTransferTest.scala @@ -126,7 +126,7 @@ class SyncDAppTransferTest extends PropSpec with WithDomain { blockDiff.scriptsRun shouldBe 2 blockDiff.portfolios(recipient.toAddress).balance shouldBe transferAmount blockDiff.portfolios(senderDApp.toAddress).balance shouldBe -transferAmount - blockDiff.transactions should contain key invoke.id() + blockDiff.transaction(invoke.id()) shouldBe defined } } @@ -151,7 +151,7 @@ class SyncDAppTransferTest extends PropSpec with WithDomain { blockDiff.scriptsRun shouldBe 2 blockDiff.portfolios(recipient.toAddress).balance shouldBe transferAmount blockDiff.portfolios(senderDApp.toAddress).balance shouldBe -transferAmount - blockDiff.transactions should contain key invoke.id() + blockDiff.transaction(invoke.id()) shouldBe defined } } @@ -195,7 +195,7 @@ class SyncDAppTransferTest extends PropSpec with WithDomain { blockDiff.scriptsRun shouldBe 2 blockDiff.portfolios(recipient.toAddress).balance shouldBe transferAmount blockDiff.portfolios(invokerDApp.toAddress).balance shouldBe -transferAmount - blockDiff.transactions should contain key invoke.id() + blockDiff.transaction(invoke.id()) shouldBe defined } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeValidationTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeValidationTest.scala index 9708f6fa459..33236224e19 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeValidationTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeValidationTest.scala @@ -228,7 +228,7 @@ class SyncInvokeValidationTest extends PropSpec with WithDomain { d.appendBlock(setScript(dApp1Signer, dApp1), setScript(dApp2Signer, dApp2), setScript(dApp3Signer, dApp3)) d.appendAndAssertSucceed(invoke(dApp1Address)) - d.liquidDiff.transactions.head._2.affected shouldBe Set(dApp1Address, dApp2Address, dApp3Address, defaultAddress) + d.liquidDiff.transactions.head.affected shouldBe Set(dApp1Address, dApp2Address, dApp3Address, defaultAddress) } } diff --git a/node/src/test/scala/com/wavesplatform/utils/DiffMatchers.scala b/node/src/test/scala/com/wavesplatform/utils/DiffMatchers.scala index d1a084859e2..967a5f87e1e 100644 --- a/node/src/test/scala/com/wavesplatform/utils/DiffMatchers.scala +++ b/node/src/test/scala/com/wavesplatform/utils/DiffMatchers.scala @@ -10,7 +10,7 @@ trait DiffMatchers { class DiffAppliedTxMatcher(transactionId: ByteStr, shouldBeApplied: Boolean) extends Matcher[Diff] { override def apply(diff: Diff): MatchResult = { - val isApplied = diff.transactions.get(transactionId) match { + val isApplied = diff.transaction(transactionId) match { case Some(nt) if nt.applied => true case _ => false }