diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/EvaluatorV2Benchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/EvaluatorV2Benchmark.scala index 85b60437dc..f9770b1630 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/EvaluatorV2Benchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/EvaluatorV2Benchmark.scala @@ -1,25 +1,24 @@ package com.wavesplatform.lang.v1 -import java.util.concurrent.TimeUnit -import cats.Id import com.wavesplatform.lang.Common import com.wavesplatform.lang.directives.values.{V1, V3} import com.wavesplatform.lang.v1.EvaluatorV2Benchmark.* import com.wavesplatform.lang.v1.compiler.Terms.{EXPR, IF, TRUE} import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 -import com.wavesplatform.lang.v1.evaluator.ctx.{DisabledLogEvaluationContext, EvaluationContext} +import com.wavesplatform.lang.v1.evaluator.ctx.DisabledLogEvaluationContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext import com.wavesplatform.lang.v1.traits.Environment import org.openjdk.jmh.annotations.* import org.openjdk.jmh.infra.Blackhole +import java.util.concurrent.TimeUnit import scala.annotation.tailrec object EvaluatorV2Benchmark { - val pureContext: CTX[Environment] = PureContext.build(V1, useNewPowPrecision = true).withEnvironment[Environment] - val pureEvalContext: EvaluationContext[Environment, Id] = pureContext.evaluationContext(Common.emptyBlockchainEnvironment()) - val evaluatorV2: EvaluatorV2 = new EvaluatorV2(DisabledLogEvaluationContext(pureEvalContext), V1, true, true, false) + val pureContext = PureContext.build(V1, useNewPowPrecision = true).withEnvironment[Environment] + val pureEvalContext = pureContext.evaluationContext(Common.emptyBlockchainEnvironment()) + val evaluatorV2 = new EvaluatorV2(DisabledLogEvaluationContext(pureEvalContext), V1, Int.MaxValue, true, false, true, true, true) } @OutputTimeUnit(TimeUnit.MILLISECONDS) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala index 05457b8df3..8c23d783ab 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala @@ -20,22 +20,22 @@ import org.openjdk.jmh.infra.Blackhole @Measurement(iterations = 10, time = 1) class FractionIntBenchmark { @Benchmark - def fraction1(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, LogExtraInfo(), V5, true, true, false)) + def fraction1(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, LogExtraInfo(), V5, true, true, false, true)) @Benchmark - def fraction2(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, LogExtraInfo(), V5, true, true, false)) + def fraction2(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, LogExtraInfo(), V5, true, true, false, true)) @Benchmark - def fraction3(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, LogExtraInfo(), V5, true, true, false)) + def fraction3(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, LogExtraInfo(), V5, true, true, false, true)) @Benchmark - def fraction1Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1Round, LogExtraInfo(), V5, true, true, false)) + def fraction1Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1Round, LogExtraInfo(), V5, true, true, false, true)) @Benchmark - def fraction2Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2Round, LogExtraInfo(), V5, true, true, false)) + def fraction2Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2Round, LogExtraInfo(), V5, true, true, false, true)) @Benchmark - def fraction3Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3Round, LogExtraInfo(), V5, true, true, false)) + def fraction3Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3Round, LogExtraInfo(), V5, true, true, false, true)) } @State(Scope.Benchmark) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala index 95367b95d8..eedd9024c0 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala @@ -31,5 +31,14 @@ package object v1 { expr: EXPR, stdLibVersion: StdLibVersion = StdLibVersion.VersionDic.all.max ): (Log[Id], Int, Either[ExecutionError, Terms.EVALUATED]) = - EvaluatorV2.applyCompleted(ctx, expr, LogExtraInfo(), stdLibVersion, newMode = true, correctFunctionCallScope = true, enableExecutionLog = false) + EvaluatorV2.applyCompleted( + ctx, + expr, + LogExtraInfo(), + stdLibVersion, + newMode = true, + correctFunctionCallScope = true, + enableExecutionLog = false, + fixedThrownError = true + ) } diff --git a/benchmark/src/test/scala/com/wavesplatform/state/StateSyntheticBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/state/StateSyntheticBenchmark.scala index 88bba79581..ec4989cb30 100644 --- a/benchmark/src/test/scala/com/wavesplatform/state/StateSyntheticBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/state/StateSyntheticBenchmark.scala @@ -67,7 +67,7 @@ object StateSyntheticBenchmark { val textScript = "sigVerify(tx.bodyBytes,tx.proofs[0],tx.senderPublicKey)" val untypedScript = Parser.parseExpr(textScript).get.value - val typedScript = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), untypedScript).explicitGet()._1 + val typedScript = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), V1, untypedScript).explicitGet()._1 val setScriptBlock = nextBlock( Seq( diff --git a/grpc-server/src/main/scala/com/wavesplatform/api/grpc/AccountsApiGrpcImpl.scala b/grpc-server/src/main/scala/com/wavesplatform/api/grpc/AccountsApiGrpcImpl.scala index 5e14d9a99d..fbcf7b3e76 100644 --- a/grpc-server/src/main/scala/com/wavesplatform/api/grpc/AccountsApiGrpcImpl.scala +++ b/grpc-server/src/main/scala/com/wavesplatform/api/grpc/AccountsApiGrpcImpl.scala @@ -63,8 +63,15 @@ class AccountsApiGrpcImpl(commonApi: CommonAccountsApi)(implicit sc: Scheduler) override def getScript(request: AccountRequest): Future[ScriptResponse] = Future { commonApi.script(request.address.toAddress()) match { - case Some(desc) => ScriptResponse(PBTransactions.toPBScript(Some(desc.script)), desc.script.expr.toString, desc.verifierComplexity, desc.publicKey.toByteString) - case None => ScriptResponse() + case Some(desc) => + ScriptResponse( + PBTransactions.toPBScript(Some(desc.script)), + desc.script.expr.toString, + desc.verifierComplexity, + desc.publicKey.toByteString + ) + case None => + ScriptResponse() } } 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 a83d09fdc2..62324eaaf1 100644 --- a/grpc-server/src/main/scala/com/wavesplatform/events/events.scala +++ b/grpc-server/src/main/scala/com/wavesplatform/events/events.scala @@ -3,7 +3,7 @@ package com.wavesplatform.events import cats.Monoid import cats.implicits.catsSyntaxSemigroup import com.google.protobuf.ByteString -import com.wavesplatform.account.{Address, AddressOrAlias, Alias, PublicKey} +import com.wavesplatform.account.{Address, AddressOrAlias, PublicKey} import com.wavesplatform.block.{Block, MicroBlock} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.* @@ -17,7 +17,6 @@ import com.wavesplatform.protobuf.transaction.InvokeScriptResult.Call.Argument import com.wavesplatform.protobuf.transaction.{PBAmounts, PBTransactions, InvokeScriptResult as PBInvokeScriptResult} import com.wavesplatform.state.* import com.wavesplatform.state.diffs.invoke.InvokeScriptTransactionLike -import com.wavesplatform.state.reader.SnapshotBlockchain import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.assets.exchange.ExchangeTransaction import com.wavesplatform.transaction.lease.LeaseTransaction @@ -386,9 +385,6 @@ object StateUpdate { } } - private lazy val WavesAlias = Alias.fromString("alias:W:waves", Some('W'.toByte)).explicitGet() - private lazy val WavesAddress = Address.fromString("3PGd1eQR8EhLkSogpmu9Ne7hSH1rQ5ALihd", Some('W'.toByte)).explicitGet() - def atomic(blockchainBeforeWithMinerReward: Blockchain, snapshot: StateSnapshot): StateUpdate = { val blockchain = blockchainBeforeWithMinerReward val blockchainAfter = SnapshotBlockchain(blockchain, snapshot) @@ -426,25 +422,37 @@ object StateUpdate { assetAfter = blockchainAfter.assetDescription(asset) } yield AssetStateUpdate(asset.id, assetBefore, assetAfter) - val updatedLeases = snapshot.leaseStates.map { case (leaseId, newState) => + val newLeaseUpdates = snapshot.newLeases.collect { + case (newId, staticInfo) if !snapshot.cancelledLeases.contains(newId) => + LeaseUpdate( + newId, + LeaseStatus.Active, + staticInfo.amount.value, + staticInfo.sender, + staticInfo.recipientAddress, + staticInfo.sourceId + ) + } + + val cancelledLeaseUpdates = snapshot.cancelledLeases.map { case (id, _) => + val si = snapshot.newLeases.get(id).orElse(blockchain.leaseDetails(id).map(_.static)) LeaseUpdate( - leaseId, - if (newState.isActive) LeaseStatus.Active else LeaseStatus.Inactive, - newState.amount, - newState.sender, - newState.recipient match { - case `WavesAlias` => WavesAddress - case other => blockchainAfter.resolveAlias(other).explicitGet() - }, - newState.sourceId + id, + LeaseStatus.Inactive, + si.fold(0L)(_.amount.value), + si.fold(PublicKey(new Array[Byte](32)))(_.sender), + si.fold(PublicKey(new Array[Byte](32)).toAddress)(_.recipientAddress), + si.fold(ByteStr.empty)(_.sourceId) ) - }.toVector + } + + val updatedLeases = newLeaseUpdates ++ cancelledLeaseUpdates val updatedScripts = snapshot.accountScriptsByAddress.map { case (address, newScript) => ScriptUpdate(ByteStr(address.bytes), blockchain.accountScript(address).map(_.script.bytes()), newScript.map(_.script.bytes())) }.toVector - StateUpdate(balances.toVector, leaseBalanceUpdates, dataEntries, assets, updatedLeases, updatedScripts, Seq.empty) + StateUpdate(balances.toVector, leaseBalanceUpdates, dataEntries, assets, updatedLeases.toSeq, updatedScripts, Seq.empty) } private[this] def transactionsMetadata(blockchain: Blockchain, snapshot: StateSnapshot): Seq[TransactionMetadata] = { diff --git a/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/AccountsApiGrpcSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/AccountsApiGrpcSpec.scala index 61c1cb035f..697740ce86 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/AccountsApiGrpcSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/AccountsApiGrpcSpec.scala @@ -3,15 +3,7 @@ package com.wavesplatform.api.grpc.test import com.google.protobuf.ByteString import com.wavesplatform.TestValues import com.wavesplatform.account.{Address, KeyPair} -import com.wavesplatform.api.grpc.{ - AccountRequest, - AccountsApiGrpcImpl, - BalanceResponse, - BalancesRequest, - DataEntryResponse, - DataRequest, - LeaseResponse -} +import com.wavesplatform.api.grpc.* import com.wavesplatform.block.Block import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto.DigestLength @@ -19,7 +11,7 @@ import com.wavesplatform.db.WithDomain import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.history.Domain import com.wavesplatform.protobuf.Amount -import com.wavesplatform.protobuf.transaction.{DataTransactionData, Recipient} +import com.wavesplatform.protobuf.transaction.{DataEntry, Recipient} import com.wavesplatform.state.{BlockRewardCalculator, EmptyDataEntry, IntegerDataEntry} import com.wavesplatform.test.* import com.wavesplatform.transaction.Asset.Waves @@ -151,7 +143,7 @@ class AccountsApiGrpcSpec extends FreeSpec with BeforeAndAfterAll with DiffMatch result1.runSyncUnsafe() shouldBe List( DataEntryResponse.of( ByteString.copyFrom(sender.toAddress.bytes), - Some(DataTransactionData.DataEntry.of("key2", DataTransactionData.DataEntry.Value.IntValue(456))) + Some(DataEntry.of("key2", DataEntry.Value.IntValue(456))) ) ) @@ -160,11 +152,11 @@ class AccountsApiGrpcSpec extends FreeSpec with BeforeAndAfterAll with DiffMatch result2.runSyncUnsafe() shouldBe List( DataEntryResponse.of( ByteString.copyFrom(sender.toAddress.bytes), - Some(DataTransactionData.DataEntry.of("key2", DataTransactionData.DataEntry.Value.IntValue(456))) + Some(DataEntry.of("key2", DataEntry.Value.IntValue(456))) ), DataEntryResponse.of( ByteString.copyFrom(sender.toAddress.bytes), - Some(DataTransactionData.DataEntry.of("key3", DataTransactionData.DataEntry.Value.IntValue(789))) + Some(DataEntry.of("key3", DataEntry.Value.IntValue(789))) ) ) } diff --git a/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/AssetsApiGrpcSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/AssetsApiGrpcSpec.scala index 3ecbc270e4..abbeee9e08 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/AssetsApiGrpcSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/AssetsApiGrpcSpec.scala @@ -3,6 +3,7 @@ package com.wavesplatform.api.grpc.test import com.google.protobuf.ByteString import com.wavesplatform.account.KeyPair import com.wavesplatform.api.grpc.{AssetInfoResponse, AssetsApiGrpcImpl, NFTRequest, NFTResponse} +import com.wavesplatform.block.Block.ProtoBlockVersion import com.wavesplatform.db.WithDomain import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.features.BlockchainFeatures @@ -55,6 +56,49 @@ class AssetsApiGrpcSpec extends FreeSpec with BeforeAndAfterAll with DiffMatcher } } + "NODE-999. GetNftList limit should work properly" in withDomain( + RideV6.addFeatures(BlockchainFeatures.ReduceNFTFee), + AddrWithBalance.enoughBalances(sender) + ) { d => + val nftIssues = (1 to 5).map(idx => TxHelpers.issue(sender, 1, name = s"nft$idx", reissuable = false)) + val limit = 2 + val afterId = 1 // second element + + d.appendBlock() + val mb1 = d.appendMicroBlock(nftIssues.take(afterId + 1)*) + d.appendMicroBlock(nftIssues.drop(afterId + 1)*) + + // full liquid + d.rocksDBWriter.containsTransaction(nftIssues(afterId)) shouldBe false + d.rocksDBWriter.containsTransaction(nftIssues(afterId + 1)) shouldBe false + check() + + // liquid afterId + d.appendBlock(d.createBlock(ProtoBlockVersion, nftIssues.drop(afterId + 1), Some(mb1))) + d.rocksDBWriter.containsTransaction(nftIssues(afterId)) shouldBe true + d.rocksDBWriter.containsTransaction(nftIssues(afterId + 1)) shouldBe false + check() + + // full solid + d.appendBlock() + d.rocksDBWriter.containsTransaction(nftIssues(afterId)) shouldBe true + d.rocksDBWriter.containsTransaction(nftIssues(afterId + 1)) shouldBe true + check() + + def check() = { + val (observer, result) = createObserver[NFTResponse] + val request = NFTRequest.of( + ByteString.copyFrom(sender.toAddress.bytes), + limit, + afterAssetId = ByteString.copyFrom(nftIssues(afterId).asset.id.arr) + ) + getGrpcApi(d).getNFTList(request, observer) + val response = result.runSyncUnsafe() + response.size shouldBe limit + response.map(_.assetInfo.get.name) shouldBe nftIssues.slice(afterId + 1, afterId + limit + 1).map(_.name.toStringUtf8) + } + } + private def getGrpcApi(d: Domain) = new AssetsApiGrpcImpl(d.assetsApi, d.accountsApi) } diff --git a/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/GRPCBroadcastSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/GRPCBroadcastSpec.scala index a47b4105d7..97f4b4e997 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/GRPCBroadcastSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/api/grpc/test/GRPCBroadcastSpec.scala @@ -1,7 +1,7 @@ package com.wavesplatform.api.grpc.test import scala.concurrent.{Await, Future} -import scala.concurrent.duration.Duration +import scala.concurrent.duration.* import com.wavesplatform.account.Address import com.wavesplatform.common.state.ByteStr import com.wavesplatform.test.{FlatSpec, TestTime} @@ -91,7 +91,7 @@ class GRPCBroadcastSpec extends FlatSpec with BeforeAndAfterAll with PathMockFac @throws[StatusException]("on failed broadcast") def assertBroadcast(tx: Transaction): Unit = { - Await.result(grpcTxApi.broadcast(PBTransactions.protobuf(tx)), Duration.Inf) + Await.result(grpcTxApi.broadcast(PBTransactions.protobuf(tx)), 10.seconds) } } } diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesEthereumInvokeTxSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesEthereumInvokeTxSpec.scala index ca5cd1faf0..d3ffa870df 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesEthereumInvokeTxSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesEthereumInvokeTxSpec.scala @@ -12,10 +12,9 @@ import com.wavesplatform.events.protobuf.BlockchainUpdated.Append import com.wavesplatform.test.NumericExt import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.EthTxGenerator.Arg -import com.wavesplatform.transaction.{Asset, EthTxGenerator, EthereumTransaction, TxHelpers, TxNonNegativeAmount} import com.wavesplatform.transaction.TxHelpers.secondAddress import com.wavesplatform.transaction.assets.IssueTransaction -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer +import com.wavesplatform.transaction.{Asset, EthTxGenerator, EthereumTransaction, TxHelpers} class BlockchainUpdatesEthereumInvokeTxSpec extends BlockchainUpdatesTestBase { val ethAddressBalanceAfterTx: Long = firstTxParticipantBalanceBefore - invokeFee @@ -89,9 +88,9 @@ class BlockchainUpdatesEthereumInvokeTxSpec extends BlockchainUpdatesTestBase { val massTx = TxHelpers.massTransfer( assetDappAccount, Seq( - ParsedTransfer(firstTxParticipantAddress, TxNonNegativeAmount.unsafeFrom(amount)), - ParsedTransfer(secondAddress, TxNonNegativeAmount.unsafeFrom(amount)), - ParsedTransfer(invokerDappAddress, TxNonNegativeAmount.unsafeFrom(amount)) + firstTxParticipantAddress -> amount, + secondAddress -> amount, + invokerDappAddress -> amount ), asset, fee = 500000 @@ -141,7 +140,15 @@ class BlockchainUpdatesEthereumInvokeTxSpec extends BlockchainUpdatesTestBase { doubleNestedInvokeTest(assetDappAccount, balancesSeq, issue, invoke, massTx, caller, Subscribe) { append => val invokeScriptMetadata = append.transactionsMetadata.head.getEthereum.getInvoke checkEthereumBase(append, invoke, foo) - checkInvokeDoubleNestedBlockchainUpdates(append, invokeScriptMetadata, assetDappAddress, firstTxParticipantAddress, secondAddress, issue, callerBalancesMap) + checkInvokeDoubleNestedBlockchainUpdates( + append, + invokeScriptMetadata, + assetDappAddress, + firstTxParticipantAddress, + secondAddress, + issue, + callerBalancesMap + ) } } @@ -149,7 +156,15 @@ class BlockchainUpdatesEthereumInvokeTxSpec extends BlockchainUpdatesTestBase { doubleNestedInvokeTest(assetDappAccount, balancesSeq, issue, invoke, massTx, caller, GetBlockUpdate) { append => val invokeScriptMetadata = append.transactionsMetadata.head.getEthereum.getInvoke checkEthereumBase(append, invoke, foo) - checkInvokeDoubleNestedBlockchainUpdates(append, invokeScriptMetadata, assetDappAddress, firstTxParticipantAddress, secondAddress, issue, callerBalancesMap) + checkInvokeDoubleNestedBlockchainUpdates( + append, + invokeScriptMetadata, + assetDappAddress, + firstTxParticipantAddress, + secondAddress, + issue, + callerBalancesMap + ) } } @@ -157,7 +172,15 @@ class BlockchainUpdatesEthereumInvokeTxSpec extends BlockchainUpdatesTestBase { doubleNestedInvokeTest(assetDappAccount, balancesSeq, issue, invoke, massTx, caller, GetBlockUpdateRange) { append => val invokeScriptMetadata = append.transactionsMetadata.head.getEthereum.getInvoke checkEthereumBase(append, invoke, foo) - checkInvokeDoubleNestedBlockchainUpdates(append, invokeScriptMetadata, assetDappAddress, firstTxParticipantAddress, secondAddress, issue, callerBalancesMap) + checkInvokeDoubleNestedBlockchainUpdates( + append, + invokeScriptMetadata, + assetDappAddress, + firstTxParticipantAddress, + secondAddress, + issue, + callerBalancesMap + ) } } diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesGetBlockUpdatesRangeSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesGetBlockUpdatesRangeSpec.scala index 3e2c0211a7..82fc0abcfa 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesGetBlockUpdatesRangeSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesGetBlockUpdatesRangeSpec.scala @@ -178,7 +178,7 @@ class BlockchainUpdatesGetBlockUpdatesRangeSpec extends BlockchainUpdatesTestBas "BU-165. Return correct data for massTransfer" in { val massTransferFee = fee * 6 - val massTransfer = TxHelpers.massTransfer(firstTxParticipant, recipients, firstToken.asset, massTransferFee) + val massTransfer = TxHelpers.massTransfer(firstTxParticipant, recipients.map(v => v.address -> v.amount.value), firstToken.asset, massTransferFee) withGenerateGetBlockUpdateRange( GetBlockUpdatesRangeRequest.of(1, 3), settings = currentSettings, diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesGetBlockUpdatesSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesGetBlockUpdatesSpec.scala index cd8ccd2c6e..0d719ec459 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesGetBlockUpdatesSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesGetBlockUpdatesSpec.scala @@ -160,7 +160,8 @@ class BlockchainUpdatesGetBlockUpdatesSpec extends BlockchainUpdatesTestBase { "BU-200. Return correct data for massTransfer" in { val massTransferFee = fee * 6 - val massTransfer = TxHelpers.massTransfer(firstTxParticipant, recipients, firstToken.asset, massTransferFee) + val massTransfer = + TxHelpers.massTransfer(firstTxParticipant, recipients.map(r => r.address -> r.amount.value), firstToken.asset, massTransferFee) withGenerateGetBlockUpdate( height = 3, @@ -271,7 +272,7 @@ class BlockchainUpdatesGetBlockUpdatesSpec extends BlockchainUpdatesTestBase { val ethereumTransfer: EthereumTransaction = EthTxGenerator.generateEthTransfer(firstTxParticipantEthereum, secondTxParticipantAddress, amount, secondTokenAsset) val ethAddress = ethereumTransfer.senderAddress.value() - val transfer = TxHelpers.transfer(secondTxParticipant, ethAddress, secondTokenQuantity, secondTokenAsset) + val transfer = TxHelpers.transfer(secondTxParticipant, ethAddress, secondTokenQuantity, secondTokenAsset) withGenerateGetBlockUpdate( height = 4, diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesInvokeTxSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesInvokeTxSpec.scala index a9e8840699..28e3f0e80f 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesInvokeTxSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesInvokeTxSpec.scala @@ -9,15 +9,14 @@ import com.wavesplatform.events.fixtures.BlockchainUpdateGrpcMethod.* import com.wavesplatform.events.fixtures.InvokeWavesTxCheckers.checkInvokeDoubleNestedBlockchainUpdates import com.wavesplatform.events.fixtures.PrepareInvokeTestData.* import com.wavesplatform.events.fixtures.WavesTxChecks.* -import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BYTESTR, CONST_LONG, CONST_STRING, EXPR} import com.wavesplatform.events.protobuf.BlockchainUpdated.Append +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BYTESTR, CONST_LONG, CONST_STRING, EXPR} import com.wavesplatform.test.NumericExt import com.wavesplatform.transaction.Asset.Waves -import com.wavesplatform.transaction.{Asset, TxHelpers, TxNonNegativeAmount} import com.wavesplatform.transaction.TxHelpers.secondAddress -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction +import com.wavesplatform.transaction.{Asset, TxHelpers} class BlockchainUpdatesInvokeTxSpec extends BlockchainUpdatesTestBase { "Simple invoke transaction" - { @@ -87,9 +86,9 @@ class BlockchainUpdatesInvokeTxSpec extends BlockchainUpdatesTestBase { val massTx = TxHelpers.massTransfer( assetDappAccount, Seq( - ParsedTransfer(firstTxParticipantAddress, TxNonNegativeAmount.unsafeFrom(amount)), - ParsedTransfer(secondAddress, TxNonNegativeAmount.unsafeFrom(amount)), - ParsedTransfer(invokerDappAddress, TxNonNegativeAmount.unsafeFrom(amount)) + firstTxParticipantAddress -> amount, + secondAddress -> amount, + invokerDappAddress -> amount ), asset, fee = 500000 @@ -144,7 +143,8 @@ class BlockchainUpdatesInvokeTxSpec extends BlockchainUpdatesTestBase { append, invokeScriptMetadata, assetDappAddress, - firstTxParticipantAddress, secondAddress, + firstTxParticipantAddress, + secondAddress, issue, callerBalancesMap ) @@ -159,7 +159,8 @@ class BlockchainUpdatesInvokeTxSpec extends BlockchainUpdatesTestBase { append, invokeScriptMetadata, assetDappAddress, - firstTxParticipantAddress, secondAddress, + firstTxParticipantAddress, + secondAddress, issue, callerBalancesMap ) @@ -174,7 +175,8 @@ class BlockchainUpdatesInvokeTxSpec extends BlockchainUpdatesTestBase { append, invokeScriptMetadata, assetDappAddress, - firstTxParticipantAddress, secondAddress, + firstTxParticipantAddress, + secondAddress, issue, callerBalancesMap ) @@ -201,7 +203,15 @@ class BlockchainUpdatesInvokeTxSpec extends BlockchainUpdatesTestBase { doubleNestedInvokeTest(assetDappAccount, balancesSeq, issue, invoke, massTx, originCaller, GetBlockUpdate) { append => val invokeScriptMetadata = append.transactionsMetadata.head.getInvokeScript checkInvokeBase(append, invoke) - checkInvokeDoubleNestedBlockchainUpdates(append, invokeScriptMetadata, assetDappAddress, invokerDappAddress, invokerDappAddress, issue, originalCallerBalancesMap) + checkInvokeDoubleNestedBlockchainUpdates( + append, + invokeScriptMetadata, + assetDappAddress, + invokerDappAddress, + invokerDappAddress, + issue, + originalCallerBalancesMap + ) } } @@ -209,7 +219,15 @@ class BlockchainUpdatesInvokeTxSpec extends BlockchainUpdatesTestBase { doubleNestedInvokeTest(assetDappAccount, balancesSeq, issue, invoke, massTx, originCaller, GetBlockUpdateRange) { append => val invokeScriptMetadata = append.transactionsMetadata.head.getInvokeScript checkInvokeBase(append, invoke) - checkInvokeDoubleNestedBlockchainUpdates(append, invokeScriptMetadata, assetDappAddress, invokerDappAddress, invokerDappAddress, issue, originalCallerBalancesMap) + checkInvokeDoubleNestedBlockchainUpdates( + append, + invokeScriptMetadata, + assetDappAddress, + invokerDappAddress, + invokerDappAddress, + issue, + originalCallerBalancesMap + ) } } } diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala index 34feeb4a0d..4c7db9915c 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala @@ -11,16 +11,7 @@ import com.wavesplatform.crypto.DigestLength import com.wavesplatform.db.InterferableDB import com.wavesplatform.events.FakeObserver.* import com.wavesplatform.events.StateUpdate.LeaseUpdate.LeaseStatus -import com.wavesplatform.utils.byteStrOrdering -import com.wavesplatform.events.StateUpdate.{ - AssetInfo, - AssetStateUpdate, - BalanceUpdate, - DataEntryUpdate, - LeaseUpdate, - LeasingBalanceUpdate, - ScriptUpdate -} +import com.wavesplatform.events.StateUpdate.{AssetInfo, AssetStateUpdate, BalanceUpdate, DataEntryUpdate, LeaseUpdate, LeasingBalanceUpdate, ScriptUpdate} import com.wavesplatform.events.api.grpc.protobuf.{GetBlockUpdateRequest, GetBlockUpdatesRangeRequest, SubscribeRequest} import com.wavesplatform.events.protobuf.BlockchainUpdated.Rollback.RollbackType import com.wavesplatform.events.protobuf.BlockchainUpdated.Update @@ -36,8 +27,7 @@ import com.wavesplatform.lang.v1.compiler.Terms.FUNCTION_CALL import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.protobuf.* import com.wavesplatform.protobuf.block.PBBlocks -import com.wavesplatform.protobuf.transaction.DataTransactionData.DataEntry -import com.wavesplatform.protobuf.transaction.InvokeScriptResult +import com.wavesplatform.protobuf.transaction.{DataEntry, InvokeScriptResult} import com.wavesplatform.protobuf.transaction.InvokeScriptResult.{Call, Invocation, Payment} import com.wavesplatform.settings.{Constants, WavesSettings} import com.wavesplatform.state.{AssetDescription, BlockRewardCalculator, EmptyDataEntry, Height, LeaseBalance, StringDataEntry} @@ -51,6 +41,7 @@ import com.wavesplatform.transaction.smart.SetScriptTransaction import com.wavesplatform.transaction.transfer.TransferTransaction import com.wavesplatform.transaction.utils.Signed import com.wavesplatform.transaction.{Asset, CreateAliasTransaction, DataTransaction, GenesisTransaction, PaymentTransaction, TxHelpers} +import com.wavesplatform.utils.byteStrOrdering import io.grpc.StatusException import monix.execution.Scheduler.Implicits.global import org.scalactic.source.Position @@ -420,7 +411,7 @@ class BlockchainUpdatesSpec extends FreeSpec with WithBUDomain with ScalaFutures (1 to blocksCount + 1).foreach(_ => d.appendBlock()) val result = Await - .result(r.getBlockUpdatesRange(GetBlockUpdatesRangeRequest(1, blocksCount)), Duration.Inf) + .result(r.getBlockUpdatesRange(GetBlockUpdatesRangeRequest(1, blocksCount)), 1.minute) .updates .map(_.update.append.map(_.getBlock.vrf.toByteStr).filterNot(_.isEmpty)) @@ -1215,7 +1206,7 @@ class BlockchainUpdatesSpec extends FreeSpec with WithBUDomain with ScalaFutures Await .result( repo.getBlockUpdate(GetBlockUpdateRequest(height)), - Duration.Inf + 1.minute ) .getUpdate .update diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeInvokeTxSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeInvokeTxSpec.scala index 171f1ebdf0..929b38e139 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeInvokeTxSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeInvokeTxSpec.scala @@ -15,10 +15,9 @@ import com.wavesplatform.settings.WavesSettings import com.wavesplatform.state.{BinaryDataEntry, BooleanDataEntry, DataEntry, EmptyDataEntry, IntegerDataEntry, StringDataEntry} import com.wavesplatform.test.{FreeSpec, NumericExt} import com.wavesplatform.transaction.Asset.Waves -import com.wavesplatform.transaction.TxHelpers.{defaultAddress, script, secondAddress, secondSigner, setScript} +import com.wavesplatform.transaction.TxHelpers +import com.wavesplatform.transaction.TxHelpers.* import com.wavesplatform.transaction.smart.SetScriptTransaction -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer -import com.wavesplatform.transaction.{TxHelpers, TxNonNegativeAmount} import org.scalatest.concurrent.ScalaFutures class BlockchainUpdatesSubscribeInvokeTxSpec extends FreeSpec with WithBUDomain with ScalaFutures { @@ -139,9 +138,9 @@ class BlockchainUpdatesSubscribeInvokeTxSpec extends FreeSpec with WithBUDomain val massTx = TxHelpers.massTransfer( assetDappAccount, Seq( - ParsedTransfer(dAppAddress, TxNonNegativeAmount.unsafeFrom(assetTransferAmount)), - ParsedTransfer(secondAddress, TxNonNegativeAmount.unsafeFrom(assetTransferAmount)), - ParsedTransfer(invokerDappAddress, TxNonNegativeAmount.unsafeFrom(assetTransferAmount)) + dAppAddress -> assetTransferAmount, + secondAddress -> assetTransferAmount, + invokerDappAddress -> assetTransferAmount ), asset, fee = 500000 diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeSpec.scala index 36b47da26c..7b96f8d211 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSubscribeSpec.scala @@ -151,7 +151,7 @@ class BlockchainUpdatesSubscribeSpec extends BlockchainUpdatesTestBase { "BU-16. Return correct data for massTransfer" in { val massTransferFee = fee * 6 - val massTransfer = TxHelpers.massTransfer(firstTxParticipant, recipients, firstToken.asset, massTransferFee) + val massTransfer = TxHelpers.massTransfer(firstTxParticipant, recipients.map(r => r.address -> r.amount.value), firstToken.asset, massTransferFee) withGenerateSubscription( settings = currentSettings, diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/MetadataSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/MetadataSpec.scala index 28d043a221..90f3a31a1f 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/MetadataSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/MetadataSpec.scala @@ -1,16 +1,15 @@ package com.wavesplatform.events import com.wavesplatform.common.state.ByteStr -import FakeObserver.* +import com.wavesplatform.events.FakeObserver.* import com.wavesplatform.events.api.grpc.protobuf.SubscribeRequest import com.wavesplatform.events.protobuf.TransactionMetadata import com.wavesplatform.protobuf.* import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.RideV6 import com.wavesplatform.transaction.assets.exchange.* -import com.wavesplatform.transaction.transfer.MassTransferTransaction import com.wavesplatform.transaction.utils.EthConverters.* -import com.wavesplatform.transaction.{Asset, EthTxGenerator, TxExchangeAmount, TxHelpers, TxMatcherFee, TxNonNegativeAmount, TxOrderPrice} +import com.wavesplatform.transaction.{Asset, EthTxGenerator, TxExchangeAmount, TxHelpers, TxMatcherFee, TxOrderPrice} class MetadataSpec extends FreeSpec with WithBUDomain { "BlockchainUpdates returns correct metadata for supported transaction types" in withDomainAndRepo(RideV6) { (d, r) => @@ -63,9 +62,9 @@ class MetadataSpec extends FreeSpec with WithBUDomain { val massTransfer = TxHelpers.massTransfer( genesisAddress, Seq( - MassTransferTransaction.ParsedTransfer(issuer.toAddress, TxNonNegativeAmount.unsafeFrom(100.waves)), - MassTransferTransaction.ParsedTransfer(matcher.toAddress, TxNonNegativeAmount.unsafeFrom(100.waves)), - MassTransferTransaction.ParsedTransfer(ethOrderSender.toWavesAddress, TxNonNegativeAmount.unsafeFrom(100.waves)) + issuer.toAddress -> 100.waves, + matcher.toAddress -> 100.waves, + ethOrderSender.toWavesAddress -> 100.waves ), fee = 0.003.waves ) diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/fixtures/WavesTxChecks.scala b/grpc-server/src/test/scala/com/wavesplatform/events/fixtures/WavesTxChecks.scala index bb8a73ad3c..5e01c45f5a 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/fixtures/WavesTxChecks.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/fixtures/WavesTxChecks.scala @@ -8,10 +8,10 @@ import com.wavesplatform.events.StateUpdate.LeaseUpdate.LeaseStatus import com.wavesplatform.events.protobuf.StateUpdate.AssetDetails.AssetScriptInfo import com.wavesplatform.events.protobuf.StateUpdate.{AssetDetails, BalanceUpdate, DataEntryUpdate, LeaseUpdate, LeasingUpdate, ScriptUpdate} import com.wavesplatform.events.protobuf.TransactionMetadata -import com.wavesplatform.protobuf.Amount import com.wavesplatform.protobuf.order.Order import com.wavesplatform.protobuf.transaction.* import com.wavesplatform.protobuf.transaction.Transaction.Data +import com.wavesplatform.protobuf.{Amount, transaction} import com.wavesplatform.state.DataEntry import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.assets.* @@ -277,7 +277,7 @@ object WavesTxChecks extends Matchers with OptionValues { expectedValues.zip(actualArguments).foreach(item => item._1 shouldBe item._2) } - def checkInvokeScriptResultData(result: Seq[DataTransactionData.DataEntry], actualData: Seq[(String, Any)])(implicit pos: Position): Unit = { + def checkInvokeScriptResultData(result: Seq[transaction.DataEntry], actualData: Seq[(String, Any)])(implicit pos: Position): Unit = { if (result.length != actualData.length) { throw new IllegalArgumentException("Number of expected values does not match the number of actual data arguments.") } diff --git a/lang/shared/src/main/scala/com/wavesplatform/common/utils/FastBase58.scala b/lang/shared/src/main/scala/com/wavesplatform/common/utils/FastBase58.scala index 05024ebd27..1590f02191 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/common/utils/FastBase58.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/common/utils/FastBase58.scala @@ -73,6 +73,7 @@ object FastBase58 extends BaseXXEncDec { outArray(outIndex) = longValue & 0xffffffffL } + // called only from Try structure, see BaseXXEncDec.tryDecode(str: String) if (base58EncMask > 0) throw new IllegalArgumentException("Output number too big (carry to the next int32)") if ((outArray(0) & zeroMask) != 0) throw new IllegalArgumentException("Output number too big (last int32 filled too far)") } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/ExecutionError.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/ExecutionError.scala index d02bdd7a91..63dfdfea2e 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/ExecutionError.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/ExecutionError.scala @@ -7,4 +7,5 @@ case class CommonError(details: String, cause: Option[ValidationError] = None) e override def toString: String = s"CommonError($message)" override def message: String = cause.map(_.toString).getOrElse(details) } +case class ThrownError(message: String) extends ExecutionError case class FailOrRejectError(message: String, skipInvokeComplexity: Boolean = true) extends ExecutionError with ValidationError diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala index 08b562a68f..9e5002fdba 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala @@ -393,6 +393,7 @@ object BaseGlobal { def apply(n: Int): T = if (from + n < until) arr(from + n) else throw new ArrayIndexOutOfBoundsException(n) + // should be never thrown due to passing Random.nextInt(arr.size) at the single point of call def partitionInPlace(p: T => Boolean): (ArrayView[T], ArrayView[T]) = { var upper = until - 1 diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/CompilationError.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/CompilationError.scala index 08e377eb8f..243fa22889 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/CompilationError.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/CompilationError.scala @@ -1,7 +1,5 @@ package com.wavesplatform.lang.v1.compiler -import java.nio.charset.StandardCharsets - import cats.Show import com.wavesplatform.lang.v1.ContractLimits import com.wavesplatform.lang.v1.compiler.Types.* @@ -9,6 +7,8 @@ import com.wavesplatform.lang.v1.evaluator.ctx.FunctionTypeSignature import com.wavesplatform.lang.v1.parser.Expressions import com.wavesplatform.lang.v1.parser.Expressions.{Declaration, PART} +import java.nio.charset.StandardCharsets + sealed trait CompilationError { def start: Int def end: Int @@ -52,18 +52,11 @@ object CompilationError { s"but ${names.map(n => s"`$n`").mkString(", ")} found" } - final case class UnusedCaseVariables(start: Int, end: Int, names: List[String]) extends CompilationError { - val message = s"Unused case variable(s) ${names.map(n => s"`$n`").mkString(", ")}" - } - final case class AlreadyDefined(start: Int, end: Int, name: String, isFunction: Boolean) extends CompilationError { val message = if (isFunction) s"Value '$name' can't be defined because function with this name is already defined" else s"Value '$name' already defined in the scope" } - final case class NonExistingType(start: Int, end: Int, name: String, existing: List[String]) extends CompilationError { - val message = s"Value '$name' declared as non-existing type, while all possible types are $existing" - } final case class BadFunctionSignatureSameArgNames(start: Int, end: Int, name: String) extends CompilationError { val message = s"Function '$name' declared with duplicating argument names" diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/CompilationStepResultDec.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/CompilationStepResultDec.scala new file mode 100644 index 0000000000..aa0f6f7537 --- /dev/null +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/CompilationStepResultDec.scala @@ -0,0 +1,12 @@ +package com.wavesplatform.lang.v1.compiler + +import com.wavesplatform.lang.v1.compiler.Types.FINAL +import com.wavesplatform.lang.v1.parser.Expressions + +case class CompilationStepResultDec( + ctx: CompilerContext, + dec: Terms.DECLARATION, + t: FINAL, + parseNodeExpr: Expressions.Declaration, + errors: Iterable[CompilationError] = Iterable.empty +) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/CompilationStepResultExpr.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/CompilationStepResultExpr.scala new file mode 100644 index 0000000000..bf8eb9fd0c --- /dev/null +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/CompilationStepResultExpr.scala @@ -0,0 +1,12 @@ +package com.wavesplatform.lang.v1.compiler + +import com.wavesplatform.lang.v1.compiler.Types.FINAL +import com.wavesplatform.lang.v1.parser.Expressions + +case class CompilationStepResultExpr( + ctx: CompilerContext, + expr: Terms.EXPR, + t: FINAL, + parseNodeExpr: Expressions.EXPR, + errors: Iterable[CompilationError] = Iterable.empty +) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ContractCompiler.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ContractCompiler.scala index a8647939e8..1240d5aae7 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ContractCompiler.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ContractCompiler.scala @@ -13,7 +13,7 @@ import com.wavesplatform.lang.contract.meta.{MetaMapper, V1 as MetaV1, V2 as Met import com.wavesplatform.lang.directives.values.{StdLibVersion, V3, V6} import com.wavesplatform.lang.v1.compiler.CompilationError.{AlreadyDefined, Generic, UnionNotAllowedForCallableArgs, WrongArgumentType} import com.wavesplatform.lang.v1.compiler.CompilerContext.{VariableInfo, vars} -import com.wavesplatform.lang.v1.compiler.ExpressionCompiler.* +import com.wavesplatform.lang.v1.compiler.ContractCompiler.* import com.wavesplatform.lang.v1.compiler.ScriptResultSource.FreeCall import com.wavesplatform.lang.v1.compiler.Terms.EXPR import com.wavesplatform.lang.v1.compiler.Types.{BOOLEAN, BYTESTR, LONG, STRING} @@ -25,16 +25,13 @@ import com.wavesplatform.lang.v1.parser.Expressions.{FUNC, PART, Type} import com.wavesplatform.lang.v1.parser.Parser.LibrariesOffset import com.wavesplatform.lang.v1.parser.{Expressions, Parser} import com.wavesplatform.lang.v1.task.imports.* -import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader, compiler} +import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader} import scala.annotation.tailrec -object ContractCompiler { - val FreeCallInvocationArg = "i" - +class ContractCompiler(version: StdLibVersion) extends ExpressionCompiler(version) { private def compileAnnotatedFunc( af: Expressions.ANNOTATEDFUNC, - version: StdLibVersion, saveExprContext: Boolean, allowIllFormedStrings: Boolean, source: ScriptResultSource @@ -88,10 +85,10 @@ object ContractCompiler { _.flatMap(_.dic(version).toList).map(nameAndType => (nameAndType._1, VariableInfo(AnyPos, nameAndType._2))) ) .getOrElse(List.empty) - unionInCallableErrs <- checkCallableUnions(af, annotationsWithErr._1.toList.flatten, version) + unionInCallableErrs <- checkCallableUnions(af, annotationsWithErr._1.toList.flatten) compiledBody <- local { modify[Id, CompilerContext, CompilationError](vars.modify(_)(_ ++ annotationBindings)).flatMap(_ => - compiler.ExpressionCompiler.compileFunc(af.f.position, af.f, saveExprContext, annotationBindings.map(_._1), allowIllFormedStrings) + compileFunc(af.f.position, af.f, saveExprContext, annotationBindings.map(_._1), allowIllFormedStrings) ) } annotatedFuncWithErr <- getCompiledAnnotatedFunc(annotationsWithErr, compiledBody._1).handleError() @@ -132,7 +129,6 @@ object ContractCompiler { private def compileContract( parsedDapp: Expressions.DAPP, - version: StdLibVersion, needCompaction: Boolean, removeUnusedCode: Boolean, source: ScriptResultSource, @@ -149,7 +145,7 @@ object ContractCompiler { annFuncArgTypesErr <- validateAnnotatedFuncsArgTypes(parsedDapp).handleError() compiledAnnFuncsWithErr <- parsedDapp.fs .traverse[CompileM, (Option[AnnotatedFunction], List[(String, Types.FINAL)], Expressions.ANNOTATEDFUNC, Iterable[CompilationError])](af => - local(compileAnnotatedFunc(af, version, saveExprContext, allowIllFormedStrings, source)) + local(compileAnnotatedFunc(af, saveExprContext, allowIllFormedStrings, source)) ) annotatedFuncs = compiledAnnFuncsWithErr.filter(_._1.nonEmpty).map(_._1.get) parsedNodeAFuncs = compiledAnnFuncsWithErr.map(_._3) @@ -236,7 +232,7 @@ object ContractCompiler { } yield result } - def handleValid[T](part: PART[T]): CompileM[PART.VALID[T]] = part match { + private def handleValid[T](part: PART[T]): CompileM[PART.VALID[T]] = part match { case x: PART.VALID[T] => x.pure[CompileM] case PART.INVALID(p, message) => raiseError(Generic(p.start, p.end, message)) } @@ -305,13 +301,7 @@ object ContractCompiler { } } - val primitiveCallableTypes: Set[String] = - Set(LONG, BYTESTR, BOOLEAN, STRING).map(_.name) - - val allowedCallableTypesV4: Set[String] = - primitiveCallableTypes + "List[]" - - private def validateDuplicateVarsInContract(contract: Expressions.DAPP): CompileM[Any] = { + private def validateDuplicateVarsInContract(contract: Expressions.DAPP): CompileM[Any] = for { ctx <- get[Id, CompilerContext, CompilationError] annotationVars = contract.fs.flatMap(_.anns.flatMap(_.args)).traverse[CompileM, PART.VALID[String]](handleValid) @@ -339,7 +329,52 @@ object ContractCompiler { } } } yield () + + private def checkCallableUnions( + func: Expressions.ANNOTATEDFUNC, + annotations: List[Annotation], + ): CompileM[Seq[UnionNotAllowedForCallableArgs]] = { + @tailrec + def containsUnion(tpe: Type): Boolean = + tpe match { + case Expressions.Union(types) if types.size > 1 => true + case Expressions.Single(PART.VALID(_, Type.ListTypeName), Some(PART.VALID(_, Expressions.Union(types)))) if types.size > 1 => true + case Expressions.Single( + PART.VALID(_, Type.ListTypeName), + Some(PART.VALID(_, inner @ Expressions.Single(PART.VALID(_, Type.ListTypeName), _))) + ) => + containsUnion(inner) + case _ => false + } + + val isCallable = annotations.exists { + case CallableAnnotation(_) => true + case _ => false + } + + if (version < V6 || !isCallable) { + Seq.empty[UnionNotAllowedForCallableArgs].pure[CompileM] + } else { + func.f.args + .filter { case (_, tpe) => + containsUnion(tpe) + } + .map { case (argName, _) => + UnionNotAllowedForCallableArgs(argName.position.start, argName.position.end) + } + .pure[CompileM] + } } +} + +object ContractCompiler { + val FreeCallInvocationArg = "i" + + val primitiveCallableTypes: Set[String] = + Set(LONG, BYTESTR, BOOLEAN, STRING).map(_.name) + + val allowedCallableTypesV4: Set[String] = + primitiveCallableTypes + "List[]" def apply( c: CompilerContext, @@ -350,7 +385,8 @@ object ContractCompiler { removeUnusedCode: Boolean = false, allowIllFormedStrings: Boolean = false ): Either[String, DApp] = { - compileContract(contract, version, needCompaction, removeUnusedCode, source, allowIllFormedStrings = allowIllFormedStrings) + new ContractCompiler(version) + .compileContract(contract, needCompaction, removeUnusedCode, source, allowIllFormedStrings = allowIllFormedStrings) .run(c) .map( _._2 @@ -375,7 +411,7 @@ object ContractCompiler { val parser = new Parser(version)(offset) parser.parseContract(input) match { case fastparse.Parsed.Success(xs, _) => - ContractCompiler(ctx, xs, version, source, needCompaction, removeUnusedCode, allowIllFormedStrings) match { + apply(ctx, xs, version, source, needCompaction, removeUnusedCode, allowIllFormedStrings) match { case Left(err) => Left(err) case Right(c) => Right(c) } @@ -396,7 +432,8 @@ object ContractCompiler { new Parser(version)(offset) .parseDAPPWithErrorRecovery(input) .flatMap { case (parseResult, removedCharPosOpt) => - compileContract(parseResult, version, needCompaction, removeUnusedCode, ScriptResultSource.CallableFunction, saveExprContext) + new ContractCompiler(version) + .compileContract(parseResult, needCompaction, removeUnusedCode, ScriptResultSource.CallableFunction, saveExprContext) .run(ctx) .map( _._2 @@ -437,41 +474,4 @@ object ContractCompiler { Left(parser.toString(input, f)) } } - - private def checkCallableUnions( - func: Expressions.ANNOTATEDFUNC, - annotations: List[Annotation], - version: StdLibVersion - ): CompileM[Seq[UnionNotAllowedForCallableArgs]] = { - @tailrec - def containsUnion(tpe: Type): Boolean = - tpe match { - case Expressions.Union(types) if types.size > 1 => true - case Expressions.Single(PART.VALID(_, Type.ListTypeName), Some(PART.VALID(_, Expressions.Union(types)))) if types.size > 1 => true - case Expressions.Single( - PART.VALID(_, Type.ListTypeName), - Some(PART.VALID(_, inner @ Expressions.Single(PART.VALID(_, Type.ListTypeName), _))) - ) => - containsUnion(inner) - case _ => false - } - - val isCallable = annotations.exists { - case CallableAnnotation(_) => true - case _ => false - } - - if (version < V6 || !isCallable) { - Seq.empty[UnionNotAllowedForCallableArgs].pure[CompileM] - } else { - func.f.args - .filter { case (_, tpe) => - containsUnion(tpe) - } - .map { case (argName, _) => - UnionNotAllowedForCallableArgs(argName.position.start, argName.position.end) - } - .pure[CompileM] - } - } } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala index 8a45f5bfee..9a9ae79997 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala @@ -4,28 +4,17 @@ import cats.implicits.* import cats.{Id, Show} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.lang.{CommonError, StringOps} -import com.wavesplatform.lang.directives.values.StdLibVersion +import com.wavesplatform.lang.directives.values.{StdLibVersion, V8} import com.wavesplatform.lang.v1.compiler.CompilationError.* import com.wavesplatform.lang.v1.compiler.CompilerContext.* +import com.wavesplatform.lang.v1.compiler.ExpressionCompiler.* import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.compiler.Types.* import com.wavesplatform.lang.v1.evaluator.EvaluatorV1.* import com.wavesplatform.lang.v1.evaluator.ctx.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.GlobalValNames import com.wavesplatform.lang.v1.parser.BinaryOperation.* -import com.wavesplatform.lang.v1.parser.Expressions.{ - BINARY_OP, - CompositePattern, - ConstsPat, - MATCH_CASE, - ObjPat, - PART, - Pos, - Single, - TuplePat, - Type, - TypedVar -} +import com.wavesplatform.lang.v1.parser.Expressions.{BINARY_OP, CompositePattern, ConstsPat, MATCH_CASE, ObjPat, PART, Pos, Single, TuplePat, Type, TypedVar} import com.wavesplatform.lang.v1.parser.Parser.LibrariesOffset import com.wavesplatform.lang.v1.parser.{BinaryOperation, Expressions, Parser} import com.wavesplatform.lang.v1.task.imports.* @@ -34,90 +23,7 @@ import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader} import java.nio.charset.StandardCharsets import scala.util.Try -object ExpressionCompiler { - case class CompilationStepResultExpr( - ctx: CompilerContext, - expr: Terms.EXPR, - t: FINAL, - parseNodeExpr: Expressions.EXPR, - errors: Iterable[CompilationError] = Iterable.empty - ) - - case class CompilationStepResultDec( - ctx: CompilerContext, - dec: Terms.DECLARATION, - t: FINAL, - parseNodeExpr: Expressions.Declaration, - errors: Iterable[CompilationError] = Iterable.empty - ) - - def compile( - input: String, - offset: LibrariesOffset, - ctx: CompilerContext, - version: StdLibVersion, - allowIllFormedStrings: Boolean = false - ): Either[String, (EXPR, FINAL)] = { - val parser = new Parser(version)(offset) - parser.parseExpr(input) match { - case fastparse.Parsed.Success(xs, _) => ExpressionCompiler(ctx, xs, allowIllFormedStrings) - case f: fastparse.Parsed.Failure => Left(parser.toString(input, f)) - } - } - - def compileBoolean(input: String, offset: LibrariesOffset, ctx: CompilerContext, version: StdLibVersion): Either[String, EXPR] = { - compile(input, offset, ctx, version).flatMap { - case (expr, BOOLEAN) => Right(expr) - case _ => Left("Script should return boolean") - } - } - - def compileUntyped(input: String, offset: LibrariesOffset, ctx: CompilerContext, version: StdLibVersion): Either[String, EXPR] = { - compile(input, offset, ctx, version) - .map { case (expr, _) => expr } - } - - def compileWithParseResult( - input: String, - offset: LibrariesOffset, - ctx: CompilerContext, - version: StdLibVersion, - saveExprContext: Boolean = true - ): Either[(String, Int, Int), (EXPR, Expressions.SCRIPT, Iterable[CompilationError])] = - new Parser(version)(offset) - .parseExpressionWithErrorRecovery(input) - .flatMap { case (parseResult, removedCharPosOpt) => - compileExprWithCtx(parseResult.expr, saveExprContext, allowIllFormedStrings = false) - .run(ctx) - .value - ._2 - .map { compRes => - val errorList = - compRes.errors ++ - (if (compRes.t equivalent BOOLEAN) Nil else List(Generic(0, 0, "Script should return boolean"))) ++ - (if (removedCharPosOpt.isEmpty) - Nil - else - List( - Generic( - removedCharPosOpt.get.start, - removedCharPosOpt.get.end, - "Parsing failed. Some chars was removed as result of recovery process." - ) - )) - (compRes.expr, parseResult.copy(expr = compRes.parseNodeExpr), errorList) - } - .leftMap(e => (s"Compilation failed: ${Show[CompilationError].show(e)}", e.start, e.end)) - } - - def compileDecls(input: String, offset: LibrariesOffset, ctx: CompilerContext, version: StdLibVersion): Either[String, EXPR] = { - val adjustedDecls = s"$input\n${GlobalValNames.Unit}" - compileUntyped(adjustedDecls, offset, ctx, version) - } - - private def compileExpr(expr: Expressions.EXPR): CompileM[(Terms.EXPR, FINAL, Expressions.EXPR, Iterable[CompilationError])] = - compileExprWithCtx(expr, allowIllFormedStrings = false).map(r => (r.expr, r.t, r.parseNodeExpr, r.errors)) - +class ExpressionCompiler(val version: StdLibVersion) { private def compileExprWithCtx( expr: Expressions.EXPR, saveExprContext: Boolean = false, @@ -172,6 +78,10 @@ object ExpressionCompiler { } } + private def compileExpr(expr: Expressions.EXPR): CompileM[(Terms.EXPR, FINAL, Expressions.EXPR, Iterable[CompilationError])] = + compileExprWithCtx(expr, allowIllFormedStrings = false) + .map(r => (r.expr, r.t, r.parseNodeExpr, r.errors)) + private def compileIf( p: Pos, condExpr: Expressions.EXPR, @@ -353,63 +263,7 @@ object ExpressionCompiler { } } yield result - private def exprContainsRef(expr: Expressions.EXPR, ref: String): Boolean = - expr match { - case Expressions.GETTER(_, expr, _, _, _, _) => - exprContainsRef(expr, ref) - - case Expressions.BLOCK(_, decl, body, _, _) => - val refIsOverlappedByDecl = - decl.name match { - case PART.VALID(_, name) if name == ref => true - case _ => false - } - if (refIsOverlappedByDecl) false - else { - val declContainsRef = - decl match { - case Expressions.LET(_, _, value, _, _) => - exprContainsRef(value, ref) - case Expressions.FUNC(_, expr, _, args) => - val refIsOverlappedByArg = - args.exists { - case (PART.VALID(_, name), _) if name == ref => true - case _ => false - } - if (!refIsOverlappedByArg) exprContainsRef(expr, ref) - else false - } - declContainsRef || exprContainsRef(body, ref) - } - - case Expressions.IF(_, cond, ifTrue, ifFalse, _, _) => - exprContainsRef(cond, ref) || - exprContainsRef(ifTrue, ref) || - exprContainsRef(ifFalse, ref) - - case Expressions.FUNCTION_CALL(_, _, args, _, _) => - args.exists(exprContainsRef(_, ref)) - - case Expressions.REF(_, PART.VALID(_, name), _, _) if name == ref => - true - - case BINARY_OP(_, a, _, b, _, _) => - exprContainsRef(a, ref) || exprContainsRef(b, ref) - - case Expressions.MATCH(_, matchingExpr, cases, _, _) => - exprContainsRef(matchingExpr, ref) || - cases.exists { - case MATCH_CASE(_, TypedVar(Some(PART.VALID(_, varName)), _), caseExpr, _, _) if varName != ref => - exprContainsRef(caseExpr, ref) - case MATCH_CASE(_, TypedVar(None, _), caseExpr, _, _) => - exprContainsRef(caseExpr, ref) - case _ => false - } - - case _ => false - } - - def compileBlock( + private def compileBlock( pos: Expressions.Pos, declaration: Expressions.Declaration, expr: Expressions.EXPR, @@ -439,7 +293,12 @@ object ExpressionCompiler { _.getBytes(StandardCharsets.UTF_8).length <= ContractLimits.MaxDeclarationNameInBytes ) - def compileLet(p: Pos, let: Expressions.LET, saveExprContext: Boolean, allowIllFormedStrings: Boolean): CompileM[CompilationStepResultDec] = + protected def compileLet( + p: Pos, + let: Expressions.LET, + saveExprContext: Boolean, + allowIllFormedStrings: Boolean + ): CompileM[CompilationStepResultDec] = for { _ <- checkDeclarationNameSize(p, let) letNameWithErr <- validateShadowing(p, let).handleError() @@ -458,7 +317,7 @@ object ExpressionCompiler { } } yield result - def compileFunc( + protected def compileFunc( p: Pos, func: Expressions.FUNC, saveExprContext: Boolean, @@ -508,10 +367,10 @@ object ExpressionCompiler { } yield (result, argTypesWithErr._1.map(_.map(nameAnfInfo => (nameAnfInfo._1, nameAnfInfo._2.vType))).getOrElse(List.empty)) } - def updateCtx(letName: String, letType: Types.FINAL, p: Pos): CompileM[Unit] = + protected def updateCtx(letName: String, letType: Types.FINAL, p: Pos): CompileM[Unit] = modify[Id, CompilerContext, CompilationError](vars.modify(_)(_ + (letName -> VariableInfo(p, letType)))) - def updateCtx(funcName: String, typeSig: FunctionTypeSignature, p: Pos): CompileM[Unit] = + protected def updateCtx(funcName: String, typeSig: FunctionTypeSignature, p: Pos): CompileM[Unit] = modify[Id, CompilerContext, CompilationError](functions.modify(_)(_ + (funcName -> FunctionInfo(p, List(typeSig))))) private def compileLetBlock( @@ -753,12 +612,7 @@ object ExpressionCompiler { } } - def mkIf(p: Pos, cond: EXPR, ifTrue: (EXPR, FINAL), ifFalse: (EXPR, FINAL)): Either[CompilationError, (EXPR, FINAL)] = { - val t = TypeInferrer.findCommonType(ifTrue._2, ifFalse._2) - (IF(cond, ifTrue._1, ifFalse._1), t).asRight - } - - def mkIfCases( + private def mkIfCases( cases: List[MATCH_CASE], caseTypes: List[FINAL], refTmp: Expressions.REF, @@ -840,7 +694,8 @@ object ExpressionCompiler { val typeIf = tTypes.foldLeft(isInst(hType))((other, matchType) => BINARY_OP(mc.position, isInst(matchType), BinaryOperation.OR_OP, other)) Right(makeIfCase(typeIf, blockWithNewVar, further)) - case Nil => ??? + case Nil => + ??? } } yield cases case (_: TypedVar, t) => @@ -849,51 +704,49 @@ object ExpressionCompiler { case (ConstsPat(consts, _), _) => val cond = consts .map(c => BINARY_OP(mc.position, c, BinaryOperation.EQ_OP, refTmp)) - .reduceRight((c, r) => BINARY_OP(mc.position, c, BinaryOperation.OR_OP, r)) + .reduceRight(BINARY_OP(mc.position, _, BinaryOperation.OR_OP, _)) Right(makeIfCase(cond, blockWithNewVar, further)) case (p: CompositePattern, _) => - val pos = p.position - val newRef = p.caseType.fold(refTmp)(t => refTmp.copy(resultType = Some(caseType))) - val conditions = makeConditionsFromCompositePattern(p, newRef) - val cond = if (conditions.isEmpty) { - Expressions.TRUE(pos): Expressions.EXPR - } else { - conditions.reduceRight { (c, r) => - BINARY_OP(pos, c, BinaryOperation.AND_OP, r): Expressions.EXPR + val pos = p.position + val newRef = p.caseType.fold(refTmp)(_ => refTmp.copy(resultType = Some(caseType))) + makeConditionsFromCompositePattern(ctx, p, newRef) + .map { conditions => + val cond = + if (conditions.isEmpty) + Expressions.TRUE(pos) + else + conditions.reduceRight(BINARY_OP(pos, _, BinaryOperation.AND_OP, _)) + val checkingCond = + if (p.isInstanceOf[TuplePat]) { + val (resolvedTypes, size) = resolveTypesFromCompositePattern(p) + if (p.patternsWithFields.size == size) { + val typeChecks = + resolvedTypes + .map(t => Expressions.FUNCTION_CALL(pos, PART.VALID(pos, IsInstanceOf), List(refTmp, Expressions.CONST_STRING(pos, t)))) + .reduceLeft[Expressions.EXPR] { case (c, r) => BINARY_OP(pos, c, BinaryOperation.OR_OP, r) } + BINARY_OP(pos, cond, BinaryOperation.AND_OP, typeChecks) + } else { + val size = Expressions.CONST_LONG(pos, p.patternsWithFields.size) + val getSize = Expressions.FUNCTION_CALL(pos, PART.VALID(pos, "size"), List(refTmp)) + val compareSize = BINARY_OP(pos, getSize, BinaryOperation.EQ_OP, size) + BINARY_OP(pos, cond, BinaryOperation.AND_OP, compareSize) + } + } else + cond + makeIfCase( + p.caseType.fold(checkingCond)(t => + BINARY_OP( + pos, + Expressions.FUNCTION_CALL(pos, PART.VALID(pos, IsInstanceOf), List(refTmp, Expressions.CONST_STRING(pos, t.name))), + BinaryOperation.AND_OP, + Expressions.BLOCK(pos, Expressions.LET(pos, newRef.key, newRef, Some(caseType), true), checkingCond) + ) + ), + blockWithNewVar, + further + ) } - } - val checkingCond = - if (p.isInstanceOf[TuplePat]) { - val (resolvedTypes, size) = resolveTypesFromCompositePattern(p) - if (p.patternsWithFields.size == size) { - val typeChecks = - resolvedTypes - .map(t => Expressions.FUNCTION_CALL(pos, PART.VALID(pos, IsInstanceOf), List(refTmp, Expressions.CONST_STRING(pos, t)))) - .reduceLeft[Expressions.EXPR] { case (c, r) => BINARY_OP(pos, c, BinaryOperation.OR_OP, r) } - BINARY_OP(pos, cond, BinaryOperation.AND_OP, typeChecks) - } else { - val size = Expressions.CONST_LONG(pos, p.patternsWithFields.size) - val getSize = Expressions.FUNCTION_CALL(pos, PART.VALID(pos, "size"), List(refTmp)) - val compareSize = BINARY_OP(pos, getSize, BinaryOperation.EQ_OP, size) - BINARY_OP(pos, cond, BinaryOperation.AND_OP, compareSize) - } - } else - cond - Right( - makeIfCase( - p.caseType.fold(checkingCond)(t => - BINARY_OP( - pos, - Expressions.FUNCTION_CALL(pos, PART.VALID(pos, IsInstanceOf), List(refTmp, Expressions.CONST_STRING(pos, t.name))), - BinaryOperation.AND_OP, - Expressions.BLOCK(pos, Expressions.LET(pos, newRef.key, newRef, Some(caseType), true), checkingCond) - ) - ), - blockWithNewVar, - further - ) - ) } } } @@ -923,47 +776,56 @@ object ExpressionCompiler { Expressions.GETTER(pos, exp, field, checkObjectType = false) } - private def makeConditionsFromCompositePattern(p: CompositePattern, newRef: Expressions.REF): Seq[Expressions.EXPR] = - p.subpatterns collect { + private def makeConditionsFromCompositePattern( + ctx: CompilerContext, + p: CompositePattern, + newRef: Expressions.REF + ): Either[Generic, Seq[Expressions.EXPR]] = + p.subpatterns.traverse { case (pat @ TypedVar(_, Expressions.Union(types)), path) if types.nonEmpty => val pos = pat.position val v = mkGet(path, newRef, pos) - types - .map { + val r = types + .collect { case Expressions.Single(t, None) => - Expressions.FUNCTION_CALL(pos, PART.VALID(pos, IsInstanceOf), List(v, Expressions.CONST_STRING(pos, t))): Expressions.EXPR + Expressions.FUNCTION_CALL(pos, PART.VALID(pos, IsInstanceOf), List(v, Expressions.CONST_STRING(pos, t))) case Expressions.Single(PART.VALID(pos, Type.ListTypeName), Some(PART.VALID(_, Expressions.AnyType(_)))) => val t = PART.VALID(pos, "List[Any]") - Expressions.FUNCTION_CALL( - pos, - PART.VALID(pos, IsInstanceOf), - List(v, Expressions.CONST_STRING(pos, t)) - ): Expressions.EXPR - case _ => ??? - } - .reduceRight[Expressions.EXPR] { (c, r) => - BINARY_OP(pos, c, BinaryOperation.OR_OP, r) + Expressions.FUNCTION_CALL(pos, PART.VALID(pos, IsInstanceOf), List(v, Expressions.CONST_STRING(pos, t))) } + .reduceRight[Expressions.EXPR](BINARY_OP(pos, _, BinaryOperation.OR_OP, _)) + Right(r) case (pat @ TypedVar(_, Expressions.Single(PART.VALID(_, Type.ListTypeName), Some(PART.VALID(_, Expressions.AnyType(_))))), path) => val pos = pat.position val v = mkGet(path, newRef, pos) val t = PART.VALID(pos, "List[Any]") - Expressions.FUNCTION_CALL(pos, PART.VALID(pos, IsInstanceOf), List(v, Expressions.CONST_STRING(pos, t))): Expressions.EXPR + val r = Expressions.FUNCTION_CALL(pos, PART.VALID(pos, IsInstanceOf), List(v, Expressions.CONST_STRING(pos, t))) + Right(r) case (pat @ TypedVar(_, Expressions.Single(t, None)), path) => val pos = pat.position val v = mkGet(path, newRef, pos) - Expressions.FUNCTION_CALL(pos, PART.VALID(pos, IsInstanceOf), List(v, Expressions.CONST_STRING(pos, t))): Expressions.EXPR - case (TypedVar(_, Expressions.Single(_, _)), _) => ??? + val r = Expressions.FUNCTION_CALL(pos, PART.VALID(pos, IsInstanceOf), List(v, Expressions.CONST_STRING(pos, t))) + Right(r) case (pat @ ConstsPat(consts, _), path) => val pos = pat.position val v = mkGet(path, newRef, pos) consts - .map { c => - BINARY_OP(pos, c, BinaryOperation.EQ_OP, v) - } - .reduceRight[BINARY_OP] { (c, r) => - BINARY_OP(pos, c, BinaryOperation.OR_OP, r) + .traverse { + case const @ ( + _: Expressions.CONST_LONG | _: Expressions.CONST_STRING | _: Expressions.CONST_BYTESTR | _: Expressions.TRUE | + _: Expressions.FALSE | _: Expressions.REF + ) => + Right(BINARY_OP(pos, const, BinaryOperation.EQ_OP, v)) + case func @ Expressions.FUNCTION_CALL(pos, PART.VALID(_, name), _, _, _) if ctx.predefTypes.contains(name) => + Right(BINARY_OP(pos, func, BinaryOperation.EQ_OP, v)) + case expr if version < V8 => + Right(BINARY_OP(pos, expr, BinaryOperation.EQ_OP, v)) + case expr => + Left(Generic(expr.position.start, expr.position.end, "Only constant value could be matched with object field")) } + .map(_.reduceRight(BINARY_OP(pos, _, BinaryOperation.OR_OP, _))) + case _ => + Right(Expressions.TRUE(newRef.position)) } private def resolveTypesFromCompositePattern(p: CompositePattern): (Seq[PART[String]], Int) = { @@ -1070,27 +932,102 @@ object ExpressionCompiler { types.toList .traverse(handleCompositeType(pos, _, expectedType, varName)) .map(types => TUPLE(types)) - case Expressions.AnyType(pos) => (ANY: FINAL).pure[CompileM] + case _: Expressions.AnyType => + (ANY: FINAL).pure[CompileM] } - def handlePart[T](part: PART[T]): CompileM[T] = part match { + protected def handlePart[T](part: PART[T]): CompileM[T] = part match { case PART.VALID(_, x) => x.pure[CompileM] case PART.INVALID(p, message) => raiseError(Generic(p.start, p.end, message)) } +} +object ExpressionCompiler { implicit class RichBoolean(val b: Boolean) extends AnyVal { final def toOption[A](a: => A): Option[A] = if (b) Some(a) else None } - def apply(c: CompilerContext, expr: Expressions.EXPR, allowIllFormedStrings: Boolean = false): Either[String, (EXPR, FINAL)] = - applyWithCtx(c, expr, allowIllFormedStrings).map(r => (r._2, r._3)) + def compileWithParseResult( + input: String, + offset: LibrariesOffset, + ctx: CompilerContext, + version: StdLibVersion, + saveExprContext: Boolean = true + ): Either[(String, Int, Int), (EXPR, Expressions.SCRIPT, Iterable[CompilationError])] = + new Parser(version)(offset) + .parseExpressionWithErrorRecovery(input) + .flatMap { case (parseResult, removedCharPosOpt) => + new ExpressionCompiler(version) + .compileExprWithCtx(parseResult.expr, saveExprContext, allowIllFormedStrings = false) + .run(ctx) + .value + ._2 + .map { compRes => + val errorList = + compRes.errors ++ + (if (compRes.t equivalent BOOLEAN) Nil else List(Generic(0, 0, "Script should return boolean"))) ++ + (if (removedCharPosOpt.isEmpty) + Nil + else + List( + Generic( + removedCharPosOpt.get.start, + removedCharPosOpt.get.end, + "Parsing failed. Some chars was removed as result of recovery process." + ) + )) + (compRes.expr, parseResult.copy(expr = compRes.parseNodeExpr), errorList) + } + .leftMap(e => (s"Compilation failed: ${Show[CompilationError].show(e)}", e.start, e.end)) + } + + def compile( + input: String, + offset: LibrariesOffset, + ctx: CompilerContext, + version: StdLibVersion, + allowIllFormedStrings: Boolean = false + ): Either[String, (EXPR, FINAL)] = { + val parser = new Parser(version)(offset) + parser.parseExpr(input) match { + case fastparse.Parsed.Success(expr, _) => apply(ctx, version, expr, allowIllFormedStrings) + case f: fastparse.Parsed.Failure => Left(parser.toString(input, f)) + } + } + + def compileBoolean(input: String, offset: LibrariesOffset, ctx: CompilerContext, version: StdLibVersion): Either[String, EXPR] = { + compile(input, offset, ctx, version).flatMap { + case (expr, BOOLEAN) => Right(expr) + case _ => Left("Script should return boolean") + } + } + + def compileUntyped(input: String, offset: LibrariesOffset, ctx: CompilerContext, version: StdLibVersion): Either[String, EXPR] = { + compile(input, offset, ctx, version) + .map { case (expr, _) => expr } + } + + def compileDecls(input: String, offset: LibrariesOffset, ctx: CompilerContext, version: StdLibVersion): Either[String, EXPR] = { + val adjustedDecls = s"$input\n${GlobalValNames.Unit}" + compileUntyped(adjustedDecls, offset, ctx, version) + } + + def apply( + c: CompilerContext, + version: StdLibVersion, + expr: Expressions.EXPR, + allowIllFormedStrings: Boolean = false + ): Either[String, (EXPR, FINAL)] = + applyWithCtx(c, version, expr, allowIllFormedStrings).map(r => (r._2, r._3)) def applyWithCtx( c: CompilerContext, + version: StdLibVersion, expr: Expressions.EXPR, allowIllFormedStrings: Boolean = false ): Either[String, (CompilerContext, EXPR, FINAL)] = - compileExprWithCtx(expr, allowIllFormedStrings = allowIllFormedStrings) + new ExpressionCompiler(version) + .compileExprWithCtx(expr, allowIllFormedStrings = allowIllFormedStrings) .run(c) .value ._2 diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/TypeCast.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/TypeCast.scala index 38b6207c03..eea379da39 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/TypeCast.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/TypeCast.scala @@ -1,7 +1,6 @@ package com.wavesplatform.lang.v1.compiler import com.wavesplatform.common.utils.* import com.wavesplatform.lang.v1.compiler.CompilationError.{GenericFunctionNotFound, TypeCastAllowedOnlyForGenericList} -import com.wavesplatform.lang.v1.compiler.ExpressionCompiler.CompilationStepResultExpr import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.compiler.Types.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.GlobalValNames diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ContractEvaluator.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ContractEvaluator.scala index 531b5e6576..f66b0202f1 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ContractEvaluator.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ContractEvaluator.scala @@ -119,7 +119,8 @@ object ContractEvaluator { limit: Int, correctFunctionCallScope: Boolean, newMode: Boolean, - enableExecutionLog: Boolean + enableExecutionLog: Boolean, + fixedThrownError: Boolean ): Coeval[Either[(ExecutionError, Int, Log[Id]), (ScriptResult, Log[Id])]] = Coeval .now(buildExprFromInvocation(dApp, i, version).leftMap((_, limit, Nil))) @@ -134,7 +135,8 @@ object ContractEvaluator { limit, correctFunctionCallScope, newMode, - enableExecutionLog + enableExecutionLog, + fixedThrownError ) case Left(error) => Coeval.now(Left(error)) } @@ -148,10 +150,21 @@ object ContractEvaluator { limit: Int, correctFunctionCallScope: Boolean, newMode: Boolean, - enableExecutionLog: Boolean + enableExecutionLog: Boolean, + fixedThrownError: Boolean ): Coeval[Either[(ExecutionError, Int, Log[Id]), (ScriptResult, Log[Id])]] = EvaluatorV2 - .applyLimitedCoeval(expr, logExtraInfo, limit, ctx, version, correctFunctionCallScope, newMode, enableExecutionLog = enableExecutionLog) + .applyLimitedCoeval( + expr, + logExtraInfo, + limit, + ctx, + version, + correctFunctionCallScope, + newMode, + enableExecutionLog = enableExecutionLog, + fixedThrownError = fixedThrownError + ) .map(_.flatMap { case (expr, unusedComplexity, log) => val result = expr match { diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV2.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV2.scala index aed5720e7c..77c16ee5f5 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV2.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/EvaluatorV2.scala @@ -9,20 +9,13 @@ import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.compiler.Types.CASETYPEREF import com.wavesplatform.lang.v1.evaluator.ContextfulNativeFunction.{Extended, Simple} -import com.wavesplatform.lang.v1.evaluator.ctx.{ - DisabledLogEvaluationContext, - EnabledLogEvaluationContext, - EvaluationContext, - LoggedEvaluationContext, - NativeFunction, - UserFunction -} import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo -import com.wavesplatform.lang.v1.evaluator.EvaluatorV2.logFunc import com.wavesplatform.lang.v1.evaluator.EvaluatorV2.LogKeys.* +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2.logFunc import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Bindings +import com.wavesplatform.lang.v1.evaluator.ctx.* import com.wavesplatform.lang.v1.traits.Environment -import com.wavesplatform.lang.{CommonError, ExecutionError} +import com.wavesplatform.lang.{CommonError, ExecutionError, ThrownError} import monix.eval.Coeval import shapeless.syntax.std.tuple.* @@ -32,10 +25,12 @@ import scala.collection.mutable.ListBuffer class EvaluatorV2( val ctx: LoggedEvaluationContext[Environment, Id], val stdLibVersion: StdLibVersion, + val limit: Int, val correctFunctionCallScope: Boolean, val newMode: Boolean, val enableExecutionLog: Boolean, - val checkConstructorArgsTypes: Boolean = false + val checkConstructorArgsTypes: Boolean, + val fixedThrownError: Boolean ) { private val overheadCost: Int = if (newMode) 0 else 1 @@ -110,13 +105,21 @@ class EvaluatorV2( evaluated } -> unusedComplexity } - case f: Simple[Environment] => Coeval((f.evaluate(ctx.ec.environment, args), limit - cost)) + case f: Simple[Environment] => + Coeval((f.evaluate(ctx.ec.environment, args), limit - cost)) } for { (result, unusedComplexity) <- EvaluationResult( evaluation .map { case (result, evaluatedComplexity) => - result.bimap((_, evaluatedComplexity), (_, evaluatedComplexity)) + result.bimap( + { + case e: ThrownError if !fixedThrownError && function.ev.isInstanceOf[Extended[Environment]] => (e, this.limit) + case e: ThrownError if !fixedThrownError => (e, 0) + case e => (e, evaluatedComplexity) + }, + (_, evaluatedComplexity) + ) } .onErrorHandleWith { case _: SecurityException => @@ -359,7 +362,8 @@ object EvaluatorV2 { correctFunctionCallScope: Boolean, newMode: Boolean, checkConstructorArgsTypes: Boolean = false, - enableExecutionLog: Boolean = false + enableExecutionLog: Boolean = false, + fixedThrownError: Boolean ): Coeval[Either[(ExecutionError, Int, Log[Id]), (EXPR, Int, Log[Id])]] = { val log = ListBuffer[LogItem[Id]]() @@ -370,7 +374,16 @@ object EvaluatorV2 { } var ref = expr.deepCopy.value logCall(loggedCtx, logExtraInfo, ref, enableExecutionLog) - new EvaluatorV2(loggedCtx, stdLibVersion, correctFunctionCallScope, newMode, enableExecutionLog, checkConstructorArgsTypes) + new EvaluatorV2( + loggedCtx, + stdLibVersion, + limit, + correctFunctionCallScope, + newMode, + enableExecutionLog, + checkConstructorArgsTypes, + fixedThrownError + ) .root(ref, v => EvaluationResult { ref = v }, limit, Nil) .map((ref, _)) .value @@ -389,7 +402,8 @@ object EvaluatorV2 { correctFunctionCallScope: Boolean, newMode: Boolean, handleExpr: EXPR => Either[ExecutionError, EVALUATED], - enableExecutionLog: Boolean + enableExecutionLog: Boolean, + fixedThrownError: Boolean ): (Log[Id], Int, Either[ExecutionError, EVALUATED]) = EvaluatorV2 .applyLimitedCoeval( @@ -400,7 +414,8 @@ object EvaluatorV2 { stdLibVersion, correctFunctionCallScope, newMode, - enableExecutionLog = enableExecutionLog + enableExecutionLog = enableExecutionLog, + fixedThrownError = fixedThrownError ) .value() .fold( @@ -420,7 +435,8 @@ object EvaluatorV2 { stdLibVersion: StdLibVersion, correctFunctionCallScope: Boolean, newMode: Boolean, - enableExecutionLog: Boolean + enableExecutionLog: Boolean, + fixedThrownError: Boolean ): (Log[Id], Int, Either[ExecutionError, EVALUATED]) = applyOrDefault( ctx, @@ -431,7 +447,8 @@ object EvaluatorV2 { correctFunctionCallScope, newMode, expr => Left(s"Unexpected incomplete evaluation result $expr"), - enableExecutionLog + enableExecutionLog, + fixedThrownError ) private def logCall( 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 4c5968fca6..52b0a4a6a9 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 @@ -4,8 +4,7 @@ import cats.implicits.* import cats.{Id, Monad} import com.wavesplatform.common.merkle.Merkle.createRoot import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.lang.{ExecutionError, CommonError} -import com.wavesplatform.lang.directives.values.{StdLibVersion, V3, *} +import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.compiler.Types.* import com.wavesplatform.lang.v1.compiler.{CompilerContext, Terms} @@ -15,8 +14,10 @@ import com.wavesplatform.lang.v1.evaluator.FunctionIds.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.crypto.RSA.DigestAlgorithm import com.wavesplatform.lang.v1.evaluator.ctx.{BaseFunction, EvaluationContext, NativeFunction} import com.wavesplatform.lang.v1.{BaseGlobal, CTX} +import com.wavesplatform.lang.{CommonError, ExecutionError, ThrownError} import scala.collection.mutable +import scala.util.Try object CryptoContext { @@ -30,7 +31,7 @@ object CryptoContext { UNION.create(rsaHashAlgs(v), if (v > V3 && v < V6) Some("RsaDigestAlgs") else None) private val rsaHashLib = { - import com.wavesplatform.lang.v1.evaluator.ctx.impl.crypto.RSA._ + import com.wavesplatform.lang.v1.evaluator.ctx.impl.crypto.RSA.* rsaTypeNames.zip(List(NONE, MD5, SHA1, SHA224, SHA256, SHA384, SHA512, SHA3224, SHA3256, SHA3384, SHA3512)).toMap } @@ -370,16 +371,16 @@ object CryptoContext { ("index", LONG) ) { case xs @ ARR(proof) :: CONST_BYTESTR(value) :: CONST_LONG(index) :: Nil => - 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)))) + val sizeCheckedProofs = proof.collect { case bs @ CONST_BYTESTR(v) if v.size == 32 => bs } + if (value.size == 32 && proof.length <= 16 && sizeCheckedProofs.size == proof.size) { + Try(createRoot(value.arr, Math.toIntExact(index), sizeCheckedProofs.reverse.map(_.bs.arr))) + .toEither + .leftMap(e => ThrownError(if (e.getMessage != null) e.getMessage else "error")) + .flatMap(r => CONST_BYTESTR(ByteStr(r))) } else { - notImplemented[Id, EVALUATED](s"createMerkleRoot(merkleProof: ByteVector, valueBytes: ByteVector)", xs) + notImplemented[Id, EVALUATED](s"createMerkleRoot(merkleProof: ByteVector, valueBytes: ByteVector, index: Int)", xs) } - case xs => notImplemented[Id, EVALUATED](s"createMerkleRoot(merkleProof: ByteVector, valueBytes: ByteVector)", xs) + case xs => notImplemented[Id, EVALUATED](s"createMerkleRoot(merkleProof: ByteVector, valueBytes: ByteVector, index: Int)", xs) } def toBase16StringF(checkLength: Boolean): BaseFunction[NoContext] = NativeFunction("toBase16String", 10, TOBASE16, STRING, ("bytes", BYTESTR)) { diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala index 0098d6b741..2fbb5dc0d9 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala @@ -42,7 +42,7 @@ object PureContext { val MaxListLengthV4 = 1000 // As an optimization, JVM might throw an ArithmeticException with empty stack trace and null message. - // The workaround below retrows an exception with the message explicitly set. + // The workaround below rethrows an exception with the message explicitly set. lazy val divLong: BaseFunction[NoContext] = createTryOp(DIV_OP, LONG, LONG, DIV_LONG) { (a, b) => try Math.floorDiv(a, b) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala index 0e955ab498..c12a726ef3 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala @@ -1,5 +1,7 @@ package com.wavesplatform.lang.v1.evaluator.ctx.impl.waves +import cats.data.EitherT +import cats.implicits.toTraverseOps import cats.syntax.applicative.* import cats.syntax.either.* import cats.syntax.functor.* @@ -11,17 +13,17 @@ import com.wavesplatform.lang.v1.FunctionHeader.{Native, User} import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.compiler.Types.* import com.wavesplatform.lang.v1.evaluator.FunctionIds.* +import com.wavesplatform.lang.v1.evaluator.ctx.impl.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.EnvironmentFunctions.AddressLength import com.wavesplatform.lang.v1.evaluator.ctx.impl.converters.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Bindings.{scriptTransfer as _, *} import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Types.* -import com.wavesplatform.lang.v1.evaluator.ctx.impl.* import com.wavesplatform.lang.v1.evaluator.ctx.{BaseFunction, NativeFunction, UserFunction} import com.wavesplatform.lang.v1.evaluator.{ContextfulNativeFunction, ContextfulUserFunction, FunctionIds, Log} import com.wavesplatform.lang.v1.traits.domain.{Issue, Lease, Recipient} import com.wavesplatform.lang.v1.traits.{DataType, Environment} import com.wavesplatform.lang.v1.{BaseGlobal, FunctionHeader} -import com.wavesplatform.lang.{CoevalF, CommonError, ExecutionError, FailOrRejectError} +import com.wavesplatform.lang.{CoevalF, CommonError, ExecutionError, FailOrRejectError, ThrownError} import monix.eval.Coeval import shapeless.Coproduct.unsafeGet @@ -411,11 +413,12 @@ object Functions { case xs => notImplemented[Id, EVALUATED](s"toString(a: Address)", xs) } - private def caseObjToRecipient(c: CaseObj): Recipient = c.caseType.name match { - case addressType.name => Recipient.Address(c.fields("bytes").asInstanceOf[CONST_BYTESTR].bs) - case aliasType.name => Recipient.Alias(c.fields("alias").asInstanceOf[CONST_STRING].s) - case t => throw new IllegalArgumentException(s"Unexpected recipient type $t") - } + private def caseObjToRecipient(c: CaseObj): Either[ExecutionError, Recipient] = + c.caseType.name match { + case addressType.name => Right(Recipient.Address(c.fields("bytes").asInstanceOf[CONST_BYTESTR].bs)) + case aliasType.name => Right(Recipient.Alias(c.fields("alias").asInstanceOf[CONST_STRING].s)) + case t => Left(ThrownError(s"Unexpected recipient type $t")) + } val assetBalanceF: BaseFunction[Environment] = NativeFunction.withEnvironment[Environment]( @@ -434,9 +437,17 @@ object Functions { override def evaluate[F[_]: Monad](env: Environment[F], args: List[EVALUATED]): F[Either[ExecutionError, EVALUATED]] = args match { case (c: CaseObj) :: u :: Nil if u == unit => - env.accountBalanceOf(caseObjToRecipient(c), None).map(_.map(CONST_LONG).leftMap(CommonError(_))) + caseObjToRecipient(c) + .fold( + _.asLeft[EVALUATED].pure[F], + r => env.accountBalanceOf(r, None).map(_.map(CONST_LONG).leftMap(CommonError(_))) + ) case (c: CaseObj) :: CONST_BYTESTR(assetId: ByteStr) :: Nil => - env.accountBalanceOf(caseObjToRecipient(c), Some(assetId.arr)).map(_.map(CONST_LONG).leftMap(CommonError(_))) + caseObjToRecipient(c) + .fold( + _.asLeft[EVALUATED].pure[F], + r => env.accountBalanceOf(r, Some(assetId.arr)).map(_.map(CONST_LONG).leftMap(CommonError(_))) + ) case xs => notImplemented[F, EVALUATED](s"assetBalance(a: Address|Alias, u: ByteVector|Unit)", xs) } @@ -456,7 +467,11 @@ object Functions { override def evaluate[F[_]: Monad](env: Environment[F], args: List[EVALUATED]): F[Either[ExecutionError, EVALUATED]] = args match { case (c: CaseObj) :: CONST_BYTESTR(assetId: ByteStr) :: Nil => - env.accountBalanceOf(caseObjToRecipient(c), Some(assetId.arr)).map(_.map(CONST_LONG).leftMap(CommonError(_))) + caseObjToRecipient(c) + .fold( + _.asLeft[EVALUATED].pure[F], + r => env.accountBalanceOf(r, Some(assetId.arr)).map(_.map(CONST_LONG).leftMap(CommonError(_))) + ) case xs => notImplemented[F, EVALUATED](s"assetBalance(a: Address|Alias, u: ByteVector)", xs) } @@ -475,20 +490,25 @@ object Functions { override def evaluate[F[_]: Monad](env: Environment[F], args: List[EVALUATED]): F[Either[ExecutionError, EVALUATED]] = args match { case (c: CaseObj) :: Nil => - env - .accountWavesBalanceOf(caseObjToRecipient(c)) - .map( - _.map(b => - CaseObj( - balanceDetailsType, - Map( - "available" -> CONST_LONG(b.available), - "regular" -> CONST_LONG(b.regular), - "generating" -> CONST_LONG(b.generating), - "effective" -> CONST_LONG(b.effective) + caseObjToRecipient(c) + .fold( + _.asLeft[EVALUATED].pure[F], + r => + env + .accountWavesBalanceOf(r) + .map( + _.map(b => + CaseObj( + balanceDetailsType, + Map( + "available" -> CONST_LONG(b.available), + "regular" -> CONST_LONG(b.regular), + "generating" -> CONST_LONG(b.generating), + "effective" -> CONST_LONG(b.effective) + ) + ) + ).leftMap(CommonError(_)) ) - ) - ).leftMap(CommonError(_)) ) case xs => notImplemented[F, EVALUATED](s"wavesBalance(a: Address|Alias)", xs) @@ -580,7 +600,7 @@ object Functions { Map[StdLibVersion, Long](V5 -> 75L), id, ANY, - ("dapp", addressOrAliasType), + ("dApp", addressOrAliasType), ("name", optionString), ("args", LIST(ANY)), ("payments", listPayment) @@ -588,59 +608,90 @@ object Functions { new ContextfulNativeFunction.Extended[Environment]( name, ANY, - Seq(("dapp", BYTESTR), ("name", STRING), ("args", LIST(ANY)), ("payments", listPayment)) + Seq(("dApp", BYTESTR), ("name", STRING), ("args", LIST(ANY)), ("payments", listPayment)) ) { override def evaluate[F[_]: Monad]( env: Environment[F], args: List[EVALUATED], availableComplexity: Int - )(implicit m: Monad[CoevalF[F, *]]): Coeval[F[(Either[ExecutionError, (EVALUATED, Log[F])], Int)]] = { - val dAppBytes = args match { - case (dApp: CaseObj) :: _ if dApp.caseType == addressType => - dApp.fields("bytes") match { - case CONST_BYTESTR(d) => d.pure[F] - case a => throw new IllegalArgumentException(s"Unexpected address bytes $a") + )(implicit monad: Monad[CoevalF[F, *]]): Coeval[F[(Either[ExecutionError, (EVALUATED, Log[F])], Int)]] = { + def thrown[R](message: String): F[Either[ExecutionError, R]] = + (ThrownError(message): ExecutionError).asLeft[R].pure[F] + + val processedArgs = for { + dAppBytes <- EitherT[F, ExecutionError, ByteStr]( + args match { + case (dApp: CaseObj) :: _ if dApp.caseType == addressType => + dApp.fields.get("bytes") match { + case Some(CONST_BYTESTR(d)) => d.asRight[ExecutionError].pure[F] + case a => thrown(s"Unexpected address bytes $a") + } + case (dApp: CaseObj) :: _ if dApp.caseType == aliasType => + dApp.fields.get("alias") match { + case Some(CONST_STRING(a)) => env.resolveAlias(a).map(_.bimap(ThrownError, _.bytes)) + case arg => thrown(s"Unexpected alias arg $arg") + } + case arg :: _ => + thrown(s"Unexpected recipient arg $arg") + case args => + thrown(s"Unexpected args $args") } - case (dApp: CaseObj) :: _ if dApp.caseType == aliasType => - (dApp.fields("alias"): @unchecked) match { - case CONST_STRING(a) => env.resolveAlias(a).map(_.explicitGet().bytes) + ) + name <- EitherT[F, ExecutionError, String]( + args match { + case _ :: CONST_STRING(name) :: _ => name.asRight[ExecutionError].pure[F] + case _ :: CaseObj(UNIT, _) :: _ => "default".asRight[ExecutionError].pure[F] + case _ :: arg :: _ => thrown(s"Unexpected name arg $arg") + case args => thrown(s"Unexpected args $args") } - case args => throw new IllegalArgumentException(s"Unexpected recipient args $args") - } - val name = args match { - case _ :: CONST_STRING(name) :: _ => name - case _ :: CaseObj(UNIT, _) :: _ => "default" - case args => throw new IllegalArgumentException(s"Unexpected input args $args") - } - args match { - case _ :: _ :: ARR(args) :: ARR(payments) :: Nil => - env - .callScript( - Recipient.Address(dAppBytes.asInstanceOf[ByteStr]), - name, - args.toList, - payments.map { - case p: CaseObj if p.caseType == paymentType => - (List("assetId", "amount").map(p.fields): @unchecked) match { - case CONST_BYTESTR(a) :: CONST_LONG(v) :: Nil => (Some(a.arr), v) - case CaseObj(UNIT, _) :: CONST_LONG(v) :: Nil => (None, v) + ) + payments <- EitherT[F, ExecutionError, Seq[(Option[Array[Byte]], Long)]]( + args match { + case _ :: _ :: _ :: ARR(payments) :: Nil => + (payments: Seq[EVALUATED]) + .traverse { + case p: CaseObj if p.caseType == paymentType => + List("assetId", "amount").flatMap(p.fields.get) match { + case CONST_BYTESTR(a) :: CONST_LONG(v) :: Nil => Right((Some(a.arr), v)) + case CaseObj(UNIT, _) :: CONST_LONG(v) :: Nil => Right((None, v)) + case args => Left(ThrownError(s"Unexpected payment args $args"): ExecutionError) + } + case arg => + Left(ThrownError(s"Unexpected payment arg $arg"): ExecutionError) + } + .pure[F] + case args => + (s"Unexpected args $args": ExecutionError).asLeft[Seq[(Option[Array[Byte]], Long)]].pure[F] + } + ) + } yield (dAppBytes, name, payments) + monad.flatMap(Coeval(processedArgs.value)) { + case Right((dAppBytes, name, payments)) => + args match { + case _ :: _ :: ARR(passedArgs) :: _ :: Nil => + env + .callScript( + Recipient.Address(dAppBytes), + name, + passedArgs.toList, + payments, + availableComplexity, + reentrant + ) + .map(_.map { case (result, spentComplexity) => + val mappedError = result.leftMap { + case reject: FailOrRejectError => reject + case other => CommonError("Nested invoke error", Some(other)): ExecutionError } - case arg => throw new IllegalArgumentException(s"Unexpected payment arg $arg") - }, - availableComplexity, - reentrant - ) - .map(_.map { case (result, spentComplexity) => - val mappedError = result.leftMap { - case reject: FailOrRejectError => reject - case other => CommonError("Nested invoke error", Some(other)) - } - (mappedError, availableComplexity - spentComplexity) - }) - case xs => - val err = - notImplemented[F, (EVALUATED, Log[F])](s"invoke(dApp: Address, function: String, args: List[Any], payments: List[Payment])", xs) - Coeval.now(err.map((_, 0))) + (mappedError, availableComplexity - spentComplexity) + }) + case xs => + val signature = "invoke(dApp: Address, func: String, args: List[Any], payments: List[Payment])" + val err = notImplemented[F, (EVALUATED, Log[F])](signature, xs) + Coeval.now(err.map((_, 0))) + } + case Left(error) => + (error.asLeft[(EVALUATED, Log[F])], availableComplexity).pure[F].pure[Coeval] } } } @@ -886,26 +937,27 @@ object Functions { override def evaluate[F[_]: Monad](env: Environment[F], args: List[EVALUATED]): F[Either[ExecutionError, EVALUATED]] = args match { case CaseObj(`leaseActionType`, fields) :: Nil => - val recipient = caseObjToRecipient(fields(FieldNames.LeaseRecipient).asInstanceOf[CaseObj]) - val r = recipient match { - case Recipient.Address(bytes) if bytes.arr.length > AddressLength => - Left(CommonError(s"Address bytes length=${bytes.arr.length} exceeds limit=$AddressLength"): ExecutionError) - case Recipient.Alias(name) if name.length > MaxAliasLength => - Left(CommonError(s"Alias name length=${name.length} exceeds limit=$MaxAliasLength"): ExecutionError) - case _ => - CONST_BYTESTR( - Lease.calculateId( - Lease( - recipient, - fields(FieldNames.LeaseAmount).asInstanceOf[CONST_LONG].t, - fields(FieldNames.LeaseNonce).asInstanceOf[CONST_LONG].t - ), - env.txId + caseObjToRecipient(fields(FieldNames.LeaseRecipient).asInstanceOf[CaseObj]) + .flatMap { + case Recipient.Address(bytes) if bytes.arr.length > AddressLength => + Left(CommonError(s"Address bytes length=${bytes.arr.length} exceeds limit=$AddressLength"): ExecutionError) + case Recipient.Alias(name) if name.length > MaxAliasLength => + Left(CommonError(s"Alias name length=${name.length} exceeds limit=$MaxAliasLength"): ExecutionError) + case recipient => + CONST_BYTESTR( + Lease.calculateId( + Lease( + recipient, + fields(FieldNames.LeaseAmount).asInstanceOf[CONST_LONG].t, + fields(FieldNames.LeaseNonce).asInstanceOf[CONST_LONG].t + ), + env.txId + ) ) - ) - } - r.pure[F] - case xs => notImplemented[F, EVALUATED](s"calculateLeaseId(l: Lease)", xs) + } + .pure[F] + case xs => + notImplemented[F, EVALUATED](s"calculateLeaseId(l: Lease)", xs) } } } @@ -929,13 +981,17 @@ object Functions { override def evaluate[F[_]: Monad](env: Environment[F], args: List[EVALUATED]): F[Either[ExecutionError, EVALUATED]] = args match { case List(addr: CaseObj) => - env - .accountScript(caseObjToRecipient(addr)) - .map( - _.map(si => CONST_BYTESTR(ByteStr(global.blake2b256(si.bytes().arr)))) - .getOrElse(Right(unit)) + caseObjToRecipient(addr) + .fold( + _.asLeft[EVALUATED].pure[F], + recipient => + env + .accountScript(recipient) + .map( + _.map(si => CONST_BYTESTR(ByteStr(global.blake2b256(si.bytes().arr)))) + .getOrElse(Right(unit)) + ) ) - case xs => notImplemented[F, EVALUATED](s"scriptHash(account: AddressOrAlias))", xs) } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/Parser.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/Parser.scala index 40cbc52a39..0a61ef2788 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/Parser.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/Parser.scala @@ -309,10 +309,11 @@ class Parser(stdLibVersion: StdLibVersion)(implicit offset: LibrariesOffset) { }) ).?.map(_.getOrElse(Union(Seq()))) + val objPatMin = if (stdLibVersion >= V8) 1 else 0 def pattern(implicit c: fastparse.P[Any]): P[Pattern] = (varDefP ~ comment ~ typesDefP).map { case (v, t) => TypedVar(v, t) } | (Index ~ "(" ~ pattern.rep(min = 2, sep = ",") ~/ ")" ~ Index).map(p => TuplePat(p._2, Pos(p._1, p._3))) | - (Index ~ anyVarName() ~ "(" ~ (anyVarName() ~ "=" ~ pattern).rep(sep = ",") ~ ")" ~ Index) + (Index ~ anyVarName() ~ "(" ~ (anyVarName() ~ "=" ~ pattern).rep(min = objPatMin, sep = ",") ~ ")" ~ Index) .map(p => ObjPat(p._3.map(kp => (PART.toOption(kp._1).get, kp._2)).toMap, Single(p._2, None), Pos(p._1, p._4))) | (Index ~ baseExpr.rep(min = 1, sep = "|") ~ Index).map(p => ConstsPat(p._2, Pos(p._1, p._3))) diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/ContractIntegrationTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/ContractIntegrationTest.scala index f4d504ccf7..f53431ea3e 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/ContractIntegrationTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/ContractIntegrationTest.scala @@ -175,7 +175,8 @@ class ContractIntegrationTest extends PropSpec with Inside { Int.MaxValue, correctFunctionCallScope = true, newMode = false, - enableExecutionLog = true + enableExecutionLog = true, + fixedThrownError = true ) .value() .leftMap { case (e, _, log) => (e, log) } @@ -190,7 +191,16 @@ class ContractIntegrationTest extends PropSpec with Inside { compiled.decs, compiled.verifierFuncOpt.get, EvaluatorV2 - .applyCompleted(ctx.evaluationContext(environment), _, _, V3, correctFunctionCallScope = true, newMode = false, enableExecutionLog = false), + .applyCompleted( + ctx.evaluationContext(environment), + _, + _, + V3, + correctFunctionCallScope = true, + newMode = false, + enableExecutionLog = false, + fixedThrownError = true + ), txObject ) ._3 diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala index 4985b44bba..1905a9fa6a 100755 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala @@ -93,7 +93,16 @@ class IntegrationTest extends PropSpec with Inside { val evalCtx = ctx.evaluationContext(env).asInstanceOf[EvaluationContext[Environment, Id]] compiled.flatMap(v => EvaluatorV2 - .applyCompleted(evalCtx, v._1, LogExtraInfo(), version, correctFunctionCallScope = true, newMode = true, enableExecutionLog = false) + .applyCompleted( + evalCtx, + v._1, + LogExtraInfo(), + version, + correctFunctionCallScope = true, + newMode = true, + enableExecutionLog = false, + fixedThrownError = true + ) ._3 .bimap(_.message, _.asInstanceOf[T]) ) diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/DecompilerTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/DecompilerTest.scala index 5874a51e30..d78e73802a 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/DecompilerTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/DecompilerTest.scala @@ -561,7 +561,7 @@ class DecompilerTest extends PropSpec { def compileExpr(code: String, v: StdLibVersion = V3): Either[String, (EXPR, TYPE)] = { val untyped = Parser.parseExpr(code).get.value - val typed = ExpressionCompiler(getTestContext(v).compilerContext, untyped) + val typed = ExpressionCompiler(getTestContext(v).compilerContext, v, untyped) typed } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ErrorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ErrorTest.scala index 34180e1189..e3c17978cc 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ErrorTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ErrorTest.scala @@ -3,7 +3,7 @@ package com.wavesplatform.lang.compiler import com.wavesplatform.common.state.ByteStr import com.wavesplatform.lang.Common.multiplierFunction import com.wavesplatform.lang.contract.DApp -import com.wavesplatform.lang.directives.values.V5 +import com.wavesplatform.lang.directives.values.{V3, V5} import com.wavesplatform.lang.v1.compiler.{ExpressionCompiler, TestCompiler} import com.wavesplatform.lang.v1.evaluator.ctx.impl.GlobalValNames import com.wavesplatform.lang.v1.parser.BinaryOperation.SUM_OP @@ -158,7 +158,7 @@ class ErrorTest extends PropSpec { private def errorTests(exprs: ((String, String), Expressions.EXPR)*): Unit = exprs.foreach { case ((label, error), input) => property(s"Error: $label") { - ExpressionCompiler(compilerContext, input) should produce(error) + ExpressionCompiler(compilerContext, V3, input) should produce(error) } } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerV1Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerV1Test.scala index 871e4ba353..db285b5f70 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerV1Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerV1Test.scala @@ -35,7 +35,7 @@ class ExpressionCompilerV1Test extends PropSpec { property("should infer generic function return type") { import com.wavesplatform.lang.v1.parser.Expressions.* - val v = ExpressionCompiler(compilerContext, FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, idT.name), List(CONST_LONG(AnyPos, 1)))).explicitGet() + val v = ExpressionCompiler(compilerContext, V3, FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, idT.name), List(CONST_LONG(AnyPos, 1)))).explicitGet() v._2 shouldBe LONG } @@ -44,6 +44,7 @@ class ExpressionCompilerV1Test extends PropSpec { val v = ExpressionCompiler( compilerContext, + V3, FUNCTION_CALL( AnyPos, PART.VALID(AnyPos, "getElement"), @@ -59,7 +60,7 @@ class ExpressionCompilerV1Test extends PropSpec { } val expectedResult = Right(LONG) - ExpressionCompiler(compilerContext, expr).map(_._2) match { + ExpressionCompiler(compilerContext, V3, expr).map(_._2) match { case Right(x) => Right(x) shouldBe expectedResult case e @ Left(_) => e shouldBe expectedResult } @@ -68,11 +69,11 @@ class ExpressionCompilerV1Test extends PropSpec { property("string limit") { val maxString = "a" * Terms.DataEntryValueMax val expr = Parser.parseExpr(s""" "$maxString" """).get.value - ExpressionCompiler(compilerContext, expr).map(_._1) shouldBe CONST_STRING(maxString) + ExpressionCompiler(compilerContext, V3, expr).map(_._1) shouldBe CONST_STRING(maxString) val tooBigString = maxString + "a" val expr2 = Parser.parseExpr(s""" "$tooBigString" """).get.value - ExpressionCompiler(compilerContext, expr2) should produce("String size=32768 exceeds 32767 bytes") + ExpressionCompiler(compilerContext, V3, expr2) should produce("String size=32768 exceeds 32767 bytes") } @@ -94,8 +95,8 @@ class ExpressionCompilerV1Test extends PropSpec { """.stripMargin Parser.parseExpr(script).get.value } - ExpressionCompiler(compilerContext, funcExpr) should produce(s"Function '$tooLongName' size = 256 bytes exceeds 255") - ExpressionCompiler(compilerContext, letExpr) should produce(s"Let '$tooLongName' size = 256 bytes exceeds 255") + ExpressionCompiler(compilerContext, V3, funcExpr) should produce(s"Function '$tooLongName' size = 256 bytes exceeds 255") + ExpressionCompiler(compilerContext, V3, letExpr) should produce(s"Let '$tooLongName' size = 256 bytes exceeds 255") } @@ -117,18 +118,18 @@ class ExpressionCompilerV1Test extends PropSpec { """.stripMargin Parser.parseExpr(script).get.value } - ExpressionCompiler(compilerContext, funcExpr) shouldBe Symbol("right") - ExpressionCompiler(compilerContext, letExpr) shouldBe Symbol("right") + ExpressionCompiler(compilerContext, V3, funcExpr) shouldBe Symbol("right") + ExpressionCompiler(compilerContext, V3, letExpr) shouldBe Symbol("right") } property("tuple type checks") { val script = """ ("a", true, 123, base58'aaaa')._3 == true """ val expr = Parser.parseExpr(script).get.value - ExpressionCompiler(compilerContextV4, expr) should produce("Can't match inferred types of T over Int, Boolean") + ExpressionCompiler(compilerContextV4, V4, expr) should produce("Can't match inferred types of T over Int, Boolean") val script2 = """ ("a", true, 123, base58'aaaa') == ("a", true, "b", base58'aaaa') """ val expr2 = Parser.parseExpr(script2).get.value - ExpressionCompiler(compilerContextV4, expr2) should produce( + ExpressionCompiler(compilerContextV4, V4, expr2) should produce( "Can't match inferred types of T over (String, Boolean, Int, ByteVector), (String, Boolean, String, ByteVector)" ) @@ -146,7 +147,7 @@ class ExpressionCompilerV1Test extends PropSpec { | (((v, q), (true, v)), q) == (((1, true), (q, q)), v) """.stripMargin val expr3 = Parser.parseExpr(script3).get.value - ExpressionCompiler(compilerContextV4, expr3) shouldBe Symbol("right") + ExpressionCompiler(compilerContextV4, V4, expr3) shouldBe Symbol("right") val script4 = """ @@ -156,7 +157,7 @@ class ExpressionCompilerV1Test extends PropSpec { | (((v, q), (true, v)), q) == (((1, true), (v, q)), v) """.stripMargin val expr4 = Parser.parseExpr(script4).get.value - ExpressionCompiler(compilerContextV4, expr4) should produce( + ExpressionCompiler(compilerContextV4, V4, expr4) should produce( "Can't match inferred types of T over " + "(((Int|String, Boolean|Int|String), (Boolean, Int|String)), Boolean|Int|String), " + "(((Int, Boolean), (Int|String, Boolean|Int|String)), Int|String) in 102-154" @@ -171,7 +172,7 @@ class ExpressionCompilerV1Test extends PropSpec { | a1 == b1 """.stripMargin val expr = Parser.parseExpr(script).get.value - ExpressionCompiler(compilerContextV4, expr) shouldBe Symbol("right") + ExpressionCompiler(compilerContextV4, V4, expr) shouldBe Symbol("right") } property("function with tuple args") { @@ -189,7 +190,7 @@ class ExpressionCompilerV1Test extends PropSpec { | """.stripMargin val expr = Parser.parseExpr(script).get.value - ExpressionCompiler(compilerContextV4, expr) shouldBe Symbol("right") + ExpressionCompiler(compilerContextV4, V4, expr) shouldBe Symbol("right") val script2 = """ @@ -200,7 +201,7 @@ class ExpressionCompilerV1Test extends PropSpec { | """.stripMargin val expr2 = Parser.parseExpr(script2).get.value - ExpressionCompiler(compilerContextV4, expr2) should produce( + ExpressionCompiler(compilerContextV4, V4, expr2) should produce( "Non-matching types: expected: (String, Boolean), actual: Boolean in 69-86" ) @@ -213,7 +214,7 @@ class ExpressionCompilerV1Test extends PropSpec { | """.stripMargin val expr3 = Parser.parseExpr(script3).get.value - ExpressionCompiler(compilerContextV4, expr3) should produce( + ExpressionCompiler(compilerContextV4, V4, expr3) should produce( "Non-matching types: expected: ((Int, String), Boolean)|(Int, String, Boolean), actual: (Int, String) in 73-84" ) } @@ -237,7 +238,7 @@ class ExpressionCompilerV1Test extends PropSpec { | """.stripMargin val expr = Parser.parseExpr(script).get.value - ExpressionCompiler(compilerContextV4, expr) shouldBe Symbol("right") + ExpressionCompiler(compilerContextV4, V4, expr) shouldBe Symbol("right") val script2 = """ @@ -249,7 +250,7 @@ class ExpressionCompilerV1Test extends PropSpec { | true """.stripMargin val expr2 = Parser.parseExpr(script2).get.value - ExpressionCompiler(compilerContextV4, expr2) should produce( + ExpressionCompiler(compilerContextV4, V4, expr2) should produce( "Matching not exhaustive: " + "possibleTypes are (Int, String), ((Boolean, Int), ByteVector), " + "while matched are (Int, String), (Boolean, Int, ByteVector)" @@ -274,7 +275,7 @@ class ExpressionCompilerV1Test extends PropSpec { | """.stripMargin val expr3 = Parser.parseExpr(script3).get.value - ExpressionCompiler(compilerContextV4, expr3) shouldBe Symbol("right") + ExpressionCompiler(compilerContextV4, V4, expr3) shouldBe Symbol("right") val script4 = """ @@ -288,7 +289,7 @@ class ExpressionCompilerV1Test extends PropSpec { | """.stripMargin val expr4 = Parser.parseExpr(script4).get.value - ExpressionCompiler(compilerContextV4, expr4) should produce( + ExpressionCompiler(compilerContextV4, V4, expr4) should produce( "Matching not exhaustive: " + "possibleTypes are (Int, String), (Boolean, String), (ByteVector, Boolean, (String, (Int, Boolean))|Int), " + "while matched are (Boolean|Int, String), (ByteVector, Boolean, Int), (ByteVector, Boolean, (String, Int, Boolean)) " + @@ -303,7 +304,7 @@ class ExpressionCompilerV1Test extends PropSpec { | } """.stripMargin val expr5 = Parser.parseExpr(script5).get.value - ExpressionCompiler(compilerContextV4, expr5) shouldBe Symbol("right") + ExpressionCompiler(compilerContextV4, V4, expr5) shouldBe Symbol("right") } property("JS API compile limit exceeding error") { @@ -330,6 +331,7 @@ class ExpressionCompilerV1Test extends PropSpec { def checkExtract(version: StdLibVersion) = ExpressionCompiler( getTestContext(version).compilerContext, + version, Parser.parseExpr(" extract(1) ").get.value ) @@ -358,7 +360,7 @@ class ExpressionCompilerV1Test extends PropSpec { DirectiveDictionary[StdLibVersion].all .foreach { version => - val result = ExpressionCompiler(getTestContext(version).compilerContext, Parser.parseExpr(expr).get.value) + val result = ExpressionCompiler(getTestContext(version).compilerContext, version, Parser.parseExpr(expr).get.value) if (version >= V4) result shouldBe Symbol("right") else @@ -390,7 +392,7 @@ class ExpressionCompilerV1Test extends PropSpec { version <- DirectiveDictionary[StdLibVersion].all scriptType <- DirectiveDictionary[ScriptType].all } { - val result = ExpressionCompiler(getTestContext(version, scriptType).compilerContext, expr(version, scriptType)) + val result = ExpressionCompiler(getTestContext(version, scriptType).compilerContext, version, expr(version, scriptType)) if (version < V5 || scriptType != Account) result.swap.getOrElse(???).split("Can't find a function").length shouldBe 9 else @@ -415,7 +417,7 @@ class ExpressionCompilerV1Test extends PropSpec { DirectiveDictionary[StdLibVersion].all .foreach { version => - val result = ExpressionCompiler(getTestContext(version).compilerContext, expr(version)) + val result = ExpressionCompiler(getTestContext(version).compilerContext, version, expr(version)) if (version < V5) result should produce( "Compilation failed: [" + @@ -446,7 +448,7 @@ class ExpressionCompilerV1Test extends PropSpec { DirectiveDictionary[StdLibVersion].all .foreach { version => - val result = ExpressionCompiler(getTestContext(version).compilerContext, expr(version)) + val result = ExpressionCompiler(getTestContext(version).compilerContext, version, expr(version)) if (version < V5) result shouldBe Symbol("right") else @@ -484,7 +486,7 @@ class ExpressionCompilerV1Test extends PropSpec { DirectiveDictionary[StdLibVersion].all .foreach { version => - ExpressionCompiler(getTestContext(version).compilerContext, expr(version)) shouldBe Symbol("right") + ExpressionCompiler(getTestContext(version).compilerContext, version, expr(version)) shouldBe Symbol("right") } } @@ -512,7 +514,7 @@ class ExpressionCompilerV1Test extends PropSpec { DirectiveDictionary[StdLibVersion].all .foreach { version => - val result = ExpressionCompiler(getTestContext(version).compilerContext, expr(version)) + val result = ExpressionCompiler(getTestContext(version).compilerContext, version, expr(version)) if (version < V5) result shouldBe Symbol("right") else { @@ -542,7 +544,7 @@ class ExpressionCompilerV1Test extends PropSpec { DirectiveDictionary[StdLibVersion].all .filter(_ >= V3) .foreach { version => - val result = ExpressionCompiler(getTestContext(version).compilerContext, expr(version)) + val result = ExpressionCompiler(getTestContext(version).compilerContext, version, expr(version)) val error = result.swap.getOrElse(???) error should include("Can't find a function 'invoke'") error should include("Can't find a function 'reentrantInvoke'") @@ -1149,7 +1151,7 @@ class ExpressionCompilerV1Test extends PropSpec { propertyName: String )(expr: Expressions.EXPR, expectedResult: Either[String, (EXPR, TYPE)] => org.scalatest.compatible.Assertion, ctx: CompilerContext): Unit = property(propertyName) { - val res = compiler.ExpressionCompiler(ctx, expr) + val res = compiler.ExpressionCompiler(ctx, V3, expr) expectedResult(res) } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ScriptPreprocessorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ScriptPreprocessorTest.scala index 8ce9d375f9..b953caabf5 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ScriptPreprocessorTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ScriptPreprocessorTest.scala @@ -6,7 +6,6 @@ import cats.kernel.Monoid import com.wavesplatform.lang.directives.values.V3 import com.wavesplatform.lang.directives.{Directive, DirectiveParser} import com.wavesplatform.lang.script.ScriptPreprocessor -import com.wavesplatform.lang.v1.CTX import com.wavesplatform.lang.v1.compiler.ExpressionCompiler import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BOOLEAN, EVALUATED} import com.wavesplatform.lang.v1.evaluator.Contextful.NoContext @@ -29,9 +28,9 @@ class ScriptPreprocessorTest extends PropSpec with ScriptGenParser { } yield r private def eval(code: String): Either[String, EVALUATED] = { - val untyped = Parser.parseExpr(code).get.value - val ctx: CTX[NoContext] = Monoid.combineAll(Seq(PureContext.build(V3, useNewPowPrecision = true))) - val typed = ExpressionCompiler(ctx.compilerContext, untyped) + val untyped = Parser.parseExpr(code).get.value + val ctx = Monoid.combineAll(Seq(PureContext.build(V3, useNewPowPrecision = true))) + val typed = ExpressionCompiler(ctx.compilerContext, V3, untyped) typed.flatMap(v => evaluator[EVALUATED](ctx.evaluationContext, v._1).leftMap(_.toString)) } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorTestBase.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorTestBase.scala index 5eae03001d..ed72f994c1 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorTestBase.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorTestBase.scala @@ -57,7 +57,7 @@ class ScriptEstimatorTestBase(estimators: ScriptEstimator*) extends PropSpec { protected def compile(code: String)(implicit version: StdLibVersion): EXPR = { val untyped = Parser.parseExpr(code).get.value - ExpressionCompiler(ctx.compilerContext, untyped).map(_._1).explicitGet() + ExpressionCompiler(ctx.compilerContext, version, untyped).map(_._1).explicitGet() } protected def estimate( diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/package.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/package.scala index 505857d05d..5f7bf55867 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/package.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/package.scala @@ -28,7 +28,8 @@ package object estimator { V3, correctFunctionCallScope = true, overhead, - enableExecutionLog = false + enableExecutionLog = false, + fixedThrownError = true ) def evaluatorV2AsEstimator(overhead: Boolean): ScriptEstimator = new ScriptEstimator { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorSpec.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorSpec.scala index 8a56d49c8e..80042803f6 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorSpec.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorSpec.scala @@ -1,18 +1,18 @@ package com.wavesplatform.lang.evaluator -import cats.implicits.* import cats.Id +import cats.implicits.* import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.lang.{Common, ExecutionError} -import com.wavesplatform.lang.directives.{DirectiveDictionary, DirectiveSet} import com.wavesplatform.lang.directives.values.* +import com.wavesplatform.lang.directives.{DirectiveDictionary, DirectiveSet} import com.wavesplatform.lang.utils.lazyContexts import com.wavesplatform.lang.v1.compiler.ExpressionCompiler import com.wavesplatform.lang.v1.compiler.Terms.{EVALUATED, EXPR} import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo import com.wavesplatform.lang.v1.evaluator.{EvaluatorV2, Log} -import com.wavesplatform.lang.v1.parser.Parser +import com.wavesplatform.lang.v1.parser.Parser.LibrariesOffset.NoLibraries import com.wavesplatform.lang.v1.testing.ScriptGen +import com.wavesplatform.lang.{Common, ExecutionError} import com.wavesplatform.test.PropSpec import org.scalatest.Inside import org.scalatest.exceptions.TestFailedException @@ -75,12 +75,20 @@ abstract class EvaluatorSpec extends PropSpec with ScriptGen with Inside { private def evalExpr(expr: EXPR, version: StdLibVersion, useNewPowPrecision: Boolean): (Log[Id], Int, Either[ExecutionError, EVALUATED]) = { val ctx = lazyContexts((DirectiveSet(version, Account, Expression).explicitGet(), useNewPowPrecision, true)).value() val evalCtx = ctx.evaluationContext(Common.emptyBlockchainEnvironment()) - EvaluatorV2.applyCompleted(evalCtx, expr, LogExtraInfo(), version, correctFunctionCallScope = true, newMode = true, enableExecutionLog = false) + EvaluatorV2.applyCompleted( + evalCtx, + expr, + LogExtraInfo(), + version, + correctFunctionCallScope = true, + newMode = true, + enableExecutionLog = false, + fixedThrownError = true + ) } - def compile(code: String, version: StdLibVersion): Either[String, EXPR] = { - val ctx = lazyContexts((DirectiveSet(version, Account, Expression).explicitGet(), true, true)).value() - val parsed = Parser.parseExpr(code).get.value - ExpressionCompiler(ctx.compilerContext, parsed, allowIllFormedStrings = true).map(_._1) + private def compile(code: String, version: StdLibVersion): Either[String, EXPR] = { + val ctx = lazyContexts((DirectiveSet(version, Account, Expression).explicitGet(), true, true)).value() + ExpressionCompiler.compile(code, NoLibraries, ctx.compilerContext, version, allowIllFormedStrings = true).map(_._1) } } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV1V2Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV1V2Test.scala index 3c0dbc8745..93359c2b6e 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV1V2Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV1V2Test.scala @@ -75,7 +75,8 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { implicitly[StdLibVersion], correctFunctionCallScope = true, newMode = true, - enableExecutionLog = false + enableExecutionLog = false, + fixedThrownError = true ) ._3 .asInstanceOf[Either[ExecutionError, T]] @@ -101,7 +102,8 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { implicitly[StdLibVersion], correctFunctionCallScope = true, newMode = true, - enableExecutionLog = true + enableExecutionLog = true, + fixedThrownError = true ) evaluatorV2Result shouldBe evaluatorV1Result.bimap(_._1, _._1) @@ -880,7 +882,7 @@ class EvaluatorV1V2Test extends PropSpec with EitherValues { evalPure[EVALUATED]( context.evaluationContext[Id], ExpressionCompiler - .apply(context.compilerContext, xs) + .apply(context.compilerContext, version, xs) .explicitGet() ._1 ) diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV2Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV2Test.scala index cd7b4ff324..c62dddc1cb 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV2Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV2Test.scala @@ -32,7 +32,8 @@ class EvaluatorV2Test extends PropSpec with Inside { ctx.evaluationContext(environment), version, correctFunctionCallScope = true, - newMode + newMode, + fixedThrownError = true ) .value() .bimap(_._1.message, { case (result, complexity, _) => (result, complexity) }) @@ -67,7 +68,7 @@ class EvaluatorV2Test extends PropSpec with Inside { private def compile(script: String): EXPR = { val parsed = Parser.parseExpr(script).get.value - ExpressionCompiler(ctx.compilerContext, parsed).explicitGet()._1 + ExpressionCompiler(ctx.compilerContext, version, parsed).explicitGet()._1 } property("multiple lets by step") { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/NestedPatternsTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/NestedPatternsTest.scala index 080aded4df..b33b757595 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/NestedPatternsTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/NestedPatternsTest.scala @@ -1,6 +1,7 @@ package com.wavesplatform.lang.evaluator -import com.wavesplatform.lang.directives.values.{StdLibVersion, V6} +import com.wavesplatform.lang.directives.values.{StdLibVersion, V6, V7, V8} import com.wavesplatform.lang.v1.compiler.Terms.CONST_BOOLEAN +import com.wavesplatform.test.produce class NestedPatternsTest extends EvaluatorSpec { implicit val v: StdLibVersion = V6 @@ -126,4 +127,58 @@ class NestedPatternsTest extends EvaluatorSpec { """.stripMargin ) shouldBe Right(CONST_BOOLEAN(true)) } + + property("restrictions from V8") { + val script1 = + s""" + | match Lease(Address(base58''), 1, 1) { + | case Lease(nonce = f()) => true + | case _ => false + | } + """.stripMargin + eval(script1)(V7, checkNext = false) shouldBe Right(CONST_BOOLEAN(true)) + eval(script1)(V8) should produce("Only constant value could be matched with object field in 63-66") + + val script2 = + s""" + | func f() = 777 + | match Lease(Address(base58''), 1, 1) { + | case Lease(nonce = f()) => true + | case _ => false + | } + """.stripMargin + eval(script2)(V7, checkNext = false) shouldBe Right(CONST_BOOLEAN(true)) + eval(script2)(V8) should produce("Only constant value could be matched with object field in 79-82") + + val script3 = + s""" + | func f() = 777 + | match Lease(Address(base58''), 1, 1) { + | case Lease(nonce = {f()}) => true + | case _ => false + | } + """.stripMargin + eval(script3)(V7, checkNext = false) shouldBe Right(CONST_BOOLEAN(false)) + eval(script3)(V8) should produce("Only constant value could be matched with object field in 80-83") + + val script4 = + s""" + | match Lease(Address(base58''), 1, 1) { + | case Lease(nonce = {1 + 2}) => true + | case _ => false + | } + """.stripMargin + eval(script4)(V7, checkNext = false) shouldBe Right(CONST_BOOLEAN(false)) + eval(script4)(V8) should produce("Only constant value could be matched with object field in 64-69") + + val script5 = + s""" + | match Lease(Address(base58''), 1, 1) { + | case Lease(nonce = {if (true) then 2 else 1}) => true + | case _ => false + | } + """.stripMargin + eval(script5)(V7, checkNext = false) shouldBe Right(CONST_BOOLEAN(false)) + eval(script5)(V8) should produce("Only constant value could be matched with object field in 64-87") + } } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/serde/SerdeTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/serde/SerdeTest.scala index 0d9c56bd73..8de49007e1 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/serde/SerdeTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/serde/SerdeTest.scala @@ -177,7 +177,7 @@ class SerdeTest extends FreeSpec { } private def roundTripTest(untypedExpr: Expressions.EXPR): Unit = { - val typedExpr = ExpressionCompiler(PureContext.build(V1, useNewPowPrecision = true).compilerContext, untypedExpr).map(_._1).explicitGet() + val typedExpr = ExpressionCompiler(PureContext.build(V1, useNewPowPrecision = true).compilerContext, V1, untypedExpr).map(_._1).explicitGet() roundTripTest(typedExpr) } diff --git a/lang/tests/src/test/scala/com/wavesplatform/utils/MerkleTest.scala b/lang/tests/src/test/scala/com/wavesplatform/utils/MerkleTest.scala index 23c3ae9942..102d83ba4d 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/utils/MerkleTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/utils/MerkleTest.scala @@ -109,8 +109,8 @@ class MerkleTest extends PropSpec { val untyped = Parser.parseExpr(code).get.value val ctx = lazyContexts((DirectiveSet(version, Account, Expression).explicitGet(), true, true))() val evalCtx = ctx.evaluationContext[Id](Common.emptyBlockchainEnvironment()) - val typed = ExpressionCompiler(ctx.compilerContext, untyped) - typed.flatMap(v => EvaluatorV2.applyCompleted(evalCtx, v._1, LogExtraInfo(), version, true, true, false)._3.leftMap(_.toString)) + val typed = ExpressionCompiler(ctx.compilerContext, V3, untyped) + typed.flatMap(v => EvaluatorV2.applyCompleted(evalCtx, v._1, LogExtraInfo(), version, true, true, false, true)._3.leftMap(_.toString)) } private def scriptSrc(root: Array[Byte], proof: Array[Byte], value: Array[Byte]): String = { diff --git a/lang/tests/src/test/scala/com/wavesplatform/utils/RSATest.scala b/lang/tests/src/test/scala/com/wavesplatform/utils/RSATest.scala index 0f15a68e63..570ff7ad77 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/utils/RSATest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/utils/RSATest.scala @@ -337,7 +337,7 @@ class RSATest extends PropSpec with BeforeAndAfterAll { private def eval[T <: EVALUATED](code: String, ctx: CTX[NoContext] = PureContext.build(V4, useNewPowPrecision = true) |+| CryptoContext.build(Global, V4)): Either[String, T] = { val untyped = Parser.parseExpr(code).get.value - val typed = ExpressionCompiler(ctx.compilerContext, untyped) + val typed = ExpressionCompiler(ctx.compilerContext, V4, untyped) typed.flatMap(v => evaluator[T](ctx.evaluationContext, v._1).leftMap(_.message)) } diff --git a/node-it/src/test/scala/com/wavesplatform/it/api/AsyncGrpcApi.scala b/node-it/src/test/scala/com/wavesplatform/it/api/AsyncGrpcApi.scala index 828fe00f0d..1c5b31a595 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/api/AsyncGrpcApi.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/api/AsyncGrpcApi.scala @@ -1,17 +1,11 @@ package com.wavesplatform.it.api import com.google.common.primitives.{Bytes, Longs} - -import java.util.NoSuchElementException - -import scala.concurrent.Future -import scala.concurrent.duration.* - import com.google.protobuf.ByteString import com.google.protobuf.empty.Empty import com.wavesplatform.account.{AddressScheme, Alias, KeyPair} -import com.wavesplatform.api.grpc.{TransactionStatus as PBTransactionStatus, *} import com.wavesplatform.api.grpc.BalanceResponse.WavesBalances +import com.wavesplatform.api.grpc.{TransactionStatus as PBTransactionStatus, *} import com.wavesplatform.common.utils.{Base58, EitherExt2} import com.wavesplatform.crypto import com.wavesplatform.it.Node @@ -26,16 +20,19 @@ import com.wavesplatform.protobuf.Amount import com.wavesplatform.protobuf.block.PBBlocks import com.wavesplatform.protobuf.utils.PBUtils import com.wavesplatform.serialization.Deser -import com.wavesplatform.transaction.{Asset, TxVersion} import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.transaction.assets.exchange.Order +import com.wavesplatform.transaction.{Asset, TxVersion} import io.grpc.stub.StreamObserver import monix.eval.Task import monix.execution.Scheduler import monix.reactive.subjects.ConcurrentSubject import play.api.libs.json.Json +import scala.concurrent.Future +import scala.concurrent.duration.* + object AsyncGrpcApi { implicit class NodeAsyncGrpcApi(val n: Node) { @@ -211,7 +208,7 @@ object AsyncGrpcApi { def putData( source: KeyPair, - data: Seq[DataTransactionData.DataEntry], + data: Seq[DataEntry], fee: Long, version: Int = 1, timestamp: Long = System.currentTimeMillis() diff --git a/node-it/src/test/scala/com/wavesplatform/it/api/AsyncHttpApi.scala b/node-it/src/test/scala/com/wavesplatform/it/api/AsyncHttpApi.scala index 0dacbcb697..d2d26afa7b 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/api/AsyncHttpApi.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/api/AsyncHttpApi.scala @@ -23,7 +23,7 @@ import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms import com.wavesplatform.lang.v1.compiler.Terms.FUNCTION_CALL import com.wavesplatform.state.DataEntry.Format -import com.wavesplatform.state.{AssetDistributionPage, DataEntry, EmptyDataEntry, LeaseBalance, Portfolio} +import com.wavesplatform.state.{AssetDistribution, AssetDistributionPage, DataEntry, EmptyDataEntry, LeaseBalance, Portfolio} import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.assets.* import com.wavesplatform.transaction.assets.exchange.{Order, ExchangeTransaction as ExchangeTx} @@ -338,6 +338,11 @@ object AsyncHttpApi extends Assertions { get(url, amountsAsStrings).as[AssetDistributionPage](amountsAsStrings) } + def assetDistribution(asset: String, amountsAsStrings: Boolean = false): Future[AssetDistribution] = { + val req = s"/assets/$asset/distribution" + get(req, amountsAsStrings).as[AssetDistribution](amountsAsStrings) + } + def effectiveBalance(address: String, confirmations: Option[Int] = None, amountsAsStrings: Boolean = false): Future[Balance] = { val maybeConfirmations = confirmations.fold("")(a => s"/$a") get(s"/addresses/effectiveBalance/$address$maybeConfirmations", amountsAsStrings).as[Balance](amountsAsStrings) diff --git a/node-it/src/test/scala/com/wavesplatform/it/api/AsyncNetworkApi.scala b/node-it/src/test/scala/com/wavesplatform/it/api/AsyncNetworkApi.scala index fc7c065dcf..a1a881d92e 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/api/AsyncNetworkApi.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/api/AsyncNetworkApi.scala @@ -18,8 +18,11 @@ object AsyncNetworkApi { s"it-client-to-${node.name}", nonce ) - sender.connect(node.networkAddress).map { ch => - if (ch.isActive) sender.send(ch, messages*).map(_ => sender.close()) else sender.close() + sender.connect(node.networkAddress).flatMap { ch => + if (ch.isActive) sender.send(ch, messages*).map(_ => sender.close()) else { + sender.close() + Future.failed(new Exception("Channel is inactive")) + } } } } diff --git a/node-it/src/test/scala/com/wavesplatform/it/api/SyncGrpcApi.scala b/node-it/src/test/scala/com/wavesplatform/it/api/SyncGrpcApi.scala index ce4224cd46..4cbd1b94b7 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/api/SyncGrpcApi.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/api/SyncGrpcApi.scala @@ -165,7 +165,7 @@ object SyncGrpcApi extends Assertions { def putData( source: KeyPair, - data: Seq[DataTransactionData.DataEntry], + data: Seq[DataEntry], fee: Long, version: Int = 1, timestamp: Long = System.currentTimeMillis(), @@ -279,11 +279,11 @@ object SyncGrpcApi extends Assertions { maybeWaitForTransaction(sync(async(n).setAssetScript(sender, assetId, script, fee, timestamp, version)), waitForTx) } - def getDataByKey(address: ByteString, key: String): List[DataTransactionData.DataEntry] = { + def getDataByKey(address: ByteString, key: String): List[DataEntry] = { accounts.getDataEntries(DataRequest.of(address, key)).toList.map(res => res.getEntry) } - def getData(address: ByteString): List[DataTransactionData.DataEntry] = { + def getData(address: ByteString): List[DataEntry] = { accounts.getDataEntries(DataRequest(address)).toList.map(res => res.getEntry) } diff --git a/node-it/src/test/scala/com/wavesplatform/it/api/SyncHttpApi.scala b/node-it/src/test/scala/com/wavesplatform/it/api/SyncHttpApi.scala index 2a090fa609..e9ab3c5ecb 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/api/SyncHttpApi.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/api/SyncHttpApi.scala @@ -14,7 +14,7 @@ import com.wavesplatform.it.Node import com.wavesplatform.it.sync.* import com.wavesplatform.lang.script.v1.ExprScript import com.wavesplatform.lang.v1.compiler.Terms -import com.wavesplatform.state.{AssetDistributionPage, DataEntry} +import com.wavesplatform.state.{AssetDistribution, AssetDistributionPage, DataEntry} import com.wavesplatform.transaction.assets.exchange.Order import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction} import com.wavesplatform.transaction.smart.InvokeScriptTransaction @@ -261,6 +261,9 @@ object SyncHttpApi extends Assertions with matchers.should.Matchers { ): AssetDistributionPage = sync(async(n).assetDistributionAtHeight(asset, height, limit, maybeAfter, amountsAsStrings)) + def assetDistribution(asset: String): AssetDistribution = + sync(async(n).assetDistribution(asset)) + def broadcastIssue( source: KeyPair, name: String, diff --git a/node-it/src/test/scala/com/wavesplatform/it/asset/IssueReissueBurnAssetSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/asset/IssueReissueBurnAssetSuite.scala index 227c2b1325..355adaa7d9 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/asset/IssueReissueBurnAssetSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/asset/IssueReissueBurnAssetSuite.scala @@ -250,15 +250,15 @@ class IssueReissueBurnAssetSuite extends BaseFreeSpec { val acc = createDapp(script(simpleReissuableAsset)) val asset = issueValidated(acc, simpleReissuableAsset) invokeScript(acc, "transferAndBurn", assetId = asset, count = 100) - val height1 = nodes.waitForHeightArise() - sender.assetDistributionAtHeight(asset, height1 - 1, 10).items.map { case (a, v) => a.toString -> v } shouldBe Map( + nodes.waitForHeightArise() + sender.assetDistribution(asset).map { case (a, v) => a.toString -> v } shouldBe Map( miner.address -> 100L, acc.toAddress.toString -> (simpleReissuableAsset.quantity - 200) ) reissue(acc, CallableMethod, asset, 400, reissuable = false) invokeScript(acc, "transferAndBurn", assetId = asset, count = 100) - val height2 = nodes.waitForHeightArise() - sender.assetDistributionAtHeight(asset, height2 - 1, 10).items.map { case (a, v) => a.toString -> v } shouldBe Map( + nodes.waitForHeightArise() + sender.assetDistribution(asset).map { case (a, v) => a.toString -> v } shouldBe Map( miner.address -> 200L, acc.toAddress.toString -> simpleReissuableAsset.quantity ) diff --git a/node-it/src/test/scala/com/wavesplatform/it/async/MicroblocksGenerationSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/async/MicroblocksGenerationSuite.scala index f14a72b6c5..c84c1be859 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/async/MicroblocksGenerationSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/async/MicroblocksGenerationSuite.scala @@ -22,10 +22,7 @@ class MicroblocksGenerationSuite extends BaseFreeSpec with TransferSending { block <- miner.blockAt(2) } yield { block.transactions.size shouldBe maxTxs - - val blockTxs = block.transactions.map(_.id) - val diff = uploadedTxs.map(_.id).toSet -- blockTxs - diff shouldBe empty + block.transactions.map(_.id) should contain theSameElementsAs uploadedTxs.map(_.id).toSet }, 3.minutes ) diff --git a/node-it/src/test/scala/com/wavesplatform/it/async/WideStateGenerationSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/async/WideStateGenerationSuite.scala index 2ef4a1f2e2..a968861617 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/async/WideStateGenerationSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/async/WideStateGenerationSuite.scala @@ -1,14 +1,13 @@ package com.wavesplatform.it.async -import java.util.concurrent.TimeoutException - import com.typesafe.config.{Config, ConfigFactory} -import com.wavesplatform.it._ -import com.wavesplatform.it.api.AsyncHttpApi._ -import com.wavesplatform.it.util._ +import com.wavesplatform.it.* +import com.wavesplatform.it.api.AsyncHttpApi.* +import com.wavesplatform.it.util.* +import java.util.concurrent.TimeoutException import scala.concurrent.Future.traverse -import scala.concurrent.duration._ +import scala.concurrent.duration.* import scala.concurrent.{Await, Future} @LoadTest @@ -66,15 +65,14 @@ class WideStateGenerationSuite extends BaseFreeSpec with WaitForHeight2 with Tra } yield () val limit = GlobalTimer.instance.schedule(Future.failed(new TimeoutException("Time is out for test")), 18.minutes) - val testWithDumps = Future.firstCompletedOf(Seq(test, limit)).recoverWith { - case e => - for { - _ <- dumpBalances() - dumps <- traverse(nodes)(dumpBlockChain) - } yield { - log.debug(dumps.mkString("Dumps:\n", "\n\n", "\n")) - throw e - } + val testWithDumps = Future.firstCompletedOf(Seq(test, limit)).recoverWith { case e => + for { + _ <- dumpBalances() + dumps <- traverse(nodes)(dumpBlockChain) + } yield { + log.debug(dumps.mkString("Dumps:\n", "\n\n", "\n")) + throw e + } } Await.result(testWithDumps, 18.minutes) @@ -84,12 +82,9 @@ class WideStateGenerationSuite extends BaseFreeSpec with WaitForHeight2 with Tra for { height <- node.height blocks <- node.blockSeq(1, height) - } yield { + } yield withClue(s"all transactions in node") { val txsInBlockchain = blocks.flatMap(_.transactions.map(_.id)) - val diff = txIds -- txsInBlockchain - withClue(s"all transactions in node") { - diff shouldBe empty - } + txIds -- txsInBlockchain shouldBe empty } } @@ -99,7 +94,7 @@ class WideStateGenerationSuite extends BaseFreeSpec with WaitForHeight2 with Tra }.map(_.toMap) .map { r => log.debug(s"""Balances: - |${r.map { case (config, balance) => s"${config.getString("address")} -> $balance" }.mkString("\n")}""".stripMargin) + |${r.map { case (config, balance) => s"${config.getString("address")} -> $balance" }.mkString("\n")}""".stripMargin) r } } diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/AssetDistributionSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/AssetDistributionSuite.scala index 2355523ffa..d8c99c2c07 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/AssetDistributionSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/AssetDistributionSuite.scala @@ -8,6 +8,8 @@ import com.wavesplatform.state.AssetDistributionPage import com.wavesplatform.transaction.transfer.MassTransferTransaction import org.scalatest.CancelAfterFailure +import scala.concurrent.duration.* + class AssetDistributionSuite extends BaseTransactionSuite with CancelAfterFailure { lazy val node: Node = nodes.head @@ -23,7 +25,7 @@ class AssetDistributionSuite extends BaseTransactionSuite with CancelAfterFailur nodes.waitForHeightArise() - val issueTx = node.issue(issuer, "TestCoin", "no description", issueAmount, 8, false, issueFee, waitForTx = true).id + val issueTx = node.issue(issuer, "TestCoin", "no description", issueAmount, 8, reissuable = false, issueFee, waitForTx = true).id node.massTransfer( issuer, @@ -47,6 +49,8 @@ class AssetDistributionSuite extends BaseTransactionSuite with CancelAfterFailur val issuerAssetDis = assetDis.view.filterKeys(_ == issuer.toAddress).values + assetDis should be equals node.assetDistribution(issueTx) + issuerAssetDis.size shouldBe 1 issuerAssetDis.head shouldBe (issueAmount - addresses.length * transferAmount) @@ -68,10 +72,34 @@ class AssetDistributionSuite extends BaseTransactionSuite with CancelAfterFailur ) } + test("'Asset distribution' works properly") { + val receivers = for (i <- 0 until 10) yield KeyPair(s"receiver#$i".getBytes("UTF-8")) + + val issueTx = node.issue(issuer, "TestCoin#2", "no description", issueAmount, 8, reissuable = false, issueFee, waitForTx = true).id + + node + .massTransfer( + issuer, + receivers.map(rc => MassTransferTransaction.Transfer(rc.toAddress.toString, 10)).toList, + minFee + minFee * receivers.length, + assetId = Some(issueTx), + waitForTx = true + ) + + nodes.waitForHeightArise() + + val distribution = node.assetDistribution(issueTx) + + distribution.size shouldBe (receivers.size + 1) + distribution(issuer.toAddress) shouldBe (issueAmount - 10 * receivers.length) + + assert(receivers.forall(rc => distribution(rc.toAddress) == 10), "Distribution correct") + } + test("Correct last page and entry count") { val receivers = for (i <- 0 until 50) yield KeyPair(s"receiver#$i".getBytes("UTF-8")) - val issueTx = node.issue(issuer, "TestCoin#2", "no description", issueAmount, 8, false, issueFee, waitForTx = true).id + val issueTx = node.issue(issuer, "TestCoin#2", "no description", issueAmount, 8, reissuable = false, issueFee, waitForTx = true).id node .massTransfer( @@ -96,6 +124,24 @@ class AssetDistributionSuite extends BaseTransactionSuite with CancelAfterFailur assert(pages.map(_.items.size).sum == 51) } + test("Unlimited list") { + val assetId = node.issue(issuer, "TestCoin#2", "no description", issueAmount, 8, reissuable = false, issueFee, waitForTx = true).id + + val receivers = for (i <- 0 until 2000) yield KeyPair(s"receiver#$i".getBytes("UTF-8")) + + val transfers = receivers.map { r => MassTransferTransaction.Transfer(r.toAddress.toString, 10L) }.toList + + transfers.grouped(100).foreach { t => + node.massTransfer(issuer, t, minFee + t.length * minFee, assetId = Some(assetId)) + } + + node.waitFor("empty utx")(_.utxSize, (_: Int) == 0, 1 second) + nodes.waitForHeightArise() + + val list = node.assetDistribution(assetId) + list should have size 2001 + } + def distributionPages(asset: String, height: Int, limit: Int): List[AssetDistributionPage] = { def _load(acc: List[AssetDistributionPage], maybeAfter: Option[String]): List[AssetDistributionPage] = { val page = node.assetDistributionAtHeight(asset, height, limit, maybeAfter) diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/BlockChallengeSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/BlockChallengeSuite.scala index 05d16dbecf..e6a8eccb07 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/BlockChallengeSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/BlockChallengeSuite.scala @@ -6,19 +6,18 @@ import com.wavesplatform.block.Block import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.consensus.FairPoSCalculator -import com.wavesplatform.{TestValues, crypto} import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.it.api.Block as ApiBlock -import com.wavesplatform.it.{BaseFunSuite, Node, NodeConfigs, TransferSending} import com.wavesplatform.it.api.AsyncNetworkApi.NodeAsyncNetworkApi +import com.wavesplatform.it.api.Block as ApiBlock import com.wavesplatform.it.api.SyncHttpApi.* +import com.wavesplatform.it.{BaseFunSuite, Node, NodeConfigs, TransferSending} import com.wavesplatform.lang.directives.values.V8 import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.network.RawBytes import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.assets.exchange.OrderType -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer -import com.wavesplatform.transaction.{Transaction, TxHelpers, TxNonNegativeAmount} +import com.wavesplatform.transaction.{Transaction, TxHelpers} +import com.wavesplatform.{TestValues, crypto} import scala.concurrent.Await import scala.concurrent.duration.* @@ -30,10 +29,10 @@ class BlockChallengeSuite extends BaseFunSuite with TransferSending { .overrideBase(_.minAssetInfoUpdateInterval(0)) .overrideBase( _.preactivatedFeatures( - BlockchainFeatures.SynchronousCalls.id.toInt -> 0, - BlockchainFeatures.RideV6.id.toInt -> 0, - BlockchainFeatures.ConsensusImprovements.id.toInt -> 0, - BlockchainFeatures.LightNode.id.toInt -> 0 + BlockchainFeatures.SynchronousCalls.id.toInt -> 0, + BlockchainFeatures.RideV6.id.toInt -> 0, + BlockchainFeatures.ConsensusImprovements.id.toInt -> 0, + BlockchainFeatures.LightNode.id.toInt -> 0 ) ) .withDefault(1) @@ -166,7 +165,7 @@ class BlockChallengeSuite extends BaseFunSuite with TransferSending { TxHelpers .massTransfer( sender.keyPair, - Seq(ParsedTransfer(challenger.keyPair.toAddress, TxNonNegativeAmount.unsafeFrom(1))), + Seq(challenger.keyPair.toAddress -> 1), fee = TestValues.fee ), TxHelpers.reissue(issue.asset, sender.keyPair), diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/activation/AcceptFailedScriptActivationSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/activation/AcceptFailedScriptActivationSuite.scala index e1c4026784..78100e76a0 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/activation/AcceptFailedScriptActivationSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/activation/AcceptFailedScriptActivationSuite.scala @@ -300,7 +300,7 @@ class AcceptFailedScriptActivationSuite extends BaseTransactionSuite with NTPTim smartMatcherFee, 100L, ts, - ts + Order.MaxLiveTime, + ts + 2.days.toMillis, smartMatcherFee ) .explicitGet() @@ -314,7 +314,7 @@ class AcceptFailedScriptActivationSuite extends BaseTransactionSuite with NTPTim smartMatcherFee, 100L, ts, - ts + Order.MaxLiveTime, + ts + 2.days.toMillis, smartMatcherFee ) .explicitGet() @@ -375,7 +375,7 @@ class AcceptFailedScriptActivationSuite extends BaseTransactionSuite with NTPTim 10L, 100L, ts, - ts + Order.MaxLiveTime, + ts + 2.days.toMillis, smartMatcherFee, matcherFeeAssetId = IssuedAsset(ByteStr.decodeBase58(feeAsset).get) ) @@ -390,7 +390,7 @@ class AcceptFailedScriptActivationSuite extends BaseTransactionSuite with NTPTim 10L, 100L, ts, - ts + Order.MaxLiveTime, + ts + 2.days.toMillis, smartMatcherFee, matcherFeeAssetId = IssuedAsset(ByteStr.decodeBase58(feeAsset).get) ) diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/DataTransactionGrpcSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/DataTransactionGrpcSuite.scala index 113c71ad2b..ef40d5457f 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/DataTransactionGrpcSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/DataTransactionGrpcSuite.scala @@ -3,18 +3,17 @@ package com.wavesplatform.it.sync.grpc import com.google.protobuf.ByteString import com.wavesplatform.account.{Address, KeyPair} import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.it.api.SyncGrpcApi._ +import com.wavesplatform.it.api.SyncGrpcApi.* import com.wavesplatform.it.sync.{dataTxSupportedVersions, minFee} -import com.wavesplatform.test._ import com.wavesplatform.lang.v1.estimator.ScriptEstimatorV1 -import com.wavesplatform.protobuf.transaction.DataTransactionData.DataEntry -import com.wavesplatform.protobuf.transaction.{DataTransactionData, PBRecipients, PBTransactions, Recipient} +import com.wavesplatform.protobuf.transaction.* import com.wavesplatform.state.StringDataEntry +import com.wavesplatform.test.* import com.wavesplatform.transaction.smart.script.ScriptCompiler import com.wavesplatform.transaction.{DataTransaction, TxVersion} import io.grpc.Status.Code -import scala.concurrent.duration._ +import scala.concurrent.duration.* class DataTransactionGrpcSuite extends GrpcBaseTransactionSuite { diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/FailedTransactionGrpcSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/FailedTransactionGrpcSuite.scala index dbc782c9b1..f90a6aa789 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/FailedTransactionGrpcSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/FailedTransactionGrpcSuite.scala @@ -12,7 +12,7 @@ import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms.FUNCTION_CALL import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 import com.wavesplatform.protobuf.Amount -import com.wavesplatform.protobuf.transaction.DataTransactionData.DataEntry +import com.wavesplatform.protobuf.transaction.DataEntry import com.wavesplatform.protobuf.transaction.{PBRecipients, PBSignedTransaction, PBTransactions, Recipient} import com.wavesplatform.state.{BinaryDataEntry, StringDataEntry} import com.wavesplatform.test.* @@ -121,31 +121,6 @@ class FailedTransactionGrpcSuite extends GrpcBaseTransactionSuite with FailedTra sender.setScript(contract, Right(Some(script)), setScriptFee, waitForTx = true) } - test("InvokeScriptTransaction: insufficient action fees propagates failed transaction") { - val invokeFee = 0.005.waves - val setAssetScriptMinFee = setAssetScriptFee + smartFee * 2 - val priorityFee = setAssetScriptMinFee + invokeFee - - updateAssetScript(result = true, smartAsset, contract, setAssetScriptMinFee) - - for (typeName <- Seq("transfer", "issue", "reissue", "burn")) { - updateTikTok("unknown", setAssetScriptMinFee) - - overflowBlock() - sendTxsAndThenPriorityTx( - _ => - sender - .broadcastInvokeScript( - caller, - Recipient().withPublicKeyHash(contractAddr), - Some(FUNCTION_CALL(FunctionHeader.User("tikTok"), List.empty)), - fee = invokeFee - ), - () => updateTikTok(typeName, priorityFee, waitForTx = false) - )((txs, _) => assertFailedTxs(txs)) - } - } - test("InvokeScriptTransaction: invoke script error in payment asset propagates failed transaction") { val invokeFee = 0.005.waves + smartFee val setAssetScriptMinFee = setAssetScriptFee + smartFee diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/InvokeScriptTransactionGrpcSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/InvokeScriptTransactionGrpcSuite.scala index 6bbe813c69..d025f584de 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/InvokeScriptTransactionGrpcSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/InvokeScriptTransactionGrpcSuite.scala @@ -5,17 +5,16 @@ import com.typesafe.config.Config import com.wavesplatform.account.{Address, KeyPair} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.it.api.SyncGrpcApi._ -import com.wavesplatform.it.sync._ -import com.wavesplatform.it.sync.smartcontract.invokeScrTxSupportedVersions import com.wavesplatform.it.NodeConfigs +import com.wavesplatform.it.api.SyncGrpcApi.* +import com.wavesplatform.it.sync.* +import com.wavesplatform.it.sync.smartcontract.invokeScrTxSupportedVersions import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BYTESTR, FUNCTION_CALL} import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 -import com.wavesplatform.protobuf.transaction.{PBRecipients, PBTransactions, Recipient} -import com.wavesplatform.protobuf.transaction.DataTransactionData.DataEntry -import com.wavesplatform.test._ +import com.wavesplatform.protobuf.transaction.{DataEntry, PBRecipients, PBTransactions, Recipient} +import com.wavesplatform.test.* import com.wavesplatform.transaction.TxVersion import com.wavesplatform.transaction.smart.script.ScriptCompiler import io.grpc.Status.Code diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeMultiplePaymentsSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeMultiplePaymentsSuite.scala index 98360c47a8..c79de4138c 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeMultiplePaymentsSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/InvokeMultiplePaymentsSuite.scala @@ -44,7 +44,7 @@ class InvokeMultiplePaymentsSuite extends BaseTransactionSuite with CancelAfterF sender.balance(callerAddress).balance shouldBe callerBalance - smartMinFee } - test("script should sheck if alias not exists") { + test("script should sheck if alias not exist") { val alias = "unknown" assertBadRequestAndMessage( diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/freecall/InvokeExpressionSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/freecall/InvokeExpressionSuite.scala index e1d67fd94c..297ee0c681 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/freecall/InvokeExpressionSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/freecall/InvokeExpressionSuite.scala @@ -5,14 +5,13 @@ import com.wavesplatform.api.http.ApiError.StateCheckFailed import com.wavesplatform.features.BlockchainFeatures.ContinuationTransaction import com.wavesplatform.it.NodeConfigs import com.wavesplatform.it.NodeConfigs.Default -import com.wavesplatform.it.api.{PutDataResponse, StateChangesDetails, Transaction, TransactionInfo} import com.wavesplatform.it.api.SyncHttpApi.* +import com.wavesplatform.it.api.{PutDataResponse, StateChangesDetails, Transaction, TransactionInfo} import com.wavesplatform.it.sync.invokeExpressionFee import com.wavesplatform.it.transactions.BaseTransactionSuite import com.wavesplatform.lang.directives.values.V6 import com.wavesplatform.lang.script.v1.ExprScript import com.wavesplatform.lang.v1.compiler.TestCompiler -import com.wavesplatform.transaction.smart.InvokeExpressionTransaction import org.scalatest.{Assertion, CancelAfterFailure} class InvokeExpressionSuite extends BaseTransactionSuite with CancelAfterFailure { @@ -55,7 +54,7 @@ class InvokeExpressionSuite extends BaseTransactionSuite with CancelAfterFailure } test("reject on illegal fields") { - val unsupportedVersion = InvokeExpressionTransaction.supportedVersions.max + 1 + val unsupportedVersion = 4 assertApiError( sender.invokeExpression(firstKeyPair, expr, version = unsupportedVersion.toByte), AssertiveApiError(StateCheckFailed.Id, s"Transaction version $unsupportedVersion has not been activated yet", matchMessage = true) diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuite.scala index 067c5d1237..1c086beb23 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/transactions/FailedTransactionSuite.scala @@ -3,15 +3,12 @@ package com.wavesplatform.it.sync.transactions import com.typesafe.config.Config import com.wavesplatform.api.http.ApiError.TransactionNotAllowedByAssetScript import com.wavesplatform.api.http.DebugMessage -import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.it.api.SyncHttpApi.* -import com.wavesplatform.it.api.{StateChanges, TransactionStatus} import com.wavesplatform.it.sync.* import com.wavesplatform.it.transactions.BaseTransactionSuite -import com.wavesplatform.lang.v1.compiler.Terms import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 -import com.wavesplatform.state.{BooleanDataEntry, StringDataEntry} +import com.wavesplatform.state.StringDataEntry import com.wavesplatform.test.* import com.wavesplatform.transaction.assets.exchange.AssetPair import com.wavesplatform.transaction.smart.script.ScriptCompiler @@ -118,52 +115,6 @@ class FailedTransactionSuite extends BaseTransactionSuite with CancelAfterFailur sender.setScript(contract, Some(script), setScriptFee, waitForTx = true).id } - test("InvokeScriptTransaction: insufficient action fees propagates failed transaction") { - val invokeFee = 0.005.waves - val setAssetScriptMinFee = setAssetScriptFee + smartFee - val priorityFee = setAssetScriptMinFee + invokeFee - - updateAssetScript(result = true, smartAsset, contract, setAssetScriptMinFee) - - for (typeName <- Seq("transfer", "issue", "reissue", "burn")) { - updateTikTok("unknown", setAssetScriptMinFee) - - val prevBalance = sender.balance(caller.toAddress.toString).balance - val prevAssetBalance = sender.assetBalance(contractAddress, smartAsset) - val prevAssets = sender.assetsBalance(contractAddress) - - overflowBlock() - sendTxsAndThenPriorityTx( - _ => sender.invokeScript(caller, contractAddress, Some("tikTok"), fee = invokeFee)._1.id, - () => updateTikTok(typeName, priorityFee, waitForTx = false) - ) { (txs, priorityTx) => - logPriorityTx(priorityTx) - - val failed = assertFailedTxs(txs) - - sender.balance(caller.toAddress.toString).balance shouldBe prevBalance - txs.size * invokeFee - sender.assetBalance(contractAddress, smartAsset) shouldBe prevAssetBalance - sender.assetsBalance(contractAddress).balances should contain theSameElementsAs prevAssets.balances - - val (scriptInvokedInfo, issuedInfo) = - if (typeName == "issue") - ("", " with 1 assets issued") - else - (" with 1 total scripts invoked", "") - - val minFee = if (typeName == "issue") invokeFee + issueFee else invokeFee + smartFee - val text = s"Fee in WAVES for InvokeScriptTransaction ($invokeFee in WAVES)" + - s"$scriptInvokedInfo$issuedInfo does not exceed minimal value of $minFee WAVES." - - failed.foreach { s => - checkStateChange(sender.stateChanges(s.id), 2, text) - } - - failed - } - } - } - test("InvokeScriptTransaction: reject transactions if account script failed") { val invokeFee = 0.005.waves val setAssetScriptMinFee = setAssetScriptFee + smartFee @@ -212,50 +163,6 @@ class FailedTransactionSuite extends BaseTransactionSuite with CancelAfterFailur } } - test("InvokeScriptTransaction: transactionHeightById returns only succeed transactions") { - val invokeFee = 0.005.waves + smartFee - val setAssetScriptMinFee = setAssetScriptFee + smartFee - val priorityFee = setAssetScriptMinFee + invokeFee - - updateAccountScript(None, caller, setScriptFee + smartFee) - updateTikTok("reissue", setAssetScriptMinFee) - updateAssetScript(result = true, smartAsset, contract, setAssetScriptMinFee) - waitForEmptyUtx() - overflowBlock() - - val failedTxs = sendTxsAndThenPriorityTx( - _ => sender.invokeScript(caller, contractAddress, Some("tikTok"), fee = invokeFee)._1.id, - () => updateAssetScript(result = false, smartAsset, contract, priorityFee) - ) { (txs, priorityTx) => - logPriorityTx(priorityTx) - assertFailedTxs(txs) - } - - checkTransactionHeightById(failedTxs) - } - - test("ExchangeTransaction: transaction validates as failed when asset script fails") { - val Precondition(amountAsset, priceAsset, buyFeeAsset, sellFeeAsset) = - exchangePreconditions( - Some(ScriptCompiler.compile("true", ScriptEstimatorV3(fixOverflow = true, overhead = false)).explicitGet()._1.bytes().base64) - ) - - val assetPair = AssetPair.createAssetPair(amountAsset, priceAsset).get - val fee = 0.003.waves + 4 * smartFee - val sellMatcherFee = fee / 100000L - val buyMatcherFee = fee / 100000L - - val (assetScript, _) = - ScriptCompiler.compile("if true then throw(\"error\") else false", ScriptEstimatorV3(fixOverflow = true, overhead = false)).explicitGet() - val scriptTx = sender.setAssetScript(priceAsset, buyerAddress, script = Some(assetScript.bytes().base64)) - nodes.waitForHeightAriseAndTxPresent(scriptTx.id) - - val tx = mkExchange(buyer, seller, matcher, assetPair, fee, buyFeeAsset, sellFeeAsset, buyMatcherFee, sellMatcherFee) - val result = sender.signedValidate(tx.json()) - (result \ "valid").as[Boolean] shouldBe false - (result \ "error").as[String] should include("not allowed by script of the asset") - } - test("ExchangeTransaction: invalid exchange tx when asset script fails on broadcast") { val init = Seq( sender.setScript(firstKeyPair, None, setScriptFee + smartFee).id, @@ -309,38 +216,6 @@ class FailedTransactionSuite extends BaseTransactionSuite with CancelAfterFailur private def waitForTxs(txs: Seq[String]): Unit = nodes.waitFor("preconditions", 500.millis)(_.transactionStatus(txs).forall(_.status == "confirmed"))(_.forall(identity)) - private def checkStateChange(info: StateChanges, code: Int, text: String, strict: Boolean = false): Unit = { - info.stateChanges shouldBe defined - info.stateChanges.get.issues.size shouldBe 0 - info.stateChanges.get.reissues.size shouldBe 0 - info.stateChanges.get.burns.size shouldBe 0 - info.stateChanges.get.error shouldBe defined - info.stateChanges.get.error.get.code shouldBe code - if (strict) - info.stateChanges.get.error.get.text shouldBe text - else - info.stateChanges.get.error.get.text should include(text) - } - - private def checkTransactionHeightById(failedTxs: Seq[TransactionStatus]): Unit = { - val defineTxs = failedTxs.map { status => - sender - .invokeScript( - caller, - contractAddress, - Some("defineTxHeight"), - List(Terms.CONST_BYTESTR(ByteStr.decodeBase58(status.id).get).explicitGet()), - fee = invokeFee - ) - ._1 - .id - } - - waitForTxs(defineTxs) - - failedTxs.foreach(status => sender.getDataByKey(contractAddress, status.id) shouldBe BooleanDataEntry(status.id, value = false)) - } - private def exchangePreconditions(initScript: Option[String]): Precondition = { val transfers = Seq( sender.transfer(sender.keyPair, sellerAddress.toAddress.toString, 100.waves).id, diff --git a/node/src/main/protobuf/waves/database.proto b/node/src/main/protobuf/waves/database.proto index 5df4ad0be0..2e723e4f7a 100644 --- a/node/src/main/protobuf/waves/database.proto +++ b/node/src/main/protobuf/waves/database.proto @@ -7,7 +7,6 @@ import "waves/transaction.proto"; import "waves/amount.proto"; import "waves/recipient.proto"; import "waves/transaction_state_snapshot.proto"; -import "google/protobuf/empty.proto"; message AssetDetails { bytes name = 1; @@ -112,8 +111,7 @@ message LeaseDetails { bytes source_id = 4; int32 height = 5; - oneof status { - google.protobuf.Empty active = 10; + oneof cancel_reason { Cancelled cancelled = 11; Expired expired = 12; } diff --git a/node/src/main/resources/swagger-ui/openapi.yaml b/node/src/main/resources/swagger-ui/openapi.yaml index 14fead51bc..b8fcd02eb0 100644 --- a/node/src/main/resources/swagger-ui/openapi.yaml +++ b/node/src/main/resources/swagger-ui/openapi.yaml @@ -2440,6 +2440,38 @@ paths: type: string balance: type: string + '/assets/{assetId}/distribution': + get: + tags: + - assets + summary: Asset balance distribution + description: Get asset balance distribution by addresses + operationId: getAssetDistributionOld + parameters: + - $ref: '#/components/parameters/assetId' + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int64 + description: map of assetId <-> balance + example: + 2eEUvypDSivnzPiLrbYEW39SM8yMZ1aq4eJuiKfs4sEY: 15 + 3PPqZ623dAfbmxmnpTjwV6yD5GA5s3PJiUG: 25 + application/json;large-significand-format=string: + schema: + type: object + additionalProperties: + type: string + description: map of assetId <-> balance + example: + 2eEUvypDSivnzPiLrbYEW39SM8yMZ1aq4eJuiKfs4sEY: "15" + 3PPqZ623dAfbmxmnpTjwV6yD5GA5s3PJiUG: "25" '/assets/{assetId}/distribution/{height}/limit/{limit}': get: tags: diff --git a/node/src/main/scala/com/wavesplatform/Application.scala b/node/src/main/scala/com/wavesplatform/Application.scala index b56ce25655..74625fabfb 100644 --- a/node/src/main/scala/com/wavesplatform/Application.scala +++ b/node/src/main/scala/com/wavesplatform/Application.scala @@ -374,9 +374,8 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con else heavyRequestExecutor ) - val routeTimeout = new RouteTimeout( - FiniteDuration(settings.config.getDuration("akka.http.server.request-timeout").getSeconds, TimeUnit.SECONDS) - )(heavyRequestScheduler) + val serverRequestTimeout = FiniteDuration(settings.config.getDuration("akka.http.server.request-timeout").getSeconds, TimeUnit.SECONDS) + val routeTimeout = new RouteTimeout(serverRequestTimeout)(heavyRequestScheduler) val apiRoutes = Seq( new EthRpcRoute(blockchainUpdater, extensionContext.transactionsApi, time), @@ -440,8 +439,8 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con ), AssetsApiRoute( settings.restAPISettings, + serverRequestTimeout, wallet, - transactionPublisher, blockchainUpdater, () => blockchainUpdater.snapshotBlockchain, time, diff --git a/node/src/main/scala/com/wavesplatform/Explorer.scala b/node/src/main/scala/com/wavesplatform/Explorer.scala index 141e608def..f034331b1e 100644 --- a/node/src/main/scala/com/wavesplatform/Explorer.scala +++ b/node/src/main/scala/com/wavesplatform/Explorer.scala @@ -12,7 +12,7 @@ import com.wavesplatform.lang.script.ContractScript import com.wavesplatform.lang.script.v1.ExprScript import com.wavesplatform.settings.Constants import com.wavesplatform.state.diffs.{DiffsCommon, SetScriptTransactionDiff} -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.state.{Blockchain, Height, Portfolio, StateSnapshot, TransactionId} import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.utils.ScorexLogging diff --git a/node/src/main/scala/com/wavesplatform/Importer.scala b/node/src/main/scala/com/wavesplatform/Importer.scala index b59eff7a73..77cfd7efce 100644 --- a/node/src/main/scala/com/wavesplatform/Importer.scala +++ b/node/src/main/scala/com/wavesplatform/Importer.scala @@ -7,7 +7,7 @@ import com.google.common.io.ByteStreams import com.google.common.primitives.Ints import com.wavesplatform.Exporter.Formats import com.wavesplatform.api.common.{CommonAccountsApi, CommonAssetsApi, CommonBlocksApi, CommonTransactionsApi} -import com.wavesplatform.block.{Block, BlockHeader, BlockSnapshot} +import com.wavesplatform.block.{Block, BlockHeader} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.consensus.PoSSelector import com.wavesplatform.database.{DBExt, KeyTags, RDB} @@ -17,13 +17,14 @@ import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.history.StorageFactory import com.wavesplatform.lang.ValidationError import com.wavesplatform.mining.Miner +import com.wavesplatform.network.BlockSnapshotResponse import com.wavesplatform.protobuf.block.{PBBlocks, VanillaBlock} import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot import com.wavesplatform.settings.WavesSettings import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult import com.wavesplatform.state.ParSignatureChecker.sigverify import com.wavesplatform.state.appender.BlockAppender -import com.wavesplatform.state.{Blockchain, BlockchainUpdaterImpl, Height, ParSignatureChecker, StateSnapshot, TxMeta} +import com.wavesplatform.state.{Blockchain, BlockchainUpdaterImpl, Height, ParSignatureChecker} import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.smart.script.trace.TracedResult import com.wavesplatform.transaction.{DiscardedBlocks, Transaction} @@ -47,7 +48,7 @@ import scala.util.{Failure, Success, Try} object Importer extends ScorexLogging { - type AppendBlock = (Block, Option[BlockSnapshot]) => Task[Either[ValidationError, BlockApplyResult]] + type AppendBlock = (Block, Option[BlockSnapshotResponse]) => Task[Either[ValidationError, BlockApplyResult]] final case class ImportOptions( configFile: Option[File] = None, @@ -214,10 +215,10 @@ object Importer extends ScorexLogging { } val maxSize = importOptions.maxQueueSize - val queue = new mutable.Queue[(VanillaBlock, Option[BlockSnapshot])](maxSize) + val queue = new mutable.Queue[(VanillaBlock, Option[BlockSnapshotResponse])](maxSize) @tailrec - def readBlocks(queue: mutable.Queue[(VanillaBlock, Option[BlockSnapshot])], remainCount: Int, maxCount: Int): Unit = { + def readBlocks(queue: mutable.Queue[(VanillaBlock, Option[BlockSnapshotResponse])], remainCount: Int, maxCount: Int): Unit = { if (remainCount == 0) () else { val blockSizeBytesLength = ByteStreams.read(blocksInputStream, lenBlockBytes, 0, Ints.BYTES) @@ -252,14 +253,12 @@ object Importer extends ScorexLogging { val block = (if (!blockV5) Block.parseBytes(blockBytes) else parsedProtoBlock).orElse(parsedProtoBlock).get val blockSnapshot = snapshotsBytes.map { bytes => - BlockSnapshot( + BlockSnapshotResponse( block.id(), block.transactionData - .foldLeft((0, Seq.empty[(StateSnapshot, TxMeta.Status)])) { case ((offset, acc), _) => + .foldLeft((0, Seq.empty[TransactionStateSnapshot])) { case ((offset, acc), _) => val txSnapshotSize = Ints.fromByteArray(bytes.slice(offset, offset + Ints.BYTES)) - val txSnapshot = StateSnapshot.fromProtobuf( - TransactionStateSnapshot.parseFrom(bytes.slice(offset + Ints.BYTES, offset + Ints.BYTES + txSnapshotSize)) - ) + val txSnapshot = TransactionStateSnapshot.parseFrom(bytes.slice(offset + Ints.BYTES, offset + Ints.BYTES + txSnapshotSize)) (offset + Ints.BYTES + txSnapshotSize, txSnapshot +: acc) } ._2 @@ -352,7 +351,7 @@ object Importer extends ScorexLogging { StorageFactory(settings, rdb, time, BlockchainUpdateTriggers.combined(triggers)) val utxPool = new UtxPoolImpl(time, blockchainUpdater, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable) val pos = PoSSelector(blockchainUpdater, settings.synchronizationSettings.maxBaseTarget) - val extAppender: (Block, Option[BlockSnapshot]) => Task[Either[ValidationError, BlockApplyResult]] = + val extAppender: (Block, Option[BlockSnapshotResponse]) => Task[Either[ValidationError, BlockApplyResult]] = BlockAppender(blockchainUpdater, time, utxPool, pos, scheduler, importOptions.verify, txSignParCheck = false) val extensions = initExtensions(settings, blockchainUpdater, scheduler, time, utxPool, rdb, actorSystem) diff --git a/node/src/main/scala/com/wavesplatform/api/common/AddressPortfolio.scala b/node/src/main/scala/com/wavesplatform/api/common/AddressPortfolio.scala index 5865baaf2f..393936b02a 100644 --- a/node/src/main/scala/com/wavesplatform/api/common/AddressPortfolio.scala +++ b/node/src/main/scala/com/wavesplatform/api/common/AddressPortfolio.scala @@ -13,6 +13,7 @@ import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.utils.ScorexLogging import java.nio.ByteBuffer +import scala.collection.immutable.VectorMap import scala.collection.mutable.ArrayBuffer import scala.jdk.CollectionConverters.* @@ -29,9 +30,8 @@ class NFTIterator(addressId: AddressId, maybeAfter: Option[IssuedAsset], resourc def skipEntry(key: Array[Byte]): Boolean = !key.endsWith(after.id.arr) - while (dbIterator.isValid && skipEntry(dbIterator.key())) { + while (dbIterator.isValid && skipEntry(dbIterator.key())) dbIterator.next() - } if (dbIterator.isValid && !skipEntry(dbIterator.key())) dbIterator.next() } @@ -99,19 +99,14 @@ class WavesBalanceIterator(addressId: AddressId, resource: DBResource) extends A class BalanceIterator( address: Address, underlying: Iterator[Seq[(IssuedAsset, Long)]], - includeAsset: IssuedAsset => Boolean, - private var pendingOverrides: Map[(Address, Asset), Long] + private var pendingOverrides: VectorMap[(Address, Asset), Long] ) extends AbstractIterator[Seq[(IssuedAsset, Long)]] { - private def nextOverride(): Seq[(IssuedAsset, Long)] = if (pendingOverrides.isEmpty) endOfData() else { - val balances = pendingOverrides.collect { - case ((`address`, asset: IssuedAsset), balance) if includeAsset(asset) => - asset -> balance - }.toSeq - pendingOverrides = Map.empty - balances + val assetsWithBalances = pendingOverrides.collect { case ((`address`, asset: IssuedAsset), balance) => asset -> balance }.toSeq + pendingOverrides = VectorMap.empty + assetsWithBalances } override def computeNext(): Seq[(IssuedAsset, Long)] = @@ -138,11 +133,15 @@ object AddressPortfolio { resource .get(Keys.addressId(address)) .fold(Iterator.empty[Seq[(IssuedAsset, Long)]])(addressId => new NFTIterator(addressId, maybeAfter, resource).asScala), - asset => loadAssetDescription(asset).exists(_.nft), snapshot.balances ).asScala - .map(_.collect { case (asset, balance) if balance > 0 => asset } - .flatMap(a => loadAssetDescription(a).map(a -> _))) + .map { assets => + maybeAfter + .filter(after => assets.exists(_._1 == after)) + .fold(assets)(after => assets.dropWhile(_._1 != after).drop(1)) + .collect { case (asset, balance) if balance > 0 => asset } + .flatMap(asset => loadAssetDescription(asset).collect { case description if description.nft => asset -> description }) + } def assetBalanceIterator( resource: DBResource, @@ -155,10 +154,9 @@ object AddressPortfolio { resource .get(Keys.addressId(address)) .fold(Iterator.empty[Seq[(IssuedAsset, Long)]])(addressId => new AssetBalanceIterator(addressId, resource).asScala), - includeAsset, snapshot.balances ).asScala - .map(_.filter { case (asset, balance) => - includeAsset(asset) && balance > 0 - }) + .map( + _.filter { case (asset, balance) => includeAsset(asset) && balance > 0 } + ) } diff --git a/node/src/main/scala/com/wavesplatform/api/common/CommonAccountsApi.scala b/node/src/main/scala/com/wavesplatform/api/common/CommonAccountsApi.scala index 0e7bc89ddc..c876739016 100644 --- a/node/src/main/scala/com/wavesplatform/api/common/CommonAccountsApi.scala +++ b/node/src/main/scala/com/wavesplatform/api/common/CommonAccountsApi.scala @@ -11,13 +11,10 @@ import com.wavesplatform.database.{DBExt, DBResource, KeyTags, Keys, RDB} import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.lang.ValidationError import com.wavesplatform.protobuf.transaction.PBRecipients -import com.wavesplatform.state.patch.CancelLeasesToDisabledAliases -import com.wavesplatform.state.reader.LeaseDetails.Status -import com.wavesplatform.state.reader.SnapshotBlockchain -import com.wavesplatform.state.{AccountScriptInfo, AssetDescription, Blockchain, DataEntry, Height, InvokeScriptResult, TxMeta} +import com.wavesplatform.state.LeaseDetails.Status +import com.wavesplatform.state.{AccountScriptInfo, AssetDescription, Blockchain, DataEntry, Height, InvokeScriptResult, SnapshotBlockchain, TxMeta} import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.EthereumTransaction.Invocation -import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.lease.LeaseTransaction import com.wavesplatform.transaction.{EthereumTransaction, TransactionType} import monix.eval.Task @@ -176,20 +173,13 @@ object CommonAccountsApi { } } - private def resolveDisabledAlias(leaseId: ByteStr): Either[ValidationError, Address] = - CancelLeasesToDisabledAliases.patchData - .get(leaseId) - .fold[Either[ValidationError, Address]](Left(GenericError("Unknown lease ID"))) { case (_, recipientAddress) => - Right(recipientAddress) - } - def leaseInfo(leaseId: ByteStr): Option[LeaseInfo] = blockchain.leaseDetails(leaseId) map { ld => LeaseInfo( leaseId, ld.sourceId, ld.sender.toAddress, - blockchain.resolveAlias(ld.recipient).orElse(resolveDisabledAlias(leaseId)).explicitGet(), - ld.amount, + ld.recipientAddress, + ld.amount.value, ld.height, ld.status match { case Status.Active => LeaseInfo.Status.Active diff --git a/node/src/main/scala/com/wavesplatform/api/http/DebugApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/DebugApiRoute.scala index a6bdd2e7ea..6aacb11294 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/DebugApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/DebugApiRoute.scala @@ -13,7 +13,7 @@ import com.wavesplatform.mining.{Miner, MinerDebugInfo} import com.wavesplatform.network.{PeerDatabase, PeerInfo, *} import com.wavesplatform.settings.{RestAPISettings, WavesSettings} import com.wavesplatform.state.diffs.TransactionDiffer -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.state.{Blockchain, Height, LeaseBalance, NG, Portfolio, StateHash, TxMeta} import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.IssuedAsset diff --git a/node/src/main/scala/com/wavesplatform/api/http/TransactionJsonSerializer.scala b/node/src/main/scala/com/wavesplatform/api/http/TransactionJsonSerializer.scala index 97e19d5ba5..d2b180f326 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/TransactionJsonSerializer.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/TransactionJsonSerializer.scala @@ -16,18 +16,18 @@ import com.wavesplatform.lang.v1.compiler.Terms.{ARR, CONST_BOOLEAN, CONST_BYTES import com.wavesplatform.lang.v1.serialization.SerdeV1 import com.wavesplatform.protobuf.transaction.PBAmounts import com.wavesplatform.state.InvokeScriptResult.{AttachedPayment, Burn, Call, ErrorMessage, Invocation, Issue, Lease, LeaseCancel, Reissue, SponsorFee} +import com.wavesplatform.state.LeaseDetails import com.wavesplatform.state.{Blockchain, DataEntry, InvokeScriptResult, TxMeta} -import com.wavesplatform.state.reader.LeaseDetails import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} -import com.wavesplatform.transaction.{Asset, PBSince, Transaction} import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction} import com.wavesplatform.transaction.serialization.impl.InvokeScriptTxSerializer import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.transfer.MassTransferTransaction +import com.wavesplatform.transaction.{Asset, PBSince, Transaction} import com.wavesplatform.utils.EthEncoding +import play.api.libs.json.* import play.api.libs.json.JsonConfiguration.Aux -import play.api.libs.json.{JsArray, JsBoolean, JsNumber, JsObject, JsString, JsValue, Json, JsonConfiguration, OWrites, OptionHandlers} final case class TransactionJsonSerializer(blockchain: Blockchain, commonApi: CommonTransactionsApi) { @@ -262,7 +262,7 @@ final case class TransactionJsonSerializer(blockchain: Blockchain, commonApi: Co tx.assetFee._1.maybeBase58Repr.foreach(gen.writeStringField("feeAssetId", _)) gen.writeNumberField("timestamp", tx.timestamp, numbersAsString) gen.writeNumberField("version", tx.version, numbersAsString) - if (tx.asInstanceOf[PBSince].isProtobufVersion) gen.writeNumberField("chainId", tx.chainId, numbersAsString) + if (PBSince.affects(tx)) gen.writeNumberField("chainId", tx.chainId, numbersAsString) gen.writeStringField("sender", tx.sender.toAddress(tx.chainId).toString) gen.writeStringField("senderPublicKey", tx.sender.toString) gen.writeArrayField("proofs")(gen => tx.proofs.proofs.foreach(p => gen.writeString(p.toString))) @@ -290,8 +290,8 @@ final case class TransactionJsonSerializer(blockchain: Blockchain, commonApi: Co gen.writeNumberField("fee", tx.assetFee._2, numbersAsString) tx.assetFee._1.maybeBase58Repr.foreach(gen.writeStringField("feeAssetId", _)) gen.writeNumberField("timestamp", tx.timestamp, numbersAsString) - gen.writeNumberField("version", tx.version, numbersAsString) - if (tx.isProtobufVersion) gen.writeNumberField("chainId", tx.chainId, numbersAsString) + gen.writeNumberField("version", 1, numbersAsString) + gen.writeNumberField("chainId", tx.chainId, numbersAsString) gen.writeStringField("bytes", EthEncoding.toHexString(tx.bytes())) gen.writeStringField("sender", tx.senderAddress().toString) gen.writeStringField("senderPublicKey", tx.signerPublicKey().toString) @@ -448,7 +448,7 @@ final case class TransactionJsonSerializer(blockchain: Blockchain, commonApi: Co ): LeaseRef = { val detailsOpt = blockchain.leaseDetails(leaseId) val txMetaOpt = detailsOpt.flatMap(d => blockchain.transactionMeta(d.sourceId)) - val recipientOpt = recipientParamOpt.orElse(detailsOpt.map(_.recipient)) + val recipientOpt = recipientParamOpt.orElse(detailsOpt.map(_.recipientAddress)) val resolvedRecipientOpt = recipientOpt.flatMap(r => blockchain.resolveAlias(r).toOption) val statusOpt = detailsOpt.map(_.status) @@ -464,7 +464,7 @@ final case class TransactionJsonSerializer(blockchain: Blockchain, commonApi: Co detailsOpt.map(_.sourceId), detailsOpt.map(_.sender.toAddress), resolvedRecipientOpt, - amountOpt orElse detailsOpt.map(_.amount), + amountOpt orElse detailsOpt.map(_.amount.value), txMetaOpt.map(_.height), status, statusDataOpt.flatMap(_._1), @@ -525,6 +525,6 @@ object TransactionJsonSerializer { object LeaseRef { import com.wavesplatform.utils.byteStrFormat implicit val config: Aux[Json.MacroOptions] = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull) - implicit val jsonWrites: OWrites[LeaseRef] = Json.writes[LeaseRef] + implicit val jsonWrites: OWrites[LeaseRef] = Json.writes[LeaseRef] } } diff --git a/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala index 64a01f9952..f7c447ad04 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala @@ -18,10 +18,15 @@ import com.wavesplatform.api.common.{CommonAccountsApi, CommonAssetsApi} import com.wavesplatform.api.http.* import com.wavesplatform.api.http.ApiError.* import com.wavesplatform.api.http.StreamSerializerUtils.* -import com.wavesplatform.api.http.assets.AssetsApiRoute.{AssetDetails, AssetInfo, DistributionParams, assetDetailsSerializer} +import com.wavesplatform.api.http.assets.AssetsApiRoute.{ + AssetDetails, + AssetInfo, + DistributionParams, + assetDetailsSerializer, + assetDistributionSerializer +} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.lang.ValidationError -import com.wavesplatform.network.TransactionPublisher import com.wavesplatform.settings.RestAPISettings import com.wavesplatform.state.{AssetDescription, AssetScriptInfo, Blockchain, TxMeta} import com.wavesplatform.transaction.Asset.IssuedAsset @@ -39,11 +44,12 @@ import play.api.libs.json.* import java.util.concurrent.* import scala.concurrent.Future +import scala.concurrent.duration.FiniteDuration case class AssetsApiRoute( settings: RestAPISettings, + serverRequestTimeout: FiniteDuration, wallet: Wallet, - transactionPublisher: TransactionPublisher, blockchain: Blockchain, compositeBlockchain: () => Blockchain, time: Time, @@ -65,6 +71,8 @@ case class AssetsApiRoute( ) ) + private val assetDistRouteTimeout = new RouteTimeout(serverRequestTimeout)(distributionTaskScheduler) + override lazy val route: Route = pathPrefix("assets") { pathPrefix("balance" / AddrSegment) { address => @@ -97,9 +105,10 @@ case class AssetsApiRoute( (path("nft" / AddrSegment / "limit" / IntNumber) & parameter("after".as[String].?)) { (address, limit, maybeAfter) => nft(address, limit, maybeAfter) } ~ pathPrefix(AssetId / "distribution") { assetId => - (path(IntNumber / "limit" / IntNumber) & parameter("after".?)) { (height, limit, maybeAfter) => - balanceDistributionAtHeight(assetId, height, limit, maybeAfter) - } + pathEndOrSingleSlash(balanceDistribution(assetId)) ~ + (path(IntNumber / "limit" / IntNumber) & parameter("after".?)) { (height, limit, maybeAfter) => + balanceDistributionAtHeight(assetId, height, limit, maybeAfter) + } } } } @@ -180,6 +189,16 @@ case class AssetsApiRoute( } } + def balanceDistribution(assetId: IssuedAsset): Route = { + implicit val jsonStreamingSupport: ToResponseMarshaller[Source[(Address, Long), NotUsed]] = + jacksonStreamMarshaller(prefix = "{", suffix = "}")(assetDistributionSerializer) + + assetDistRouteTimeout.executeFromObservable( + commonAssetsApi + .assetDistribution(assetId, blockchain.height, None) + ) + } + def balanceDistributionAtHeight(assetId: IssuedAsset, heightParam: Int, limitParam: Int, afterParam: Option[String]): Route = optionalHeaderValueByType(Accept) { accept => val paramsEi: Either[ValidationError, DistributionParams] = @@ -511,4 +530,16 @@ object AssetsApiRoute { gen.writeEndObject() } } + + def assetDistributionSerializer(numbersAsString: Boolean): JsonSerializer[(Address, Long)] = + (value: (Address, Long), gen: JsonGenerator, _: SerializerProvider) => { + val (address, balance) = value + if (numbersAsString) { + gen.writeRaw(s"\"${address.toString}\":") + gen.writeString(balance.toString) + } else { + gen.writeRaw(s"\"${address.toString}\":") + gen.writeNumber(balance) + } + } } diff --git a/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluationRequest.scala b/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluationRequest.scala index 0736d9d5e4..dd8f3a13a1 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluationRequest.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluationRequest.scala @@ -16,16 +16,31 @@ import com.wavesplatform.lang.v1.parser.Parser.LibrariesOffset.NoLibraries import com.wavesplatform.lang.v1.traits.domain.Recipient.Address as RideAddress import com.wavesplatform.lang.{ValidationError, utils} import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit} -import com.wavesplatform.state.{Blockchain, BlockchainOverrides, OverriddenBlockchain} +import com.wavesplatform.state.{Blockchain, BlockchainOverrides, SnapshotBlockchain, StateSnapshot} import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.smart.AttachedPaymentExtractor import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment -import com.wavesplatform.transaction.{TransactionType, smart} +import com.wavesplatform.transaction.{Asset, TransactionType, smart} import play.api.libs.json.* +import scala.collection.immutable.VectorMap + sealed trait UtilsEvaluationRequest { def state: Option[BlockchainOverrides] - def mkBlockchain(underlying: Blockchain): Blockchain = state.foldLeft(underlying)(new OverriddenBlockchain(_, _)) + def mkBlockchain(underlying: Blockchain): Blockchain = { + + state.fold(underlying) { ovs => + SnapshotBlockchain( + underlying, + StateSnapshot(balances = VectorMap.from[(Address, Asset), Long](for { + (addr, ov) <- ovs.accounts + (id, balance) <- ov.assetBalances + } yield ((addr, id), balance.value)) ++ VectorMap.from(ovs.accounts.flatMap { case (addr, acc) => + acc.regularBalance.map(v => ((addr, Asset.Waves), v.value)) + })) + ) + } + } } object UtilsEvaluationRequest { diff --git a/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluator.scala b/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluator.scala index 9731bdd174..67eb44c471 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluator.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluator.scala @@ -26,7 +26,7 @@ import com.wavesplatform.lang.{ValidationError, utils} import com.wavesplatform.serialization.ScriptValuesJson import com.wavesplatform.state.diffs.TransactionDiffer import com.wavesplatform.state.diffs.invoke.{InvokeDiffsCommon, InvokeScriptTransactionLike, StructuredCallableActions} -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.state.{AccountScriptInfo, Blockchain, InvokeScriptResult, Portfolio, StateSnapshot} import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.TransactionType.InvokeScript @@ -155,7 +155,8 @@ object UtilsEvaluator { correctFunctionCallScope = blockchain.checkEstimatorSumOverflow, newMode = blockchain.newEvaluatorMode, checkConstructorArgsTypes = true, - enableExecutionLog = true + enableExecutionLog = true, + fixedThrownError = true ) .value() .leftMap { case (err, _, log) => InvokeRejectError(err.message, log) } diff --git a/node/src/main/scala/com/wavesplatform/block/BlockSnapshot.scala b/node/src/main/scala/com/wavesplatform/block/BlockSnapshot.scala index 8ab14ac711..3e59eb342f 100644 --- a/node/src/main/scala/com/wavesplatform/block/BlockSnapshot.scala +++ b/node/src/main/scala/com/wavesplatform/block/BlockSnapshot.scala @@ -1,12 +1,6 @@ package com.wavesplatform.block import com.wavesplatform.block.Block.BlockId -import com.wavesplatform.network.BlockSnapshotResponse import com.wavesplatform.state.{StateSnapshot, TxMeta} case class BlockSnapshot(blockId: BlockId, snapshots: Seq[(StateSnapshot, TxMeta.Status)]) - -object BlockSnapshot { - def fromResponse(response: BlockSnapshotResponse): BlockSnapshot = - BlockSnapshot(response.blockId, response.snapshots.map(StateSnapshot.fromProtobuf)) -} diff --git a/node/src/main/scala/com/wavesplatform/block/MicroBlockSnapshot.scala b/node/src/main/scala/com/wavesplatform/block/MicroBlockSnapshot.scala index 34f43c87fe..731c040ca4 100644 --- a/node/src/main/scala/com/wavesplatform/block/MicroBlockSnapshot.scala +++ b/node/src/main/scala/com/wavesplatform/block/MicroBlockSnapshot.scala @@ -1,12 +1,6 @@ package com.wavesplatform.block import com.wavesplatform.block.Block.BlockId -import com.wavesplatform.network.MicroBlockSnapshotResponse import com.wavesplatform.state.{StateSnapshot, TxMeta} case class MicroBlockSnapshot(totalBlockId: BlockId, snapshots: Seq[(StateSnapshot, TxMeta.Status)]) - -object MicroBlockSnapshot { - def fromResponse(response: MicroBlockSnapshotResponse): MicroBlockSnapshot = - MicroBlockSnapshot(response.totalBlockId, response.snapshots.map(StateSnapshot.fromProtobuf)) -} diff --git a/node/src/main/scala/com/wavesplatform/database/Caches.scala b/node/src/main/scala/com/wavesplatform/database/Caches.scala index bafe367ba9..08b299029a 100644 --- a/node/src/main/scala/com/wavesplatform/database/Caches.scala +++ b/node/src/main/scala/com/wavesplatform/database/Caches.scala @@ -17,7 +17,7 @@ import com.wavesplatform.transaction.{Asset, DiscardedBlocks, Transaction} import com.wavesplatform.utils.ObservedLoadingCache import monix.reactive.Observer -import java.{util, lang} +import java.{lang, util} import scala.collection.immutable.VectorMap import scala.collection.mutable import scala.jdk.CollectionConverters.* @@ -321,9 +321,10 @@ abstract class Caches extends Blockchain with Storage { for ((address, script) <- snapshot.accountScriptsByAddress) stateHash.addAccountScript(address, script.map(_.script)) for ((asset, script) <- snapshot.assetScripts) stateHash.addAssetScript(asset, Some(script.script)) for ((asset, _) <- snapshot.assetStatics) if (!snapshot.assetScripts.contains(asset)) stateHash.addAssetScript(asset, None) - for ((leaseId, lease) <- snapshot.leaseStates) stateHash.addLeaseStatus(leaseId, lease.isActive) - for ((assetId, sponsorship) <- snapshot.sponsorships) stateHash.addSponsorship(assetId, sponsorship.minFee) - for ((alias, address) <- snapshot.aliases) stateHash.addAlias(address, alias.name) + for (leaseId <- snapshot.newLeases.keys) if (!snapshot.cancelledLeases.contains(leaseId)) stateHash.addLeaseStatus(leaseId, isActive = true) + for (leaseId <- snapshot.cancelledLeases.keys) stateHash.addLeaseStatus(leaseId, isActive = false) + for ((assetId, sponsorship) <- snapshot.sponsorships) stateHash.addSponsorship(assetId, sponsorship.minFee) + for ((alias, address) <- snapshot.aliases) stateHash.addAlias(address, alias.name) doAppend( newMeta, diff --git a/node/src/main/scala/com/wavesplatform/database/Keys.scala b/node/src/main/scala/com/wavesplatform/database/Keys.scala index 47fc85f3e8..d6c4c36830 100644 --- a/node/src/main/scala/com/wavesplatform/database/Keys.scala +++ b/node/src/main/scala/com/wavesplatform/database/Keys.scala @@ -9,7 +9,6 @@ import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot import com.wavesplatform.protobuf.transaction.PBRecipients import com.wavesplatform.state import com.wavesplatform.state.* -import com.wavesplatform.state.reader.LeaseDetails import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.{ERC20Address, Transaction} import com.wavesplatform.utils.* @@ -99,7 +98,7 @@ object Keys { Key(LeaseBalance, addressId.toByteArray, readLeaseBalance, writeLeaseBalance) def leaseDetailsHistory(leaseId: ByteStr): Key[Seq[Int]] = historyKey(LeaseDetailsHistory, leaseId.arr) - def leaseDetails(leaseId: ByteStr)(height: Int): Key[Option[Either[Boolean, LeaseDetails]]] = + def leaseDetails(leaseId: ByteStr)(height: Int): Key[Option[LeaseDetails]] = Key.opt(LeaseDetailsTag, Ints.toByteArray(height) ++ leaseId.arr, readLeaseDetails, writeLeaseDetails) def filledVolumeAndFeeAt(orderId: ByteStr, height: Height): Key[VolumeAndFeeNode] = diff --git a/node/src/main/scala/com/wavesplatform/database/RDB.scala b/node/src/main/scala/com/wavesplatform/database/RDB.scala index e5b5c0082f..f2c1ba1412 100644 --- a/node/src/main/scala/com/wavesplatform/database/RDB.scala +++ b/node/src/main/scala/com/wavesplatform/database/RDB.scala @@ -10,6 +10,7 @@ import java.io.File import java.nio.file.{Files, Path} import java.util import scala.jdk.CollectionConverters.* +import scala.util.{Failure, Success, Using} final class RDB( val db: RocksDB, @@ -138,14 +139,21 @@ object RDB extends StrictLogging { } else OptionsWithResources(dbOptions, Seq(dbOptions)) } - private def checkDbDir(dbPath: Path): Unit = { - val containsLdbFiles = Files.exists(dbPath) && Files.list(dbPath).iterator().asScala.exists(_.getFileName.toString.endsWith(".ldb")) - if (containsLdbFiles) { - logger.error( - s"Database directory ${dbPath.toAbsolutePath.toString} contains LevelDB files (.ldb) which is not compatible with current database. Please delete these files and restart node" - ) - logger.error("FOR THIS REASON THE NODE STOPPED AUTOMATICALLY") - forceStopApplication(FatalDBError) + private def checkDbDir(dbPath: Path): Unit = + if (Files.exists(dbPath)) { + Using(Files.list(dbPath)) { fileList => + fileList.iterator().asScala.exists(_.getFileName.toString.endsWith(".ldb")) + } match { + case Failure(exception) => + logger.error(s"Could not open data directory ${dbPath.toAbsolutePath.toString}", exception) + forceStopApplication(FatalDBError) + case Success(true) => + logger.error( + s"Database directory ${dbPath.toAbsolutePath.toString} contains LevelDB files (.ldb) which is not compatible with current database. Please delete these files and restart node" + ) + logger.error("FOR THIS REASON THE NODE STOPPED AUTOMATICALLY") + forceStopApplication(FatalDBError) + case _ => + } } - } } diff --git a/node/src/main/scala/com/wavesplatform/database/RocksDBWriter.scala b/node/src/main/scala/com/wavesplatform/database/RocksDBWriter.scala index b16f420538..f14575ddfb 100644 --- a/node/src/main/scala/com/wavesplatform/database/RocksDBWriter.scala +++ b/node/src/main/scala/com/wavesplatform/database/RocksDBWriter.scala @@ -7,8 +7,8 @@ import com.google.common.hash.{BloomFilter, Funnels} import com.google.common.primitives.Ints import com.wavesplatform.account.{Address, Alias} import com.wavesplatform.api.common.WavesBalanceIterator -import com.wavesplatform.block.BlockSnapshot import com.wavesplatform.block.Block.BlockId +import com.wavesplatform.block.BlockSnapshot import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.database @@ -17,12 +17,10 @@ import com.wavesplatform.database.protobuf.{StaticAssetInfo, TransactionMeta, Bl import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.lang.ValidationError import com.wavesplatform.protobuf.block.PBBlocks -import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot -import com.wavesplatform.protobuf.snapshot.TransactionStatus as PBStatus -import com.wavesplatform.protobuf.{ByteStrExt, ByteStringExt} +import com.wavesplatform.protobuf.snapshot.{TransactionStateSnapshot, TransactionStatus as PBStatus} +import com.wavesplatform.protobuf.{ByteStrExt, ByteStringExt, PBSnapshots} import com.wavesplatform.settings.{BlockchainSettings, DBSettings} import com.wavesplatform.state.* -import com.wavesplatform.state.reader.LeaseDetails import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.EthereumTransaction.Transfer @@ -299,7 +297,7 @@ class RocksDBWriter( private def appendBalances( balances: Map[(AddressId, Asset), (CurrentBalance, BalanceNode)], - assetStatics: Map[IssuedAsset, TransactionStateSnapshot.AssetStatic], + assetStatics: Map[IssuedAsset, AssetStaticInfo], rw: RW ): Unit = { val changedAssetBalances = MultimapBuilder.hashKeys().hashSetValues().build[IssuedAsset, java.lang.Long]() @@ -453,8 +451,8 @@ class RocksDBWriter( for ((asset, (assetStatic, assetNum)) <- snapshot.indexedAssetStatics) { val pbAssetStatic = StaticAssetInfo( - assetStatic.sourceTransactionId, - assetStatic.issuerPublicKey, + assetStatic.source.toByteString, + assetStatic.issuer.toByteString, assetStatic.decimals, assetStatic.nft, assetNum, @@ -485,8 +483,16 @@ class RocksDBWriter( expiredKeys ++= updateHistory(rw, Keys.assetDetailsHistory(asset), threshold, Keys.assetDetails(asset)) } - for ((id, details) <- snapshot.leaseStates) { - rw.put(Keys.leaseDetails(id)(height), Some(Right(details))) + for ((id, li) <- snapshot.newLeases) { + rw.put(Keys.leaseDetails(id)(height), Some(LeaseDetails(li, snapshot.cancelledLeases.getOrElse(id, LeaseDetails.Status.Active)))) + expiredKeys ++= updateHistory(rw, Keys.leaseDetailsHistory(id), threshold, Keys.leaseDetails(id)) + } + + for ((id, status) <- snapshot.cancelledLeases if !snapshot.newLeases.contains(id)) { + rw.fromHistory(Keys.leaseDetailsHistory(id), Keys.leaseDetails(id)).flatten.foreach { d => + rw.put(Keys.leaseDetails(id)(height), Some(d.copy(status = status))) + } + expiredKeys ++= updateHistory(rw, Keys.leaseDetailsHistory(id), threshold, Keys.leaseDetails(id)) } @@ -517,7 +523,10 @@ class RocksDBWriter( val txId = TransactionId(id) val size = rw.put(Keys.transactionAt(Height(height), num, rdb.txHandle), Some((meta, tx))) - rw.put(Keys.transactionStateSnapshotAt(Height(height), num, rdb.txSnapshotHandle), Some(txInfo.snapshot.toProtobuf(txInfo.status))) + rw.put( + Keys.transactionStateSnapshotAt(Height(height), num, rdb.txSnapshotHandle), + Some(PBSnapshots.toProtobuf(txInfo.snapshot, txInfo.status)) + ) rw.put(Keys.transactionMetaById(txId, rdb.txMetaHandle), Some(TransactionMeta(height, num, tx.tpe.id, meta.status.protobuf, 0, size))) targetBf.put(id.arr) @@ -792,7 +801,7 @@ class RocksDBWriter( ).explicitGet() val snapshot = if (isLightMode) { - Some(BlockSnapshot(block.id(), loadTxStateSnapshotsWithStatus(currentHeight, rdb))) + Some(BlockSnapshot(block.id(), loadTxStateSnapshotsWithStatus(currentHeight, rdb, block.transactionData))) } else None (block, Caches.toHitSource(discardedMeta), snapshot) @@ -947,23 +956,8 @@ class RocksDBWriter( override def leaseDetails(leaseId: ByteStr): Option[LeaseDetails] = readOnly { db => for { - h <- db.get(Keys.leaseDetailsHistory(leaseId)).headOption - detailsOrFlag <- db.get(Keys.leaseDetails(leaseId)(h)) - details <- detailsOrFlag.fold( - isActive => - transactionInfo(leaseId, db).collect { case (txm, lt: LeaseTransaction) => - LeaseDetails( - lt.sender, - lt.recipient, - lt.amount.value, - if (isActive) LeaseDetails.Status.Active - else LeaseDetails.Status.Cancelled(h, None), - leaseId, - txm.height - ) - }, - Some(_) - ) + h <- db.get(Keys.leaseDetailsHistory(leaseId)).headOption + details <- db.get(Keys.leaseDetails(leaseId)(h)) } yield details } diff --git a/node/src/main/scala/com/wavesplatform/database/package.scala b/node/src/main/scala/com/wavesplatform/database/package.scala index 30ea8448aa..b632c7086b 100644 --- a/node/src/main/scala/com/wavesplatform/database/package.scala +++ b/node/src/main/scala/com/wavesplatform/database/package.scala @@ -1,8 +1,5 @@ package com.wavesplatform -import java.nio.ByteBuffer -import java.util -import java.util.Map as JMap import com.google.common.base.Charsets.UTF_8 import com.google.common.collect.{Interners, Maps} import com.google.common.io.ByteStreams.{newDataInput, newDataOutput} @@ -19,24 +16,15 @@ import com.wavesplatform.database.protobuf as pb import com.wavesplatform.database.protobuf.DataEntry.Value import com.wavesplatform.database.protobuf.TransactionData.Transaction as TD import com.wavesplatform.lang.script.ScriptReader -import com.wavesplatform.protobuf.ByteStringExt import com.wavesplatform.protobuf.block.PBBlocks import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot import com.wavesplatform.protobuf.transaction.{PBRecipients, PBTransactions} +import com.wavesplatform.protobuf.{ByteStringExt, PBSnapshots} import com.wavesplatform.state.* import com.wavesplatform.state.StateHash.SectionId -import com.wavesplatform.state.reader.LeaseDetails import com.wavesplatform.transaction.Asset.IssuedAsset -import com.wavesplatform.transaction.lease.LeaseTransaction -import com.wavesplatform.transaction.{ - EthereumTransaction, - GenesisTransaction, - PBSince, - PaymentTransaction, - Transaction, - TransactionParsers, - TxValidationError -} + +import com.wavesplatform.transaction.{EthereumTransaction, PBSince, Transaction, TransactionParsers,TxPositiveAmount, TxValidationError, Versioned} import com.wavesplatform.utils.* import monix.eval.Task import monix.reactive.Observable @@ -44,6 +32,9 @@ import org.rocksdb.* import sun.nio.ch.Util import supertagged.TaggedType +import java.nio.ByteBuffer +import java.util +import java.util.Map as JMap import scala.annotation.tailrec import scala.collection.mutable.ArrayBuffer import scala.collection.{View, mutable} @@ -143,47 +134,40 @@ package object database { def writeLeaseBalance(lb: CurrentLeaseBalance): Array[Byte] = Longs.toByteArray(lb.in) ++ Longs.toByteArray(lb.out) ++ Ints.toByteArray(lb.height) ++ Ints.toByteArray(lb.prevHeight) - def writeLeaseDetails(lde: Either[Boolean, LeaseDetails]): Array[Byte] = - lde.fold( - _ => throw new IllegalArgumentException("Can not write boolean flag instead of LeaseDetails"), - ld => - pb.LeaseDetails( - ByteString.copyFrom(ld.sender.arr), - Some(PBRecipients.create(ld.recipient)), - ld.amount, - ByteString.copyFrom(ld.sourceId.arr), - ld.height, - ld.status match { - case LeaseDetails.Status.Active => pb.LeaseDetails.Status.Active(com.google.protobuf.empty.Empty()) - case LeaseDetails.Status.Cancelled(height, cancelTxId) => - pb.LeaseDetails.Status - .Cancelled(pb.LeaseDetails.Cancelled(height, cancelTxId.fold(ByteString.EMPTY)(id => ByteString.copyFrom(id.arr)))) - case LeaseDetails.Status.Expired(height) => pb.LeaseDetails.Status.Expired(pb.LeaseDetails.Expired(height)) - } - ).toByteArray - ) + def writeLeaseDetails(ld: LeaseDetails): Array[Byte] = + pb.LeaseDetails( + ByteString.copyFrom(ld.sender.arr), + Some(PBRecipients.create(ld.recipientAddress)), + ld.amount.value, + ByteString.copyFrom(ld.sourceId.arr), + ld.height, + ld.status match { + case LeaseDetails.Status.Active => pb.LeaseDetails.CancelReason.Empty + case LeaseDetails.Status.Cancelled(height, cancelTxId) => + pb.LeaseDetails.CancelReason + .Cancelled(pb.LeaseDetails.Cancelled(height, cancelTxId.fold(ByteString.EMPTY)(id => ByteString.copyFrom(id.arr)))) + case LeaseDetails.Status.Expired(height) => pb.LeaseDetails.CancelReason.Expired(pb.LeaseDetails.Expired(height)) + } + ).toByteArray - def readLeaseDetails(data: Array[Byte]): Either[Boolean, LeaseDetails] = - if (data.length == 1) Left(data(0) == 1) - else { - val d = pb.LeaseDetails.parseFrom(data) - Right( - LeaseDetails( - d.senderPublicKey.toPublicKey, - PBRecipients.toAddressOrAlias(d.recipient.get, AddressScheme.current.chainId).explicitGet(), - d.amount, - d.status match { - case pb.LeaseDetails.Status.Active(_) => LeaseDetails.Status.Active - case pb.LeaseDetails.Status.Expired(pb.LeaseDetails.Expired(height, _)) => LeaseDetails.Status.Expired(height) - case pb.LeaseDetails.Status.Cancelled(pb.LeaseDetails.Cancelled(height, transactionId, _)) => - LeaseDetails.Status.Cancelled(height, Some(transactionId.toByteStr).filter(!_.isEmpty)) - case pb.LeaseDetails.Status.Empty => ??? - }, - d.sourceId.toByteStr, - d.height - ) - ) - } + def readLeaseDetails(data: Array[Byte]): LeaseDetails = { + val d = pb.LeaseDetails.parseFrom(data) + LeaseDetails( + LeaseStaticInfo( + d.senderPublicKey.toPublicKey, + PBRecipients.toAddress(d.recipient.get, AddressScheme.current.chainId).explicitGet(), + TxPositiveAmount.unsafeFrom(d.amount), + d.sourceId.toByteStr, + d.height + ), + d.cancelReason match { + case pb.LeaseDetails.CancelReason.Expired(pb.LeaseDetails.Expired(height, _)) => LeaseDetails.Status.Expired(height) + case pb.LeaseDetails.CancelReason.Cancelled(pb.LeaseDetails.Cancelled(height, transactionId, _)) => + LeaseDetails.Status.Cancelled(height, Some(transactionId.toByteStr).filter(!_.isEmpty)) + case pb.LeaseDetails.CancelReason.Empty => LeaseDetails.Status.Active + } + ) + } def readVolumeAndFeeNode(data: Array[Byte]): VolumeAndFeeNode = if (data != null && data.length == 20) VolumeAndFeeNode(Longs.fromByteArray(data.take(8)), Longs.fromByteArray(data.slice(8, 16)), Height(Ints.fromByteArray(data.takeRight(4)))) @@ -255,17 +239,6 @@ package object database { ).toByteArray } - def readAssetStaticInfo(bb: Array[Byte]): AssetStaticInfo = { - val sai = pb.StaticAssetInfo.parseFrom(bb) - AssetStaticInfo( - sai.id.toByteStr, - TransactionId(sai.sourceId.toByteStr), - PublicKey(sai.issuerPublicKey.toByteArray), - sai.decimals, - sai.isNft - ) - } - def writeBlockMeta(data: pb.BlockMeta): Array[Byte] = data.toByteArray def readBlockMeta(bs: Array[Byte]): pb.BlockMeta = pb.BlockMeta.parseFrom(bs) @@ -647,11 +620,9 @@ package object database { def writeTransaction(v: (TxMeta, Transaction)): Array[Byte] = { val (m, tx) = v val ptx = tx match { - case lps: PBSince if !lps.isProtobufVersion => TD.LegacyBytes(ByteString.copyFrom(tx.bytes())) - case _: GenesisTransaction => TD.LegacyBytes(ByteString.copyFrom(tx.bytes())) - case _: PaymentTransaction => TD.LegacyBytes(ByteString.copyFrom(tx.bytes())) - case et: EthereumTransaction => TD.EthereumTransaction(ByteString.copyFrom(et.bytes())) - case _ => TD.WavesTransaction(PBTransactions.protobuf(tx)) + case lps: PBSince with Versioned if PBSince.affects(lps) => TD.WavesTransaction(PBTransactions.protobuf(tx)) + case et: EthereumTransaction => TD.EthereumTransaction(ByteString.copyFrom(et.bytes())) + case _ => TD.LegacyBytes(ByteString.copyFrom(tx.bytes())) } pb.TransactionData(ptx, m.status.protobuf, m.spentComplexity).toByteArray } @@ -672,8 +643,8 @@ package object database { txSnapshots.result() } - def loadTxStateSnapshotsWithStatus(height: Height, rdb: RDB): Seq[(StateSnapshot, TxMeta.Status)] = - loadTxStateSnapshots(height, rdb).map(StateSnapshot.fromProtobuf) + def loadTxStateSnapshotsWithStatus(height: Height, rdb: RDB, transactions: Seq[Transaction]): Seq[(StateSnapshot, TxMeta.Status)] = + loadTxStateSnapshots(height, rdb).zip(transactions).map { case (s, tx) => PBSnapshots.fromProtobuf(s, tx.id(), height) } def loadBlock(height: Height, rdb: RDB): Option[Block] = for { @@ -708,16 +679,13 @@ package object database { Height(pbStaticInfo.height) ) - def loadActiveLeases(rdb: RDB, fromHeight: Int, toHeight: Int): Seq[LeaseTransaction] = rdb.db.withResource { r => + def loadActiveLeases(rdb: RDB, fromHeight: Int, toHeight: Int): Map[ByteStr, LeaseDetails] = rdb.db.withResource { r => (for { - id <- loadLeaseIds(r, fromHeight, toHeight, includeCancelled = false) - details <- fromHistory(r, Keys.leaseDetailsHistory(id), Keys.leaseDetails(id)) - if details.exists(_.fold(identity, _.isActive)) - tm <- r.get(Keys.transactionMetaById(TransactionId(id), rdb.txMetaHandle)) - tx <- r.get(Keys.transactionAt(Height(tm.height), TxNum(tm.num.toShort), rdb.txHandle)) - } yield tx).collect { - case (ltm, lt: LeaseTransaction) if ltm.status == TxMeta.Status.Succeeded => lt - }.toSeq + id <- loadLeaseIds(r, fromHeight, toHeight, includeCancelled = false) + maybeNewDetails <- fromHistory(r, Keys.leaseDetailsHistory(id), Keys.leaseDetails(id)) + newDetails <- maybeNewDetails + if newDetails.isActive + } yield (id, newDetails)).toMap } def loadLeaseIds(resource: DBResource, fromHeight: Int, toHeight: Int, includeCancelled: Boolean): Set[ByteStr] = { @@ -734,7 +702,7 @@ package object database { iterator.seek(KeyTags.LeaseDetails.prefixBytes ++ Ints.toByteArray(fromHeight)) while (iterator.isValid && keyInRange()) { val leaseId = ByteStr(iterator.key().drop(6)) - if (includeCancelled || readLeaseDetails(iterator.value()).fold(identity, _.isActive)) + if (includeCancelled || readLeaseDetails(iterator.value()).isActive) leaseIds += leaseId else leaseIds -= leaseId diff --git a/node/src/main/scala/com/wavesplatform/history/History.scala b/node/src/main/scala/com/wavesplatform/history/History.scala index 4e86d87e38..47098d2b57 100644 --- a/node/src/main/scala/com/wavesplatform/history/History.scala +++ b/node/src/main/scala/com/wavesplatform/history/History.scala @@ -4,6 +4,7 @@ import com.wavesplatform.block.{Block, MicroBlock} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.database import com.wavesplatform.database.RDB +import com.wavesplatform.protobuf.PBSnapshots import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot import com.wavesplatform.state.{Blockchain, Height, StateSnapshot} @@ -43,7 +44,7 @@ object History { override def loadBlockSnapshots(id: ByteStr): Option[Seq[TransactionStateSnapshot]] = liquidBlockSnapshot(id) - .map(_.transactions.values.toSeq.map(txInfo => txInfo.snapshot.toProtobuf(txInfo.status))) + .map(_.transactions.values.toSeq.map(txInfo => PBSnapshots.toProtobuf(txInfo.snapshot, txInfo.status))) .orElse(blockchain.heightOf(id).map { h => database.loadTxStateSnapshots(Height(h), rdb) }) @@ -51,7 +52,7 @@ object History { override def loadMicroBlockSnapshots(id: ByteStr): Option[Seq[TransactionStateSnapshot]] = microBlockSnapshot(id) .map(_.transactions.values.toSeq.map { txInfo => - txInfo.snapshot.toProtobuf(txInfo.status) + PBSnapshots.toProtobuf(txInfo.snapshot, txInfo.status) }) } } diff --git a/node/src/main/scala/com/wavesplatform/metrics/BlockStats.scala b/node/src/main/scala/com/wavesplatform/metrics/BlockStats.scala index 64b0cb220b..69a46ba868 100644 --- a/node/src/main/scala/com/wavesplatform/metrics/BlockStats.scala +++ b/node/src/main/scala/com/wavesplatform/metrics/BlockStats.scala @@ -1,7 +1,7 @@ package com.wavesplatform.metrics import com.wavesplatform.block.Block.BlockId -import com.wavesplatform.block.{Block, BlockSnapshot, MicroBlock, MicroBlockSnapshot} +import com.wavesplatform.block.{Block, MicroBlock} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.network.{HandshakeHandler, MicroBlockInv} import io.netty.channel.Channel @@ -55,8 +55,8 @@ object BlockStats { Seq.empty ) - def received(s: BlockSnapshot, source: Source, ch: Channel): Unit = write( - blockSnapshot(s, source) + def receivedSnapshot(blockId: ByteStr, source: Source, ch: Channel): Unit = write( + blockSnapshot(blockId, source) .addField("from", nodeName(ch)), Event.Received, Seq.empty @@ -107,7 +107,7 @@ object BlockStats { Seq.empty ) - def received(m: MicroBlockSnapshot, ch: Channel, blockId: BlockId): Unit = write( + def received(ch: Channel, blockId: BlockId): Unit = write( microBlockSnapshot(blockId) .addField("from", nodeName(ch)), Event.Received, @@ -173,11 +173,10 @@ object BlockStats { .tag("whitelist", isWhitelistMiner.toString) } - private def blockSnapshot(s: BlockSnapshot, source: Source): Point.Builder = { + private def blockSnapshot(blockId: ByteStr, source: Source): Point.Builder = measurement(Type.BlockSnapshot) - .tag("id", id(s.blockId)) + .tag("id", id(blockId)) .tag("source", source.name) - } private def microBlockSnapshot(totalBlockId: BlockId): Point.Builder = measurement(Type.MicroSnapshot) diff --git a/node/src/main/scala/com/wavesplatform/mining/BlockChallenger.scala b/node/src/main/scala/com/wavesplatform/mining/BlockChallenger.scala index 4facb60cd9..d52c4acb41 100644 --- a/node/src/main/scala/com/wavesplatform/mining/BlockChallenger.scala +++ b/node/src/main/scala/com/wavesplatform/mining/BlockChallenger.scala @@ -16,7 +16,7 @@ import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult.Applied import com.wavesplatform.state.appender.MaxTimeDrift import com.wavesplatform.state.diffs.BlockDiffer -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.state.{Blockchain, StateSnapshot, TxStateSnapshotHashBuilder} import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.{BlockchainUpdater, Transaction} diff --git a/node/src/main/scala/com/wavesplatform/mining/Miner.scala b/node/src/main/scala/com/wavesplatform/mining/Miner.scala index ec84c72047..239fdb8d06 100644 --- a/node/src/main/scala/com/wavesplatform/mining/Miner.scala +++ b/node/src/main/scala/com/wavesplatform/mining/Miner.scala @@ -289,7 +289,7 @@ class MinerImpl( def appendTask(block: Block, totalConstraint: MiningConstraint) = BlockAppender(blockchainUpdater, timeService, utx, pos, appenderScheduler)(block, None).flatMap { - case Left(BlockFromFuture(_)) => // Time was corrected, retry + case Left(BlockFromFuture(_, _)) => // Time was corrected, retry generateBlockTask(account, None) case Left(err) => diff --git a/node/src/main/scala/com/wavesplatform/network/MessageObserver.scala b/node/src/main/scala/com/wavesplatform/network/MessageObserver.scala index 728b146088..5182362201 100644 --- a/node/src/main/scala/com/wavesplatform/network/MessageObserver.scala +++ b/node/src/main/scala/com/wavesplatform/network/MessageObserver.scala @@ -1,6 +1,6 @@ package com.wavesplatform.network -import com.wavesplatform.block.{Block, BlockSnapshot, MicroBlockSnapshot} +import com.wavesplatform.block.Block import com.wavesplatform.transaction.Transaction import com.wavesplatform.utils.{Schedulers, ScorexLogging} import io.netty.channel.ChannelHandler.Sharable @@ -19,8 +19,8 @@ class MessageObserver extends ChannelInboundHandlerAdapter with ScorexLogging { private val microblockInvs = ConcurrentSubject.publish[(Channel, MicroBlockInv)] private val microblockResponses = ConcurrentSubject.publish[(Channel, MicroBlockResponse)] private val transactions = ConcurrentSubject.publish[(Channel, Transaction)] - private val blockSnapshots = ConcurrentSubject.publish[(Channel, BlockSnapshot)] - private val microblockSnapshots = ConcurrentSubject.publish[(Channel, MicroBlockSnapshot)] + private val blockSnapshots = ConcurrentSubject.publish[(Channel, BlockSnapshotResponse)] + private val microblockSnapshots = ConcurrentSubject.publish[(Channel, MicroBlockSnapshotResponse)] override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = msg match { case b: Block => blocks.onNext((ctx.channel(), b)) @@ -29,8 +29,8 @@ class MessageObserver extends ChannelInboundHandlerAdapter with ScorexLogging { case mbInv: MicroBlockInv => microblockInvs.onNext((ctx.channel(), mbInv)) case mb: MicroBlockResponse => microblockResponses.onNext((ctx.channel(), mb)) case tx: Transaction => transactions.onNext((ctx.channel(), tx)) - case sn: BlockSnapshotResponse => blockSnapshots.onNext((ctx.channel(), BlockSnapshot.fromResponse(sn))) - case sn: MicroBlockSnapshotResponse => microblockSnapshots.onNext((ctx.channel(), MicroBlockSnapshot.fromResponse(sn))) + case sn: BlockSnapshotResponse => blockSnapshots.onNext((ctx.channel(), sn)) + case sn: MicroBlockSnapshotResponse => microblockSnapshots.onNext((ctx.channel(), sn)) case _ => super.channelRead(ctx, msg) } @@ -55,8 +55,8 @@ object MessageObserver { ChannelObservable[MicroBlockInv], ChannelObservable[MicroBlockResponse], ChannelObservable[Transaction], - ChannelObservable[BlockSnapshot], - ChannelObservable[MicroBlockSnapshot] + ChannelObservable[BlockSnapshotResponse], + ChannelObservable[MicroBlockSnapshotResponse] ) def apply(): (MessageObserver, Messages) = { diff --git a/node/src/main/scala/com/wavesplatform/network/MicroBlockSynchronizer.scala b/node/src/main/scala/com/wavesplatform/network/MicroBlockSynchronizer.scala index c7dd0da5b0..394260ae14 100644 --- a/node/src/main/scala/com/wavesplatform/network/MicroBlockSynchronizer.scala +++ b/node/src/main/scala/com/wavesplatform/network/MicroBlockSynchronizer.scala @@ -1,10 +1,8 @@ package com.wavesplatform.network import com.google.common.cache.{Cache, CacheBuilder} - -import java.util.concurrent.TimeUnit import com.wavesplatform.block.Block.BlockId -import com.wavesplatform.block.{MicroBlock, MicroBlockSnapshot} +import com.wavesplatform.block.MicroBlock import com.wavesplatform.common.state.ByteStr import com.wavesplatform.metrics.BlockStats import com.wavesplatform.settings.SynchronizationSettings.MicroblockSynchronizerSettings @@ -15,6 +13,7 @@ import monix.execution.CancelableFuture import monix.execution.schedulers.SchedulerService import monix.reactive.Observable +import java.util.concurrent.TimeUnit import scala.collection.mutable.Set as MSet import scala.concurrent.duration.FiniteDuration @@ -27,9 +26,9 @@ object MicroBlockSynchronizer extends ScorexLogging { lastBlockIdEvents: Observable[ByteStr], microblockInvs: ChannelObservable[MicroBlockInv], microblockResponses: ChannelObservable[MicroBlockResponse], - microblockSnapshots: ChannelObservable[MicroBlockSnapshot], + microblockSnapshots: ChannelObservable[MicroBlockSnapshotResponse], scheduler: SchedulerService - ): (Observable[(Channel, MicroblockData, Option[(Channel, MicroBlockSnapshot)])], Coeval[CacheSizes]) = { + ): (Observable[(Channel, MicroblockData, Option[(Channel, MicroBlockSnapshotResponse)])], Coeval[CacheSizes]) = { implicit val schdlr: SchedulerService = scheduler @@ -160,7 +159,7 @@ object MicroBlockSynchronizer extends ScorexLogging { .subscribe() microblockSnapshots.observeOn(scheduler).flatMap { case (ch, snapshot) => - BlockStats.received(snapshot, ch, snapshot.totalBlockId) + BlockStats.received(ch, snapshot.totalBlockId) Option(receivedSnapshots.getIfPresent(snapshot.totalBlockId)) match { case Some(_) => waitingForSnapshot.invalidate(snapshot.totalBlockId) diff --git a/node/src/main/scala/com/wavesplatform/network/RxExtensionLoader.scala b/node/src/main/scala/com/wavesplatform/network/RxExtensionLoader.scala index 5bcf284147..f30aca1d4f 100644 --- a/node/src/main/scala/com/wavesplatform/network/RxExtensionLoader.scala +++ b/node/src/main/scala/com/wavesplatform/network/RxExtensionLoader.scala @@ -1,7 +1,7 @@ package com.wavesplatform.network import com.google.common.cache.{Cache, CacheBuilder} -import com.wavesplatform.block.{Block, BlockSnapshot} +import com.wavesplatform.block.Block import com.wavesplatform.block.Block.BlockId import com.wavesplatform.common.state.ByteStr import com.wavesplatform.lang.ValidationError @@ -22,7 +22,7 @@ import monix.reactive.{Observable, Observer} import java.util.concurrent.TimeUnit import scala.concurrent.duration.* -case class ExtensionBlocks(remoteScore: BigInt, blocks: Seq[Block], snapshots: Map[BlockId, BlockSnapshot]) { +case class ExtensionBlocks(remoteScore: BigInt, blocks: Seq[Block], snapshots: Map[BlockId, BlockSnapshotResponse]) { override def toString: String = s"ExtensionBlocks($remoteScore, ${formatSignatures(blocks.map(_.id()))}" } @@ -31,6 +31,8 @@ object RxExtensionLoader extends ScorexLogging { type ApplyExtensionResult = Either[ValidationError, Option[BigInt]] private val dummy = new Object() + type BlockWithSnapshot = (Channel, Block, Option[BlockSnapshotResponse]) + def apply( syncTimeOut: FiniteDuration, processedBlocksCacheTimeout: FiniteDuration, @@ -40,19 +42,20 @@ object RxExtensionLoader extends ScorexLogging { invalidBlocks: InvalidBlockStorage, blocks: Observable[(Channel, Block)], signatures: Observable[(Channel, Signatures)], - snapshots: Observable[(Channel, BlockSnapshot)], + snapshots: Observable[(Channel, BlockSnapshotResponse)], syncWithChannelClosed: Observable[ChannelClosedAndSyncWith], scheduler: SchedulerService, timeoutSubject: Subject[Channel, Channel] )( extensionApplier: (Channel, ExtensionBlocks) => Task[ApplyExtensionResult] - ): (Observable[(Channel, Block, Option[BlockSnapshot])], Coeval[State], RxExtensionLoaderShutdownHook) = { + ): (Observable[BlockWithSnapshot], Coeval[State], RxExtensionLoaderShutdownHook) = { implicit val schdlr: SchedulerService = scheduler val extensions: ConcurrentSubject[(Channel, ExtensionBlocks), (Channel, ExtensionBlocks)] = ConcurrentSubject.publish[(Channel, ExtensionBlocks)] - val simpleBlocksWithSnapshot: ConcurrentSubject[(Channel, Block, Option[BlockSnapshot]), (Channel, Block, Option[BlockSnapshot])] = - ConcurrentSubject.publish[(Channel, Block, Option[BlockSnapshot])] + val simpleBlocksWithSnapshot + : ConcurrentSubject[BlockWithSnapshot, BlockWithSnapshot] = + ConcurrentSubject.publish[BlockWithSnapshot] @volatile var stateValue: State = State(LoaderState.Idle, ApplierState.Idle) val lastSyncWith: Coeval[Option[SyncWith]] = lastObserved(syncWithChannelClosed.map(_.syncWith)) @@ -215,14 +218,14 @@ object RxExtensionLoader extends ScorexLogging { } } - def onSnapshot(state: State, ch: Channel, snapshot: BlockSnapshot): State = { + def onSnapshot(state: State, ch: Channel, snapshot: BlockSnapshotResponse): State = { if (isLightMode) { state.loaderState match { case LoaderState.ExpectingBlocksWithSnapshots(c, requested, expectedBlocks, receivedBlocks, expectedSnapshots, receivedSnapshots, _) if c.channel == ch && expectedSnapshots.contains(snapshot.blockId) => val updatedExpectedSnapshots = expectedSnapshots - snapshot.blockId - BlockStats.received(snapshot, BlockStats.Source.Ext, ch) + BlockStats.receivedSnapshot(snapshot.blockId, BlockStats.Source.Ext, ch) if (updatedExpectedSnapshots.isEmpty && expectedBlocks.isEmpty) { val blockById = receivedBlocks.map(b => b.id() -> b).toMap @@ -247,7 +250,7 @@ object RxExtensionLoader extends ScorexLogging { ) } case _ => - BlockStats.received(snapshot, BlockStats.Source.Broadcast, ch) + BlockStats.receivedSnapshot(snapshot.blockId, BlockStats.Source.Broadcast, ch) Option(receivedSnapshots.getIfPresent(snapshot.blockId)) match { case Some(_) => pendingBlocks.invalidate(snapshot.blockId) @@ -378,7 +381,7 @@ object RxExtensionLoader extends ScorexLogging { expectedBlocks: Set[BlockId], receivedBlocks: Set[Block], expectedSnapshots: Set[BlockId], - receivedSnapshots: Map[BlockId, BlockSnapshot], + receivedSnapshots: Map[BlockId, BlockSnapshotResponse], timeout: CancelableFuture[Unit] ) extends WithPeer { override def toString: String = @@ -390,7 +393,7 @@ object RxExtensionLoader extends ScorexLogging { case class RxExtensionLoaderShutdownHook( extensionChannel: Observer[(Channel, ExtensionBlocks)], - simpleBlocksWithSnapshotChannel: Observer[(Channel, Block, Option[BlockSnapshot])] + simpleBlocksWithSnapshotChannel: Observer[BlockWithSnapshot] ) { def shutdown(): Unit = { extensionChannel.onComplete() diff --git a/node/src/main/scala/com/wavesplatform/protobuf/PBSnapshots.scala b/node/src/main/scala/com/wavesplatform/protobuf/PBSnapshots.scala new file mode 100644 index 0000000000..b363a96400 --- /dev/null +++ b/node/src/main/scala/com/wavesplatform/protobuf/PBSnapshots.scala @@ -0,0 +1,178 @@ +package com.wavesplatform.protobuf + +import com.google.protobuf.ByteString +import com.wavesplatform.account.{Address, Alias, PublicKey} +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.lang.script.ScriptReader +import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot +import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot.NewAsset +import com.wavesplatform.protobuf.transaction.{PBAmounts, PBTransactions} +import com.wavesplatform.state.* +import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.transaction.{Asset, TxPositiveAmount} + +import scala.collection.immutable.VectorMap + +object PBSnapshots { + + import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot as S + + def toProtobuf(snapshot: StateSnapshot, txStatus: TxMeta.Status): TransactionStateSnapshot = { + import snapshot.* + TransactionStateSnapshot( + balances.map { case ((address, asset), balance) => + S.Balance(address.toByteString, Some(PBAmounts.fromAssetAndAmount(asset, balance))) + }.toSeq, + leaseBalances.map { case (address, balance) => + S.LeaseBalance(address.toByteString, balance.in, balance.out) + }.toSeq, + newLeases = snapshot.newLeases.view.map { case (id, ld) => + S.NewLease(id.toByteString, ld.sender.toByteString, ld.recipientAddress.toByteString, ld.amount.value) + }.toSeq, + cancelledLeases = snapshot.cancelledLeases.view.map { case (id, _) => + S.CancelledLease(id.toByteString) + }.toSeq, + assetStatics.map { case (id, st) => + NewAsset(id.id.toByteString, st.issuer.toByteString, st.decimals, st.nft) + }.toSeq, + assetVolumes.map { case (asset, info) => + S.AssetVolume(asset.id.toByteString, info.isReissuable, ByteString.copyFrom(info.volume.toByteArray)) + }.toSeq, + assetNamesAndDescriptions.map { case (asset, info) => + S.AssetNameAndDescription(asset.id.toByteString, info.name.toStringUtf8, info.description.toStringUtf8) + }.toSeq, + assetScripts.map { case (asset, script) => + S.AssetScript(asset.id.toByteString, script.script.bytes().toByteString) + }.headOption, + aliases.map { case (alias, address) => S.Alias(address.toByteString, alias.name) }.headOption, + orderFills.map { case (orderId, VolumeAndFee(volume, fee)) => + S.OrderFill(orderId.toByteString, volume, fee) + }.toSeq, + accountScripts.map { case (publicKey, scriptOpt) => + scriptOpt.fold( + S.AccountScript(publicKey.toByteString) + )(script => + S.AccountScript( + publicKey.toByteString, + script.script.bytes().toByteString, + script.verifierComplexity + ) + ) + }.headOption, + accountData.map { case (address, data) => + S.AccountData(address.toByteString, data.values.map(PBTransactions.toPBDataEntry).toSeq) + }.toSeq, + sponsorships.collect { case (asset, SponsorshipValue(minFee)) => + S.Sponsorship(asset.id.toByteString, minFee) + }.toSeq, + txStatus.protobuf + ) + } + + def fromProtobuf(pbSnapshot: TransactionStateSnapshot, txId: ByteStr, height: Int): (StateSnapshot, TxMeta.Status) = { + val balances: VectorMap[(Address, Asset), Long] = + VectorMap() ++ pbSnapshot.balances.map(b => (b.address.toAddress(), b.getAmount.assetId.toAssetId) -> b.getAmount.amount) + + val leaseBalances: Map[Address, LeaseBalance] = + pbSnapshot.leaseBalances + .map(b => b.address.toAddress() -> LeaseBalance(b.in, b.out)) + .toMap + + val assetScripts: Map[IssuedAsset, AssetScriptInfo] = + pbSnapshot.assetScripts.map { s => + s.assetId.toIssuedAssetId -> AssetScriptInfo(ScriptReader.fromBytes(s.script.toByteArray).explicitGet(), 0) + }.toMap + + val assetStatics: VectorMap[IssuedAsset, AssetStaticInfo] = + VectorMap() ++ pbSnapshot.assetStatics.map(info => + info.assetId.toIssuedAssetId -> AssetStaticInfo( + info.assetId.toByteStr, + TransactionId(txId), + PublicKey(info.issuerPublicKey.toByteStr), + info.decimals, + info.nft + ) + ) + + val assetVolumes: Map[IssuedAsset, AssetVolumeInfo] = + pbSnapshot.assetVolumes + .map(v => v.assetId.toIssuedAssetId -> AssetVolumeInfo(v.reissuable, BigInt(v.volume.toByteArray))) + .toMap + + val assetNamesAndDescriptions: Map[IssuedAsset, AssetInfo] = + pbSnapshot.assetNamesAndDescriptions + .map(i => i.assetId.toIssuedAssetId -> AssetInfo(i.name, i.description, Height @@ height)) + .toMap + + val sponsorships: Map[IssuedAsset, SponsorshipValue] = + pbSnapshot.sponsorships + .map(s => s.assetId.toIssuedAssetId -> SponsorshipValue(s.minFee)) + .toMap + + val newLeases = pbSnapshot.newLeases.map { l => + l.leaseId.toByteStr -> + LeaseStaticInfo(l.senderPublicKey.toPublicKey, l.recipientAddress.toAddress(), TxPositiveAmount.unsafeFrom(l.amount), txId, height) + }.toMap + + val cancelledLeases = pbSnapshot.cancelledLeases.map { cl => + cl.leaseId.toByteStr -> LeaseDetails.Status.Cancelled(height, Some(txId)) + }.toMap + + val aliases: Map[Alias, Address] = + pbSnapshot.aliases + .map(a => Alias.create(a.alias).explicitGet() -> a.address.toAddress()) + .toMap + + val orderFills: Map[ByteStr, VolumeAndFee] = + pbSnapshot.orderFills + .map(of => of.orderId.toByteStr -> VolumeAndFee(of.volume, of.fee)) + .toMap + + val accountScripts: Map[PublicKey, Option[AccountScriptInfo]] = + pbSnapshot.accountScripts.map { pbInfo => + val info = + if (pbInfo.script.isEmpty) + None + else + Some( + AccountScriptInfo( + pbInfo.senderPublicKey.toPublicKey, + ScriptReader.fromBytes(pbInfo.script.toByteArray).explicitGet(), + pbInfo.verifierComplexity + ) + ) + pbInfo.senderPublicKey.toPublicKey -> info + }.toMap + + val accountData: Map[Address, Map[String, DataEntry[?]]] = + pbSnapshot.accountData.map { data => + val entries = + data.entries.map { pbEntry => + val entry = PBTransactions.toVanillaDataEntry(pbEntry) + entry.key -> entry + }.toMap + data.address.toAddress() -> entries + }.toMap + + ( + StateSnapshot( + VectorMap(), + balances, + leaseBalances, + assetStatics, + assetVolumes, + assetNamesAndDescriptions, + assetScripts, + sponsorships, + newLeases, + cancelledLeases, + aliases, + orderFills, + accountScripts, + accountData + ), + TxMeta.Status.fromProtobuf(pbSnapshot.transactionStatus) + ) + } +} diff --git a/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBTransactions.scala b/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBTransactions.scala index 00598610b8..c7a056c446 100644 --- a/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBTransactions.scala +++ b/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBTransactions.scala @@ -25,7 +25,16 @@ import com.wavesplatform.transaction.smart.InvokeExpressionTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.transfer.MassTransferTransaction import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer -import com.wavesplatform.transaction.{EthereumTransaction, Proofs, TxDecimals, TxExchangeAmount, TxExchangePrice, TxNonNegativeAmount, TxPositiveAmount, TxValidationError} +import com.wavesplatform.transaction.{ + EthereumTransaction, + Proofs, + TxDecimals, + TxExchangeAmount, + TxExchangePrice, + TxNonNegativeAmount, + TxPositiveAmount, + TxValidationError +} import com.wavesplatform.utils.StringBytes import scalapb.UnknownFieldSet.empty @@ -74,33 +83,34 @@ object PBTransactions { .map(pk => PublicKey(pk.toByteArray)) .orNull for { - tx <- if (unsafe) - Right( - createVanillaUnsafe( - parsedTx.version, - parsedTx.chainId.toByte, - sender, - feeAmount, - feeAsset, - parsedTx.timestamp, - Proofs(signedTx.proofs.map(_.toByteStr)), - parsedTx.data - ) - ) - else - for { - proofs <- Proofs.create(signedTx.proofs.map(_.toByteStr)) - tx <- createVanilla( - parsedTx.version, - parsedTx.chainId.toByte, - sender, - feeAmount, - feeAsset, - parsedTx.timestamp, - proofs, - parsedTx.data + tx <- + if (unsafe) + Right( + createVanillaUnsafe( + parsedTx.version, + parsedTx.chainId.toByte, + sender, + feeAmount, + feeAsset, + parsedTx.timestamp, + Proofs(signedTx.proofs.map(_.toByteStr)), + parsedTx.data + ) ) - } yield tx + else + for { + proofs <- Proofs.create(signedTx.proofs.map(_.toByteStr)) + tx <- createVanilla( + parsedTx.version, + parsedTx.chainId.toByte, + sender, + feeAmount, + feeAsset, + parsedTx.timestamp, + proofs, + parsedTx.data + ) + } yield tx } yield tx } @@ -696,8 +706,8 @@ object PBTransactions { ) } - def toVanillaDataEntry(de: DataTransactionData.DataEntry): com.wavesplatform.state.DataEntry[?] = { - import DataTransactionData.DataEntry.Value as DEV + def toVanillaDataEntry(de: DataEntry): com.wavesplatform.state.DataEntry[?] = { + import DataEntry.Value as DEV de.value match { case DEV.IntValue(num) => IntegerDataEntry(de.key, num) @@ -708,15 +718,15 @@ object PBTransactions { } } - def toPBDataEntry(de: com.wavesplatform.state.DataEntry[?]): DataTransactionData.DataEntry = { - DataTransactionData.DataEntry( + def toPBDataEntry(de: com.wavesplatform.state.DataEntry[?]): DataEntry = { + DataEntry( de.key, de match { - case IntegerDataEntry(_, value) => DataTransactionData.DataEntry.Value.IntValue(value) - case BooleanDataEntry(_, value) => DataTransactionData.DataEntry.Value.BoolValue(value) - case BinaryDataEntry(_, value) => DataTransactionData.DataEntry.Value.BinaryValue(value.toByteString) - case StringDataEntry(_, value) => DataTransactionData.DataEntry.Value.StringValue(value) - case EmptyDataEntry(_) => DataTransactionData.DataEntry.Value.Empty + case IntegerDataEntry(_, value) => DataEntry.Value.IntValue(value) + case BooleanDataEntry(_, value) => DataEntry.Value.BoolValue(value) + case BinaryDataEntry(_, value) => DataEntry.Value.BinaryValue(value.toByteString) + case StringDataEntry(_, value) => DataEntry.Value.StringValue(value) + case EmptyDataEntry(_) => DataEntry.Value.Empty } ) } diff --git a/node/src/main/scala/com/wavesplatform/state/Blockchain.scala b/node/src/main/scala/com/wavesplatform/state/Blockchain.scala index 448d0ef1f6..f0b949ecde 100644 --- a/node/src/main/scala/com/wavesplatform/state/Blockchain.scala +++ b/node/src/main/scala/com/wavesplatform/state/Blockchain.scala @@ -11,7 +11,7 @@ import com.wavesplatform.lang.script.ContractScript import com.wavesplatform.lang.v1.ContractLimits import com.wavesplatform.lang.v1.traits.domain.Issue import com.wavesplatform.settings.BlockchainSettings -import com.wavesplatform.state.reader.LeaseDetails +import com.wavesplatform.state.LeaseDetails import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.AliasDoesNotExist import com.wavesplatform.transaction.assets.IssueTransaction diff --git a/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala b/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala index 90ccafbdb2..b7297d641e 100644 --- a/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala +++ b/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala @@ -18,11 +18,9 @@ import com.wavesplatform.mining.{Miner, MiningConstraint, MiningConstraints} import com.wavesplatform.settings.{BlockchainSettings, WavesSettings} import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult.{Applied, Ignored} import com.wavesplatform.state.diffs.BlockDiffer -import com.wavesplatform.state.reader.{LeaseDetails, SnapshotBlockchain} import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.{BlockAppendError, GenericError, MicroBlockAppendError} -import com.wavesplatform.transaction.lease.* import com.wavesplatform.transaction.transfer.TransferTransactionLike import com.wavesplatform.utils.{ScorexLogging, Time, UnsupportedFeature, forceStopApplication} import kamon.Kamon @@ -37,7 +35,7 @@ class BlockchainUpdaterImpl( wavesSettings: WavesSettings, time: Time, blockchainUpdateTriggers: BlockchainUpdateTriggers, - collectActiveLeases: (Int, Int) => Seq[LeaseTransaction], + collectActiveLeases: (Int, Int) => Map[ByteStr, LeaseDetails], miner: Miner = _ => () ) extends Blockchain with BlockchainUpdater @@ -55,7 +53,7 @@ class BlockchainUpdaterImpl( private val lock = new ReentrantReadWriteLock(true) private def writeLock[B](f: => B): B = inLock(lock.writeLock(), f) - def readLock[B](f: => B): B = inLock(lock.readLock(), f) + private def readLock[B](f: => B): B = inLock(lock.readLock(), f) private lazy val maxBlockReadinessAge = wavesSettings.minerSettings.intervalAfterLastBlockThenGenerationIsAllowed.toMillis @@ -419,7 +417,7 @@ class BlockchainUpdaterImpl( ) } - private def collectLeasesToCancel(newHeight: Int): Seq[LeaseTransaction] = + private def collectLeasesToCancel(newHeight: Int): Map[ByteStr, LeaseDetails] = if (rocksdb.isFeatureActivated(BlockchainFeatures.LeaseExpiration, newHeight)) { val toHeight = newHeight - rocksdb.settings.functionalitySettings.leaseExpiration val fromHeight = rocksdb.featureActivationHeight(BlockchainFeatures.LeaseExpiration.id) match { @@ -431,25 +429,23 @@ class BlockchainUpdaterImpl( toHeight } collectActiveLeases(fromHeight, toHeight) - } else Seq.empty - - private def cancelLeases(leaseTransactions: Seq[LeaseTransaction], height: Int): Map[ByteStr, StateSnapshot] = { - val snapshotsById = - for { - lt <- leaseTransactions - ltMeta <- transactionMeta(lt.id()).toSeq - recipient <- rocksdb.resolveAlias(lt.recipient).toSeq - portfolios = Map( - lt.sender.toAddress -> Portfolio(0, LeaseBalance(0, -lt.amount.value)), - recipient -> Portfolio(0, LeaseBalance(-lt.amount.value, 0)) - ) - leaseStates = Map( - lt.id() -> LeaseDetails(lt.sender, lt.recipient, lt.amount.value, LeaseDetails.Status.Expired(height), lt.id(), ltMeta.height) + } else Map.empty + + private def cancelLeases(leaseDetails: Map[ByteStr, LeaseDetails], height: Int): Map[ByteStr, StateSnapshot] = + for { + (id, ld) <- leaseDetails + } yield id -> StateSnapshot + .build( + rocksdb, + Map( + ld.sender.toAddress -> Portfolio(0, LeaseBalance(0, -ld.amount.value)), + ld.recipientAddress -> Portfolio(0, LeaseBalance(-ld.amount.value, 0)) + ), + cancelledLeases = Map( + id -> LeaseDetails.Status.Expired(height) ) - snapshot = StateSnapshot.build(rocksdb, portfolios, leaseStates = leaseStates).explicitGet() - } yield lt.id() -> snapshot - snapshotsById.toMap - } + ) + .explicitGet() override def removeAfter(blockId: ByteStr): Either[ValidationError, DiscardedBlocks] = writeLock { log.info(s"Trying rollback blockchain to $blockId") @@ -822,7 +818,7 @@ object BlockchainUpdaterImpl { private def displayFeatures(s: Set[Short]): String = s"FEATURE${if (s.size > 1) "S" else ""} ${s.mkString(", ")} ${if (s.size > 1) "have been" else "has been"}" - def areVersionsOfSameBlock(b1: Block, b2: Block): Boolean = + private def areVersionsOfSameBlock(b1: Block, b2: Block): Boolean = b1.header.generator == b2.header.generator && b1.header.baseTarget == b2.header.baseTarget && b1.header.reference == b2.header.reference && diff --git a/node/src/main/scala/com/wavesplatform/state/Diff.scala b/node/src/main/scala/com/wavesplatform/state/Diff.scala deleted file mode 100755 index e69de29bb2..0000000000 diff --git a/node/src/main/scala/com/wavesplatform/state/reader/LeaseDetails.scala b/node/src/main/scala/com/wavesplatform/state/LeaseDetails.scala similarity index 55% rename from node/src/main/scala/com/wavesplatform/state/reader/LeaseDetails.scala rename to node/src/main/scala/com/wavesplatform/state/LeaseDetails.scala index 66cd1ea363..34f1ea64b6 100644 --- a/node/src/main/scala/com/wavesplatform/state/reader/LeaseDetails.scala +++ b/node/src/main/scala/com/wavesplatform/state/LeaseDetails.scala @@ -1,14 +1,16 @@ -package com.wavesplatform.state.reader +package com.wavesplatform.state -import com.wavesplatform.account.{AddressOrAlias, PublicKey} +import com.wavesplatform.account.{Address, PublicKey} import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.transaction.TxPositiveAmount object LeaseDetails { sealed trait Status object Status { case object Active extends Status - final case class Cancelled(height: Int, txId: Option[ByteStr]) extends Status - final case class Expired(height: Int) extends Status + sealed trait Inactive extends Status + final case class Cancelled(height: Int, txId: Option[ByteStr]) extends Inactive + final case class Expired(height: Int) extends Inactive implicit class StatusExt(val status: Status) extends AnyVal { def cancelHeight: Option[Int] = status match { @@ -26,6 +28,14 @@ object LeaseDetails { } } -case class LeaseDetails(sender: PublicKey, recipient: AddressOrAlias, amount: Long, status: LeaseDetails.Status, sourceId: ByteStr, height: Int) { - def isActive: Boolean = status == LeaseDetails.Status.Active +case class LeaseDetails( + static: LeaseStaticInfo, + status: LeaseDetails.Status +) { + def isActive: Boolean = status == LeaseDetails.Status.Active + def sender: PublicKey = static.sender + def sourceId: ByteStr = static.sourceId + def amount: TxPositiveAmount = static.amount + def height: Int = static.height + def recipientAddress: Address = static.recipientAddress } diff --git a/node/src/main/scala/com/wavesplatform/state/LeaseStaticInfo.scala b/node/src/main/scala/com/wavesplatform/state/LeaseStaticInfo.scala new file mode 100644 index 0000000000..5b17fe3ad3 --- /dev/null +++ b/node/src/main/scala/com/wavesplatform/state/LeaseStaticInfo.scala @@ -0,0 +1,13 @@ +package com.wavesplatform.state + +import com.wavesplatform.account.{Address, PublicKey} +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.transaction.TxPositiveAmount + +case class LeaseStaticInfo( + sender: PublicKey, + recipientAddress: Address, + amount: TxPositiveAmount, + sourceId: ByteStr, + height: Int +) diff --git a/node/src/main/scala/com/wavesplatform/state/NewTransactionInfo.scala b/node/src/main/scala/com/wavesplatform/state/NewTransactionInfo.scala index fb8668ecab..9945f45b9e 100644 --- a/node/src/main/scala/com/wavesplatform/state/NewTransactionInfo.scala +++ b/node/src/main/scala/com/wavesplatform/state/NewTransactionInfo.scala @@ -68,10 +68,7 @@ object NewTransactionInfo { case t: LeaseCancelTransaction => Set(t.sender.toAddress) ++ blockchain .leaseDetails(t.leaseId) - .flatMap(_.recipient match { - case alias: Alias => blockchain.resolveAlias(alias).toOption - case address: Address => Some(address) - }) + .map(_.recipientAddress) .toSet case t: LeaseTransaction => Set(t.sender.toAddress) ++ (t.recipient match { @@ -79,12 +76,12 @@ object NewTransactionInfo { case address: Address => Set(address) }) case t: MassTransferTransaction => - Set(t.sender.toAddress) ++ (t.transfers.flatMap { + Set(t.sender.toAddress) ++ t.transfers.flatMap { _.address match { case alias: Alias => blockchain.resolveAlias(alias).toOption.toSet case address: Address => Set(address) } - }) + } case t: ReissueTransaction => Set(t.sender.toAddress) case t: SetAssetScriptTransaction => Set(t.sender.toAddress) case t: SetScriptTransaction => Set(t.sender.toAddress) diff --git a/node/src/main/scala/com/wavesplatform/state/NgState.scala b/node/src/main/scala/com/wavesplatform/state/NgState.scala index 52f6e43c8d..323314e816 100644 --- a/node/src/main/scala/com/wavesplatform/state/NgState.scala +++ b/node/src/main/scala/com/wavesplatform/state/NgState.scala @@ -61,7 +61,7 @@ case class NgState( ) { def cancelExpiredLeases(snapshot: StateSnapshot): StateSnapshot = leasesToCancel - .collect { case (id, ld) if snapshot.leaseStates.get(id).forall(_.isActive) => ld } + .collect { case (id, ld) if !snapshot.cancelledLeases.contains(id) => ld } .toList .foldLeft(snapshot)(_ |+| _) diff --git a/node/src/main/scala/com/wavesplatform/state/OverriddenBlockchain.scala b/node/src/main/scala/com/wavesplatform/state/OverriddenBlockchain.scala deleted file mode 100644 index 258c670486..0000000000 --- a/node/src/main/scala/com/wavesplatform/state/OverriddenBlockchain.scala +++ /dev/null @@ -1,110 +0,0 @@ -package com.wavesplatform.state - -import cats.syntax.option.* -import com.wavesplatform.account.{Address, Alias} -import com.wavesplatform.block.Block.BlockId -import com.wavesplatform.block.SignedBlockHeader -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.lang.ValidationError -import com.wavesplatform.settings.BlockchainSettings -import com.wavesplatform.state.reader.LeaseDetails -import com.wavesplatform.transaction.transfer.TransferTransactionLike -import com.wavesplatform.transaction.{Asset, ERC20Address, Transaction} - -class OverriddenBlockchain(underlying: Blockchain, overrides: BlockchainOverrides) extends Blockchain { - override def settings: BlockchainSettings = underlying.settings - - override def hasData(address: Address): Boolean = underlying.hasData(address) - - override def accountData(acc: Address, key: String): Option[DataEntry[?]] = underlying.accountData(acc, key) - - override def accountScript(address: Address): Option[AccountScriptInfo] = underlying.accountScript(address) - - override def hasAccountScript(address: Address): Boolean = accountScript(address).nonEmpty - - override def blockHeader(height: Int): Option[SignedBlockHeader] = underlying.blockHeader(height) - - override def hitSource(height: Int): Option[ByteStr] = underlying.hitSource(height) - - override def height: Int = underlying.height - - override def activatedFeatures: Map[Short, Int] = underlying.activatedFeatures - - override def assetDescription(id: Asset.IssuedAsset): Option[AssetDescription] = underlying.assetDescription(id) - - override def assetScript(id: Asset.IssuedAsset): Option[AssetScriptInfo] = underlying.assetScript(id) - - override def resolveAlias(a: Alias): Either[ValidationError, Address] = underlying.resolveAlias(a) - - override def leaseBalance(address: Address): LeaseBalance = underlying.leaseBalance(address) - - override def balance(address: Address, mayBeAssetId: Asset): Long = - overrides.balance(address, mayBeAssetId).getOrElse(underlying.balance(address, mayBeAssetId)) - - override def transactionInfos(ids: Seq[BlockId]): Seq[Option[(TxMeta, Transaction)]] = underlying.transactionInfos(ids) - - override def leaseBalances(addresses: Seq[Address]): Map[Address, LeaseBalance] = underlying.leaseBalances(addresses) - - override def balances(req: Seq[(Address, Asset)]): Map[(Address, Asset), Long] = - req.map { case k@(addr, asset) => k -> overrides.balance(addr, asset).getOrElse(underlying.balance(addr, asset)) }.toMap - - override def wavesBalances(addresses: Seq[Address]): Map[Address, Long] = - addresses.map(addr => addr -> overrides.balance(addr, Asset.Waves).getOrElse(underlying.balance(addr))).toMap - - override def effectiveBalanceBanHeights(address: Address): Seq[Int] = underlying.effectiveBalanceBanHeights(address) - - override def lastStateHash(refId: Option[BlockId]): BlockId = underlying.lastStateHash(refId) - - // Ride: wavesBalance (specifies to=None) - /** Retrieves Waves balance snapshot in the [from, to] range (inclusive) */ - override def balanceSnapshots(address: Address, from: Int, to: Option[BlockId]): Seq[BalanceSnapshot] = { - val orig = underlying.balanceSnapshots(address, from, to) - val toHeight = to.flatMap(this.heightOf).getOrElse(this.height) - if (toHeight < this.height) orig - else - overrides - .balance(address, Asset.Waves) - .fold(orig) { regularBalance => - orig.headOption match { - case None => Seq(BalanceSnapshot(toHeight, regularBalance, 0, 0)) - case Some(latest) => latest.copy(toHeight, regularBalance) +: orig - } - } - } - - override def balanceAtHeight(address: Address, height: Int, assetId: Asset): Option[(Int, Long)] = { - lazy val orig = underlying.balanceAtHeight(address, height, assetId) - if (height < this.height) orig - else overrides.balance(address, assetId).fold(orig)(b => (height, b).some) - } - - override def transactionMeta(id: ByteStr): Option[TxMeta] = underlying.transactionMeta(id) - - override def transferById(id: ByteStr): Option[(Int, TransferTransactionLike)] = underlying.transferById(id) - - override def score: BigInt = underlying.score - - override def carryFee(refId: Option[BlockId]): Long = underlying.carryFee(refId) - - override def heightOf(blockId: ByteStr): Option[Int] = underlying.heightOf(blockId) - - override def approvedFeatures: Map[Short, Int] = underlying.approvedFeatures - - override def featureVotes(height: Int): Map[Short, Int] = underlying.featureVotes(height) - - override def containsTransaction(tx: Transaction): Boolean = underlying.containsTransaction(tx) - - override def leaseDetails(leaseId: ByteStr): Option[LeaseDetails] = underlying.leaseDetails(leaseId) - - override def filledVolumeAndFee(orderId: ByteStr): VolumeAndFee = underlying.filledVolumeAndFee(orderId) - - override def transactionInfo(id: BlockId): Option[(TxMeta, Transaction)] = underlying.transactionInfo(id) - - override def blockReward(height: Int): Option[Long] = underlying.blockReward(height) - - override def blockRewardVotes(height: Int): Seq[Long] = underlying.blockRewardVotes(height) - - override def wavesAmount(height: Int): BigInt = underlying.wavesAmount(height) - - override def resolveERC20Address(address: ERC20Address): Option[Asset.IssuedAsset] = underlying.resolveERC20Address(address) -} diff --git a/node/src/main/scala/com/wavesplatform/state/reader/SnapshotBlockchain.scala b/node/src/main/scala/com/wavesplatform/state/SnapshotBlockchain.scala similarity index 96% rename from node/src/main/scala/com/wavesplatform/state/reader/SnapshotBlockchain.scala rename to node/src/main/scala/com/wavesplatform/state/SnapshotBlockchain.scala index d8347f6c35..51146f08bb 100644 --- a/node/src/main/scala/com/wavesplatform/state/reader/SnapshotBlockchain.scala +++ b/node/src/main/scala/com/wavesplatform/state/SnapshotBlockchain.scala @@ -1,4 +1,4 @@ -package com.wavesplatform.state.reader +package com.wavesplatform.state import cats.syntax.option.* import com.wavesplatform.account.{Address, Alias} @@ -7,9 +7,7 @@ import com.wavesplatform.block.{Block, SignedBlockHeader} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.features.BlockchainFeatures.RideV6 import com.wavesplatform.lang.ValidationError -import com.wavesplatform.protobuf.ByteStringExt import com.wavesplatform.settings.BlockchainSettings -import com.wavesplatform.state.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.{AliasDoesNotExist, AliasIsDisabled} import com.wavesplatform.transaction.transfer.{TransferTransaction, TransferTransactionLike} @@ -84,8 +82,13 @@ case class SnapshotBlockchain( override def assetDescription(asset: IssuedAsset): Option[AssetDescription] = SnapshotBlockchain.assetDescription(asset, snapshot, height, inner) - override def leaseDetails(leaseId: ByteStr): Option[LeaseDetails] = - snapshot.leaseStates.get(leaseId).orElse(inner.leaseDetails(leaseId)) + override def leaseDetails(leaseId: ByteStr): Option[LeaseDetails] = { + val newer = snapshot.newLeases.get(leaseId).map(n => LeaseDetails(n, LeaseDetails.Status.Active)).orElse(inner.leaseDetails(leaseId)) + snapshot.cancelledLeases.get(leaseId) match { + case Some(newStatus) => newer.map(_.copy(status = newStatus)) + case None => newer + } + } override def transferById(id: ByteStr): Option[(Int, TransferTransactionLike)] = snapshot.transactions @@ -262,8 +265,8 @@ object SnapshotBlockchain { .get(asset) .map { case (static, assetNum) => AssetDescription( - static.sourceTransactionId.toByteStr, - static.issuerPublicKey.toPublicKey, + static.source, + static.issuer, info.get.name, info.get.description, static.decimals, diff --git a/node/src/main/scala/com/wavesplatform/state/StateHashBuilder.scala b/node/src/main/scala/com/wavesplatform/state/StateHashBuilder.scala index a8f1bac719..612ef4bb03 100644 --- a/node/src/main/scala/com/wavesplatform/state/StateHashBuilder.scala +++ b/node/src/main/scala/com/wavesplatform/state/StateHashBuilder.scala @@ -84,9 +84,9 @@ class StateHashBuilder { ) } - def addLeaseStatus(leaseId: ByteStr, status: Boolean): Unit = { + def addLeaseStatus(leaseId: ByteStr, isActive: Boolean): Unit = { addEntry(SectionId.LeaseStatus, leaseId.arr)( - if (status) Array(1: Byte) else Array(0: Byte) + if (isActive) Array(1: Byte) else Array(0: Byte) ) } diff --git a/node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala b/node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala index e4c812f361..febef0993b 100644 --- a/node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala +++ b/node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala @@ -1,20 +1,12 @@ package com.wavesplatform.state + import cats.data.Ior import cats.implicits.{catsSyntaxEitherId, catsSyntaxSemigroup, toBifunctorOps, toTraverseOps} import cats.kernel.Monoid -import com.google.protobuf.ByteString -import com.wavesplatform.account.{Address, AddressScheme, Alias, PublicKey} +import com.wavesplatform.account.{Address, Alias, PublicKey} import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.database.protobuf.EthereumTransactionMeta import com.wavesplatform.lang.ValidationError -import com.wavesplatform.lang.script.ScriptReader -import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot -import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot.AssetStatic -import com.wavesplatform.protobuf.transaction.{PBAmounts, PBRecipients, PBTransactions} -import com.wavesplatform.protobuf.{AddressExt, ByteStrExt, ByteStringExt} -import com.wavesplatform.state.reader.LeaseDetails.Status -import com.wavesplatform.state.reader.{LeaseDetails, SnapshotBlockchain} import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.{Asset, Transaction} @@ -23,14 +15,15 @@ import scala.collection.immutable.VectorMap case class StateSnapshot( transactions: VectorMap[ByteStr, NewTransactionInfo] = VectorMap(), - balances: VectorMap[(Address, Asset), Long] = VectorMap(), + balances: VectorMap[(Address, Asset), Long] = VectorMap(), // VectorMap is used to preserve the order of NFTs for a given address leaseBalances: Map[Address, LeaseBalance] = Map(), - assetStatics: VectorMap[IssuedAsset, AssetStatic] = VectorMap(), + assetStatics: VectorMap[IssuedAsset, AssetStaticInfo] = VectorMap(), assetVolumes: Map[IssuedAsset, AssetVolumeInfo] = Map(), assetNamesAndDescriptions: Map[IssuedAsset, AssetInfo] = Map(), assetScripts: Map[IssuedAsset, AssetScriptInfo] = Map(), sponsorships: Map[IssuedAsset, SponsorshipValue] = Map(), - leaseStates: Map[ByteStr, LeaseDetails] = Map(), + newLeases: Map[ByteStr, LeaseStaticInfo] = Map(), + cancelledLeases: Map[ByteStr, LeaseDetails.Status.Inactive] = Map.empty, aliases: Map[Alias, Address] = Map(), orderFills: Map[ByteStr, VolumeAndFee] = Map(), accountScripts: Map[PublicKey, Option[AccountScriptInfo]] = Map(), @@ -39,68 +32,6 @@ case class StateSnapshot( ethereumTransactionMeta: Map[ByteStr, EthereumTransactionMeta] = Map(), scriptsComplexity: Long = 0 ) { - import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot as S - - def toProtobuf(txStatus: TxMeta.Status): TransactionStateSnapshot = - TransactionStateSnapshot( - balances.map { case ((address, asset), balance) => - S.Balance(address.toByteString, Some(PBAmounts.fromAssetAndAmount(asset, balance))) - }.toSeq, - leaseBalances.map { case (address, balance) => - S.LeaseBalance(address.toByteString, balance.in, balance.out) - }.toSeq, - assetStatics.values.toSeq, - assetVolumes.map { case (asset, info) => - S.AssetVolume(asset.id.toByteString, info.isReissuable, ByteString.copyFrom(info.volume.toByteArray)) - }.toSeq, - assetNamesAndDescriptions.map { case (asset, info) => - S.AssetNameAndDescription(asset.id.toByteString, info.name.toStringUtf8, info.description.toStringUtf8, info.lastUpdatedAt) - }.toSeq, - assetScripts.map { case (asset, script) => - S.AssetScript(asset.id.toByteString, script.script.bytes().toByteString) - }.toSeq, - aliases.map { case (alias, address) => S.Alias(address.toByteString, alias.name) }.toSeq, - orderFills.map { case (orderId, VolumeAndFee(volume, fee)) => - S.OrderFill(orderId.toByteString, volume, fee) - }.toSeq, - leaseStates.map { case (leaseId, LeaseDetails(sender, recipient, amount, status, sourceId, height)) => - val pbStatus = status match { - case Status.Active => - S.LeaseState.Status.Active(S.LeaseState.Active()) - case Status.Cancelled(cancelHeight, txId) => - S.LeaseState.Status.Cancelled(S.LeaseState.Cancelled(cancelHeight, txId.fold(ByteString.EMPTY)(_.toByteString))) - case Status.Expired(expiredHeight) => - S.LeaseState.Status.Cancelled(S.LeaseState.Cancelled(expiredHeight)) - } - S.LeaseState( - leaseId.toByteString, - pbStatus, - amount, - sender.toByteString, - ByteString.copyFrom(recipient.asInstanceOf[Address].bytes), - sourceId.toByteString, - height - ) - }.toSeq, - accountScripts.map { case (publicKey, scriptOpt) => - scriptOpt.fold( - S.AccountScript(publicKey.toByteString) - )(script => - S.AccountScript( - publicKey.toByteString, - script.script.bytes().toByteString, - script.verifierComplexity - ) - ) - }.toSeq, - accountData.map { case (address, data) => - S.AccountData(address.toByteString, data.values.map(PBTransactions.toPBDataEntry).toSeq) - }.toSeq, - sponsorships.collect { case (asset, SponsorshipValue(minFee)) => - S.Sponsorship(asset.id.toByteString, minFee) - }.toSeq, - txStatus.protobuf - ) // ignores lease balances from portfolios def addBalances(portfolios: Map[Address, Portfolio], blockchain: Blockchain): Either[String, StateSnapshot] = @@ -128,7 +59,7 @@ case class StateSnapshot( transactions = transactions + (tx.id() -> NewTransactionInfo.create(tx, TxMeta.Status.Elided, StateSnapshot.empty, blockchain)) ) - lazy val indexedAssetStatics: Map[IssuedAsset, (AssetStatic, Int)] = + lazy val indexedAssetStatics: Map[IssuedAsset, (AssetStaticInfo, Int)] = assetStatics.zipWithIndex.map { case ((asset, static), i) => asset -> (static, i + 1) }.toMap lazy val accountScriptsByAddress: Map[Address, Option[AccountScriptInfo]] = @@ -139,112 +70,6 @@ case class StateSnapshot( } object StateSnapshot { - def fromProtobuf(pbSnapshot: TransactionStateSnapshot): (StateSnapshot, TxMeta.Status) = { - val balances: VectorMap[(Address, Asset), Long] = - VectorMap() ++ pbSnapshot.balances.map(b => (b.address.toAddress(), b.getAmount.assetId.toAssetId) -> b.getAmount.amount) - - val leaseBalances: Map[Address, LeaseBalance] = - pbSnapshot.leaseBalances - .map(b => b.address.toAddress() -> LeaseBalance(b.in, b.out)) - .toMap - - val assetScripts: Map[IssuedAsset, AssetScriptInfo] = - pbSnapshot.assetScripts.map { s => - s.assetId.toIssuedAssetId -> AssetScriptInfo(ScriptReader.fromBytes(s.script.toByteArray).explicitGet(), 0) - }.toMap - - val assetStatics: VectorMap[IssuedAsset, AssetStatic] = - VectorMap() ++ pbSnapshot.assetStatics.map(info => info.assetId.toIssuedAssetId -> info) - - val assetVolumes: Map[IssuedAsset, AssetVolumeInfo] = - pbSnapshot.assetVolumes - .map(v => v.assetId.toIssuedAssetId -> AssetVolumeInfo(v.reissuable, BigInt(v.volume.toByteArray))) - .toMap - - val assetNamesAndDescriptions: Map[IssuedAsset, AssetInfo] = - pbSnapshot.assetNamesAndDescriptions - .map(i => i.assetId.toIssuedAssetId -> AssetInfo(i.name, i.description, Height @@ i.lastUpdated)) - .toMap - - val sponsorships: Map[IssuedAsset, SponsorshipValue] = - pbSnapshot.sponsorships - .map(s => s.assetId.toIssuedAssetId -> SponsorshipValue(s.minFee)) - .toMap - - val leaseStates: Map[ByteStr, LeaseDetails] = - pbSnapshot.leaseStates - .map(ls => - ls.leaseId.toByteStr -> LeaseDetails( - ls.sender.toPublicKey, - PBRecipients.toAddress(ls.recipient.toByteArray, AddressScheme.current.chainId).explicitGet(), - ls.amount, - ls.status match { - case TransactionStateSnapshot.LeaseState.Status.Cancelled(c) => - LeaseDetails.Status.Cancelled(c.height, if (c.transactionId.isEmpty) None else Some(c.transactionId.toByteStr)) - case _ => - LeaseDetails.Status.Active - }, - ls.originTransactionId.toByteStr, - ls.height - ) - ) - .toMap - - val aliases: Map[Alias, Address] = - pbSnapshot.aliases - .map(a => Alias.create(a.alias).explicitGet() -> a.address.toAddress()) - .toMap - - val orderFills: Map[ByteStr, VolumeAndFee] = - pbSnapshot.orderFills - .map(of => of.orderId.toByteStr -> VolumeAndFee(of.volume, of.fee)) - .toMap - - val accountScripts: Map[PublicKey, Option[AccountScriptInfo]] = - pbSnapshot.accountScripts.map { pbInfo => - val info = - if (pbInfo.script.isEmpty) - None - else - Some( - AccountScriptInfo( - pbInfo.senderPublicKey.toPublicKey, - ScriptReader.fromBytes(pbInfo.script.toByteArray).explicitGet(), - pbInfo.verifierComplexity - ) - ) - pbInfo.senderPublicKey.toPublicKey -> info - }.toMap - - val accountData: Map[Address, Map[String, DataEntry[?]]] = - pbSnapshot.accountData.map { data => - val entries = - data.entries.map { pbEntry => - val entry = PBTransactions.toVanillaDataEntry(pbEntry) - entry.key -> entry - }.toMap - data.address.toAddress() -> entries - }.toMap - - ( - StateSnapshot( - VectorMap(), - balances, - leaseBalances, - assetStatics, - assetVolumes, - assetNamesAndDescriptions, - assetScripts, - sponsorships, - leaseStates, - aliases, - orderFills, - accountScripts, - accountData - ), - TxMeta.Status.fromProtobuf(pbSnapshot.transactionStatus) - ) - } def build( blockchain: Blockchain, @@ -254,7 +79,8 @@ object StateSnapshot { updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]] = Map(), assetScripts: Map[IssuedAsset, AssetScriptInfo] = Map(), sponsorships: Map[IssuedAsset, Sponsorship] = Map(), - leaseStates: Map[ByteStr, LeaseDetails] = Map(), + newLeases: Map[ByteStr, LeaseStaticInfo] = Map(), + cancelledLeases: Map[ByteStr, LeaseDetails.Status.Inactive] = Map.empty, aliases: Map[Alias, Address] = Map(), accountData: Map[Address, Map[String, DataEntry[?]]] = Map(), accountScripts: Map[PublicKey, Option[AccountScriptInfo]] = Map(), @@ -277,7 +103,8 @@ object StateSnapshot { assetNamesAndDescriptions(issuedAssets, updatedAssets), assetScripts, sponsorships.collect { case (asset, value: SponsorshipValue) => (asset, value) }, - resolvedLeaseStates(blockchain, leaseStates, aliases), + newLeases, + cancelledLeases, aliases, of, accountScripts, @@ -337,16 +164,9 @@ object StateSnapshot { } .map(_.toMap) - private def assetStatics(issuedAssets: VectorMap[IssuedAsset, NewAssetInfo]): VectorMap[IssuedAsset, AssetStatic] = + def assetStatics(issuedAssets: VectorMap[IssuedAsset, NewAssetInfo]): VectorMap[IssuedAsset, AssetStaticInfo] = issuedAssets.map { case (asset, info) => - asset -> - AssetStatic( - asset.id.toByteString, - info.static.source.toByteString, - info.static.issuer.toByteString, - info.static.decimals, - info.static.nft - ) + asset -> info.static } private def assetVolumes( @@ -379,20 +199,6 @@ object StateSnapshot { issued ++ updated } - private def resolvedLeaseStates( - blockchain: Blockchain, - leaseStates: Map[ByteStr, LeaseDetails], - aliases: Map[Alias, Address] - ): Map[ByteStr, LeaseDetails] = - leaseStates.view - .mapValues(details => - details.copy(recipient = details.recipient match { - case address: Address => address - case alias: Alias => aliases.getOrElse(alias, blockchain.resolveAlias(alias).explicitGet()) - }) - ) - .toMap - private def orderFills(volumeAndFees: Map[ByteStr, VolumeAndFee], blockchain: Blockchain): Either[String, Map[ByteStr, VolumeAndFee]] = volumeAndFees.toSeq .traverse { case (orderId, value) => @@ -414,7 +220,8 @@ object StateSnapshot { s1.assetNamesAndDescriptions ++ s2.assetNamesAndDescriptions, s1.assetScripts ++ s2.assetScripts, s1.sponsorships ++ s2.sponsorships, - s1.leaseStates ++ s2.leaseStates, + s1.newLeases ++ s2.newLeases, + s1.cancelledLeases ++ s2.cancelledLeases, s1.aliases ++ s2.aliases, s1.orderFills ++ s2.orderFills, s1.accountScripts ++ s2.accountScripts, diff --git a/node/src/main/scala/com/wavesplatform/state/TxStateSnapshotHashBuilder.scala b/node/src/main/scala/com/wavesplatform/state/TxStateSnapshotHashBuilder.scala index d2b4dc46af..09f31f7897 100644 --- a/node/src/main/scala/com/wavesplatform/state/TxStateSnapshotHashBuilder.scala +++ b/node/src/main/scala/com/wavesplatform/state/TxStateSnapshotHashBuilder.scala @@ -2,7 +2,7 @@ package com.wavesplatform.state import cats.implicits.catsSyntaxSemigroup import cats.syntax.either.* -import com.google.common.primitives.{Ints, Longs} +import com.google.common.primitives.{Ints, Longs, UnsignedBytes} import com.wavesplatform.account.{Address, KeyPair} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 @@ -11,117 +11,101 @@ import com.wavesplatform.lang.ValidationError import com.wavesplatform.state.TxMeta.Status import com.wavesplatform.state.diffs.BlockDiffer.{CurrentBlockFeePart, maybeApplySponsorship} import com.wavesplatform.state.diffs.TransactionDiffer -import com.wavesplatform.state.reader.SnapshotBlockchain import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.smart.script.trace.TracedResult import com.wavesplatform.transaction.{GenesisTransaction, Transaction} -import com.wavesplatform.utils.byteStrOrdering import org.bouncycastle.crypto.digests.Blake2bDigest import java.nio.charset.StandardCharsets import scala.collection.mutable object TxStateSnapshotHashBuilder { - object KeyType extends Enumeration { - val WavesBalance, AssetBalance, DataEntry, AccountScript, AssetScript, LeaseBalance, LeaseStatus, Sponsorship, Alias, VolumeAndFee, AssetStatic, - AssetVolume, AssetNameDescription, TransactionStatus = Value - } + private implicit val ByteArrayOrdering: Ordering[Array[Byte]] = (x, y) => UnsignedBytes.lexicographicalComparator().compare(x, y) val InitStateHash: ByteStr = ByteStr(crypto.fastHash("")) final case class Result(txStateSnapshotHash: ByteStr) { def createHash(prevHash: ByteStr): ByteStr = - TxStateSnapshotHashBuilder.createHash(Seq(prevHash, txStateSnapshotHash)) + TxStateSnapshotHashBuilder.createHash(Seq(prevHash.arr, txStateSnapshotHash.arr)) } case class TxStatusInfo(id: ByteStr, status: TxMeta.Status) def createHashFromSnapshot(snapshot: StateSnapshot, txStatusOpt: Option[TxStatusInfo]): Result = { - val changedKeys = mutable.Map.empty[ByteStr, Array[Byte]] - - def addEntry(keyType: KeyType.Value, key: Array[Byte]*)(value: Array[Byte]*): Unit = { - val solidKey = ByteStr(key.fold(Array(keyType.id.toByte))(_ ++ _)) - val solidValue = value.foldLeft(Array.emptyByteArray)(_ ++ _) - changedKeys(solidKey) = solidValue - } + val changedKeys = mutable.SortedSet.empty[Array[Byte]] snapshot.balances.foreach { case ((address, asset), balance) => asset match { - case Waves => addEntry(KeyType.WavesBalance, address.bytes)(Longs.toByteArray(balance)) - case asset: IssuedAsset => addEntry(KeyType.AssetBalance, address.bytes, asset.id.arr)(Longs.toByteArray(balance)) + case Waves => changedKeys += address.bytes ++ Longs.toByteArray(balance) + case asset: IssuedAsset => changedKeys += address.bytes ++ asset.id.arr ++ Longs.toByteArray(balance) } } snapshot.leaseBalances.foreach { case (address, balance) => - addEntry(KeyType.LeaseBalance, address.bytes)(Longs.toByteArray(balance.in), Longs.toByteArray(balance.out)) + changedKeys += address.bytes ++ Longs.toByteArray(balance.in) ++ Longs.toByteArray(balance.out) } for { (address, data) <- snapshot.accountData entry <- data.values - } addEntry(KeyType.DataEntry, address.bytes, entry.key.getBytes(StandardCharsets.UTF_8))(entry.valueBytes) + } changedKeys += address.bytes ++ entry.key.getBytes(StandardCharsets.UTF_8) ++ entry.valueBytes snapshot.aliases.foreach { case (alias, address) => - addEntry(KeyType.Alias, address.bytes, alias.name.getBytes(StandardCharsets.UTF_8))() + changedKeys += address.bytes ++ alias.name.getBytes(StandardCharsets.UTF_8) } - snapshot.accountScriptsByAddress.foreach { case (address, sv) => - addEntry(KeyType.AccountScript, address.bytes)( - sv.fold(Seq(Array.emptyByteArray))(s => Seq(s.script.bytes().arr, s.publicKey.arr, Longs.toByteArray(s.verifierComplexity)))* - ) + snapshot.accountScripts.foreach { case (pk, sv) => + changedKeys += pk.arr ++ (sv match { + case Some(s) => s.script.bytes().arr ++ Longs.toByteArray(s.verifierComplexity) + case None => Array.emptyByteArray + }) } for { (asset, scriptInfo) <- snapshot.assetScripts - } addEntry(KeyType.AssetScript, asset.id.arr)(scriptInfo.script.bytes().arr) + } changedKeys += asset.id.arr ++ scriptInfo.script.bytes().arr + + snapshot.newLeases.foreach { case (leaseId, details) => + changedKeys += leaseId.arr ++ booleanToBytes(true) + changedKeys += leaseId.arr ++ details.sender.arr ++ details.recipientAddress.bytes ++ Longs.toByteArray(details.amount.value) + } - snapshot.leaseStates.foreach { case (leaseId, details) => - addEntry(KeyType.LeaseStatus, leaseId.arr)(booleanToBytes(details.isActive)) + snapshot.cancelledLeases.keys.foreach { leaseId => + changedKeys += leaseId.arr ++ booleanToBytes(false) } snapshot.sponsorships.foreach { case (asset, sponsorship) => - addEntry(KeyType.Sponsorship, asset.id.arr)(Longs.toByteArray(sponsorship.minFee)) + changedKeys += asset.id.arr ++ Longs.toByteArray(sponsorship.minFee) } snapshot.orderFills.foreach { case (orderId, fillInfo) => - addEntry(KeyType.VolumeAndFee, orderId.arr)( - Longs.toByteArray(fillInfo.volume), - Longs.toByteArray(fillInfo.fee) - ) + changedKeys += orderId.arr ++ Longs.toByteArray(fillInfo.volume) ++ Longs.toByteArray(fillInfo.fee) } snapshot.assetStatics.foreach { case (asset, assetInfo) => - addEntry(KeyType.AssetStatic, asset.id.arr)( - assetInfo.issuerPublicKey.toByteArray, - Array(assetInfo.decimals.toByte), - booleanToBytes(assetInfo.nft) - ) + changedKeys += asset.id.arr ++ assetInfo.issuer.arr ++ Array(assetInfo.decimals.toByte) ++ booleanToBytes(assetInfo.nft) } snapshot.assetVolumes.foreach { case (asset, volume) => - addEntry(KeyType.AssetVolume, asset.id.arr)( - booleanToBytes(volume.isReissuable), - volume.volume.toByteArray - ) + changedKeys += asset.id.arr ++ booleanToBytes(volume.isReissuable) ++ volume.volume.toByteArray } snapshot.assetNamesAndDescriptions.foreach { case (asset, assetInfo) => - addEntry(KeyType.AssetNameDescription, asset.id.arr)( - assetInfo.name.toByteArray, - assetInfo.description.toByteArray, + changedKeys += asset.id.arr ++ + assetInfo.name.toByteArray ++ + assetInfo.description.toByteArray ++ Ints.toByteArray(assetInfo.lastUpdatedAt.toInt) - ) } txStatusOpt.foreach(txInfo => txInfo.status match { - case Status.Failed => addEntry(KeyType.TransactionStatus, txInfo.id.arr)(Array(1: Byte)) - case Status.Elided => addEntry(KeyType.TransactionStatus, txInfo.id.arr)(Array(2: Byte)) + case Status.Failed => changedKeys += txInfo.id.arr ++ Array(1: Byte) + case Status.Elided => changedKeys += txInfo.id.arr ++ Array(2: Byte) case Status.Succeeded => } ) - Result(createHash(changedKeys.toSeq.sortBy(_._1).flatMap { case (k, v) => Seq(k, ByteStr(v)) })) + Result(createHash(changedKeys)) } def createGenesisStateHash(txs: Seq[GenesisTransaction]): ByteStr = @@ -130,9 +114,7 @@ object TxStateSnapshotHashBuilder { .foldLeft(InitStateHash.arr -> Map.empty[Address, Long]) { case ((prevStateHash, balances), tx) => val newBalance = balances.getOrElse(tx.recipient, 0L) + tx.amount.value val tsh = - crypto.fastHash( - Array(TxStateSnapshotHashBuilder.KeyType.WavesBalance.id.toByte) ++ tx.recipient.bytes ++ Longs.toByteArray(newBalance) - ) + crypto.fastHash(tx.recipient.bytes ++ Longs.toByteArray(newBalance)) val newStateHash = crypto.fastHash(prevStateHash ++ tsh) newStateHash -> balances.updated(tx.recipient, newBalance) } @@ -192,12 +174,12 @@ object TxStateSnapshotHashBuilder { private def booleanToBytes(flag: Boolean): Array[Byte] = if (flag) Array(1: Byte) else Array(0: Byte) - private def createHash(bs: Iterable[ByteStr], digestFn: Blake2bDigest = newDigestInstance()): ByteStr = { - bs.foreach(bs => digestFn.update(bs.arr, 0, bs.arr.length)) + private def createHash(bs: Iterable[Array[Byte]]): ByteStr = { + val digestFn: Blake2bDigest = new Blake2bDigest(crypto.DigestLength * 8) + bs.foreach(bs => digestFn.update(bs, 0, bs.length)) val result = new Array[Byte](crypto.DigestLength) digestFn.doFinal(result, 0) ByteStr(result) } - private def newDigestInstance(): Blake2bDigest = new Blake2bDigest(crypto.DigestLength * 8) } diff --git a/node/src/main/scala/com/wavesplatform/state/appender/BlockAppender.scala b/node/src/main/scala/com/wavesplatform/state/appender/BlockAppender.scala index add355b2ca..5adacff6ff 100644 --- a/node/src/main/scala/com/wavesplatform/state/appender/BlockAppender.scala +++ b/node/src/main/scala/com/wavesplatform/state/appender/BlockAppender.scala @@ -1,9 +1,8 @@ package com.wavesplatform.state.appender -import java.time.Instant import cats.data.EitherT import cats.syntax.traverse.* -import com.wavesplatform.block.{Block, BlockSnapshot} +import com.wavesplatform.block.Block import com.wavesplatform.consensus.PoSSelector import com.wavesplatform.lang.ValidationError import com.wavesplatform.metrics.* @@ -23,6 +22,8 @@ import kamon.trace.Span import monix.eval.Task import monix.execution.Scheduler +import java.time.Instant + object BlockAppender extends ScorexLogging { def apply( blockchainUpdater: BlockchainUpdater & Blockchain, @@ -32,7 +33,7 @@ object BlockAppender extends ScorexLogging { scheduler: Scheduler, verify: Boolean = true, txSignParCheck: Boolean = true - )(newBlock: Block, snapshot: Option[BlockSnapshot]): Task[Either[ValidationError, BlockApplyResult]] = + )(newBlock: Block, snapshot: Option[BlockSnapshotResponse]): Task[Either[ValidationError, BlockApplyResult]] = Task { if ( blockchainUpdater @@ -58,7 +59,7 @@ object BlockAppender extends ScorexLogging { peerDatabase: PeerDatabase, blockChallenger: Option[BlockChallenger], scheduler: Scheduler - )(ch: Channel, newBlock: Block, snapshot: Option[BlockSnapshot]): Task[Unit] = { + )(ch: Channel, newBlock: Block, snapshot: Option[BlockSnapshotResponse]): Task[Unit] = { import metrics.* implicit val implicitTime: Time = time diff --git a/node/src/main/scala/com/wavesplatform/state/appender/MicroblockAppender.scala b/node/src/main/scala/com/wavesplatform/state/appender/MicroblockAppender.scala index ca8c9b7ffd..e5c4d4c377 100644 --- a/node/src/main/scala/com/wavesplatform/state/appender/MicroblockAppender.scala +++ b/node/src/main/scala/com/wavesplatform/state/appender/MicroblockAppender.scala @@ -9,6 +9,7 @@ import com.wavesplatform.metrics.* import com.wavesplatform.mining.BlockChallenger import com.wavesplatform.network.* import com.wavesplatform.network.MicroBlockSynchronizer.MicroblockData +import com.wavesplatform.protobuf.PBSnapshots import com.wavesplatform.state.Blockchain import com.wavesplatform.transaction.BlockchainUpdater import com.wavesplatform.transaction.TxValidationError.{InvalidSignature, InvalidStateHash} @@ -52,12 +53,20 @@ object MicroblockAppender extends ScorexLogging { peerDatabase: PeerDatabase, blockChallenger: Option[BlockChallenger], scheduler: Scheduler - )(ch: Channel, md: MicroblockData, snapshot: Option[(Channel, MicroBlockSnapshot)]): Task[Unit] = { + )(ch: Channel, md: MicroblockData, snapshot: Option[(Channel, MicroBlockSnapshotResponse)]): Task[Unit] = { import md.microBlock val microblockTotalResBlockSig = microBlock.totalResBlockSig (for { - _ <- EitherT(Task.now(microBlock.signaturesValid())) - blockId <- EitherT(apply(blockchainUpdater, utxStorage, scheduler)(microBlock, snapshot.map(_._2))) + _ <- EitherT(Task.now(microBlock.signaturesValid())) + microBlockSnapshot = snapshot + .map { case (_, mbs) => + microBlock.transactionData.zip(mbs.snapshots).map { case (tx, pbs) => + PBSnapshots.fromProtobuf(pbs, tx.id(), blockchainUpdater.height) + } + } + .map(ss => MicroBlockSnapshot(microblockTotalResBlockSig, ss)) + + blockId <- EitherT(apply(blockchainUpdater, utxStorage, scheduler)(microBlock, microBlockSnapshot)) } yield blockId).value.flatMap { case Right(blockId) => Task { diff --git a/node/src/main/scala/com/wavesplatform/state/appender/package.scala b/node/src/main/scala/com/wavesplatform/state/appender/package.scala index 9d61ba9312..cc9339a639 100644 --- a/node/src/main/scala/com/wavesplatform/state/appender/package.scala +++ b/node/src/main/scala/com/wavesplatform/state/appender/package.scala @@ -1,14 +1,16 @@ package com.wavesplatform.state import cats.syntax.either.* -import com.wavesplatform.block.{Block, BlockSnapshot} import com.wavesplatform.block.Block.BlockId +import com.wavesplatform.block.{Block, BlockSnapshot} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.consensus.PoSSelector import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.lang.ValidationError import com.wavesplatform.metrics.* import com.wavesplatform.mining.Miner +import com.wavesplatform.network.BlockSnapshotResponse +import com.wavesplatform.protobuf.PBSnapshots import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult.Applied import com.wavesplatform.transaction.* @@ -27,6 +29,12 @@ package object appender { 813207 -> ByteStr.decodeBase58("5uZoDnRKeWZV9Thu2nvJVZ5dBvPB7k2gvpzFD618FMXCbBVBMN2rRyvKBZBhAGnGdgeh2LXEeSr9bJqruJxngsE7").get ) + private def responseToSnapshot(block: Block, height: Int)(s: BlockSnapshotResponse): BlockSnapshot = + BlockSnapshot( + block.id(), + block.transactionData.zip(s.snapshots).map { case (tx, pbs) => PBSnapshots.fromProtobuf(pbs, tx.id(), height) } + ) + private[appender] def appendKeyBlock( blockchainUpdater: BlockchainUpdater & Blockchain, utx: UtxPool, @@ -35,12 +43,15 @@ package object appender { log: LoggerFacade, verify: Boolean, txSignParCheck: Boolean - )(block: Block, snapshot: Option[BlockSnapshot]): Either[ValidationError, BlockApplyResult] = + )(block: Block, snapshot: Option[BlockSnapshotResponse]): Either[ValidationError, BlockApplyResult] = for { hitSource <- if (verify) validateBlock(blockchainUpdater, pos, time)(block) else pos.validateGenerationSignature(block) newHeight <- metrics.appendBlock - .measureSuccessful(blockchainUpdater.processBlock(block, hitSource, snapshot, None, verify, txSignParCheck)) + .measureSuccessful( + blockchainUpdater + .processBlock(block, hitSource, snapshot.map(responseToSnapshot(block, blockchainUpdater.height + 1)), None, verify, txSignParCheck) + ) .map { case res @ Applied(discardedDiffs, _) => // TODO: move UTX cleanup from appender @@ -63,13 +74,22 @@ package object appender { time: Time, verify: Boolean, txSignParCheck: Boolean - )(block: Block, snapshot: Option[BlockSnapshot]): Either[ValidationError, (BlockApplyResult, Int)] = { + )(block: Block, snapshot: Option[BlockSnapshotResponse]): Either[ValidationError, (BlockApplyResult, Int)] = { if (block.header.challengedHeader.nonEmpty) { processBlockWithChallenge(blockchainUpdater, pos, time, verify, txSignParCheck)(block, snapshot) } else { for { - hitSource <- if (verify) validateBlock(blockchainUpdater, pos, time)(block) else pos.validateGenerationSignature(block) - applyResult <- metrics.appendBlock.measureSuccessful(blockchainUpdater.processBlock(block, hitSource, snapshot, None, verify, txSignParCheck)) + hitSource <- if (verify) validateBlock(blockchainUpdater, pos, time)(block) else pos.validateGenerationSignature(block) + applyResult <- metrics.appendBlock.measureSuccessful( + blockchainUpdater.processBlock( + block, + hitSource, + snapshot.map(responseToSnapshot(block, blockchainUpdater.height + 1)), + None, + verify, + txSignParCheck + ) + ) } yield applyResult -> blockchainUpdater.height } } @@ -82,7 +102,7 @@ package object appender { log: LoggerFacade, verify: Boolean, txSignParCheck: Boolean - )(block: Block, snapshot: Option[BlockSnapshot]): Either[ValidationError, BlockApplyResult] = + )(block: Block, snapshot: Option[BlockSnapshotResponse]): Either[ValidationError, BlockApplyResult] = processBlockWithChallenge(blockchainUpdater, pos, time, verify, txSignParCheck)(block, snapshot).map { case (res @ Applied(discardedDiffs, _), _) => if (block.transactionData.nonEmpty) { @@ -103,7 +123,7 @@ package object appender { time: Time, verify: Boolean, txSignParCheck: Boolean - )(block: Block, snapshot: Option[BlockSnapshot]): Either[ValidationError, (BlockApplyResult, Int)] = { + )(block: Block, snapshot: Option[BlockSnapshotResponse]): Either[ValidationError, (BlockApplyResult, Int)] = { val challengedBlock = block.toOriginal for { challengedHitSource <- @@ -111,7 +131,16 @@ package object appender { hitSource <- if (verify) validateBlock(blockchainUpdater, pos, time)(block) else pos.validateGenerationSignature(block) applyResult <- metrics.appendBlock - .measureSuccessful(blockchainUpdater.processBlock(block, hitSource, snapshot, Some(challengedHitSource), verify, txSignParCheck)) + .measureSuccessful( + blockchainUpdater.processBlock( + block, + hitSource, + snapshot.map(responseToSnapshot(block, blockchainUpdater.height + 1)), + Some(challengedHitSource), + verify, + txSignParCheck + ) + ) } yield applyResult -> blockchainUpdater.height } @@ -146,7 +175,7 @@ package object appender { grandParent = blockchain.parentHeader(parent, 2) effectiveBalance <- genBalance(height, block.header.reference).left.map(GenericError(_)) _ <- validateBlockVersion(height, block, blockchain) - _ <- Either.cond(blockTime - currentTs < MaxTimeDrift, (), BlockFromFuture(blockTime)) + _ <- Either.cond(blockTime - currentTs < MaxTimeDrift, (), BlockFromFuture(blockTime, currentTs)) _ <- pos.validateBaseTarget(height, block, parent, grandParent) hitSource <- pos.validateGenerationSignature(block) _ <- pos diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/BlockDiffer.scala b/node/src/main/scala/com/wavesplatform/state/diffs/BlockDiffer.scala index 310a6daa2f..c4402a8966 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/BlockDiffer.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/BlockDiffer.scala @@ -12,7 +12,7 @@ import com.wavesplatform.state.* import com.wavesplatform.state.StateSnapshot.monoid import com.wavesplatform.state.TxStateSnapshotHashBuilder.TxStatusInfo import com.wavesplatform.state.patch.* -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.* import com.wavesplatform.transaction.assets.exchange.ExchangeTransaction diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala b/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala index d4d58d7adb..59412cd2a6 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala @@ -13,7 +13,7 @@ import com.wavesplatform.lang.script.v1.ExprScript import com.wavesplatform.lang.script.{ContractScript, Script} import com.wavesplatform.settings.FunctionalitySettings import com.wavesplatform.state.* -import com.wavesplatform.transaction.* +import com.wavesplatform.state.diffs.invoke.InvokeDiffsCommon import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.* import com.wavesplatform.transaction.assets.* @@ -22,6 +22,7 @@ import com.wavesplatform.transaction.lease.* import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.smart.{InvokeExpressionTransaction, InvokeScriptTransaction, SetScriptTransaction} import com.wavesplatform.transaction.transfer.* +import com.wavesplatform.transaction.{Asset, *} import scala.util.{Left, Right} @@ -36,21 +37,25 @@ object CommonValidation { feeAmount: Long, allowFeeOverdraft: Boolean = false ): Either[ValidationError, T] = { - val amountDiff = assetId match { + val amountPortfolio = assetId match { case aid @ IssuedAsset(_) => Portfolio.build(aid -> -amount) case Waves => Portfolio(-amount) } - val feeDiff = feeAssetId match { + val feePortfolio = feeAssetId match { case aid @ IssuedAsset(_) => Portfolio.build(aid -> -feeAmount) case Waves => Portfolio(-feeAmount) } val checkedTx = for { - spendings <- amountDiff.combine(feeDiff) + _ <- assetId match { + case IssuedAsset(id) => InvokeDiffsCommon.checkAsset(blockchain, id) + case Waves => Right(()) + } + spendings <- amountPortfolio.combine(feePortfolio) oldWavesBalance = blockchain.balance(sender, Waves) newWavesBalance <- safeSum(oldWavesBalance, spendings.balance, "Spendings") - feeUncheckedBalance <- safeSum(oldWavesBalance, amountDiff.balance, "Transfer amount") + feeUncheckedBalance <- safeSum(oldWavesBalance, amountPortfolio.balance, "Transfer amount") overdraftFilter = allowFeeOverdraft && feeUncheckedBalance >= 0 _ <- Either.cond( @@ -95,6 +100,7 @@ object CommonValidation { for { address <- blockchain.resolveAlias(citx.dApp) + _ <- InvokeDiffsCommon.checkPayments(blockchain, citx.payments) allowFeeOverdraft = blockchain.accountScript(address) match { case Some(AccountScriptInfo(_, ContractScriptImpl(version, _), _, _)) if version >= V4 && blockchain.useCorrectPaymentCheck => true case _ => false @@ -165,20 +171,23 @@ object CommonValidation { } - def generic1or2Barrier(t: VersionedTransaction): Either[ActivationError, T] = { + def generic1or2Barrier(t: Versioned): Either[ActivationError, T] = { if (t.version == 1.toByte) Right(tx) else if (t.version == 2.toByte) activationBarrier(BlockchainFeatures.SmartAccounts) else Right(tx) } + def versionIsCorrect(tx: Versioned): Boolean = + tx.version > 0 && tx.version <= Versioned.maxVersion(tx) + val versionsBarrier = tx match { - case v: VersionedTransaction if !TransactionParsers.versionIsCorrect(v) && blockchain.isFeatureActivated(LightNode) => + case v: Versioned if !versionIsCorrect(v) && blockchain.isFeatureActivated(LightNode) => Left(UnsupportedTypeAndVersion(v.tpe.id.toByte, v.version)) - case p: PBSince if p.isProtobufVersion => + case p: PBSince with Versioned if PBSince.affects(p) => activationBarrier(BlockchainFeatures.BlockV5) - case v: VersionedTransaction if !TransactionParsers.versionIsCorrect(v) => + case v: Versioned if !versionIsCorrect(v) => Left(UnsupportedTypeAndVersion(v.tpe.id.toByte, v.version)) case _ => diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/DiffsCommon.scala b/node/src/main/scala/com/wavesplatform/state/diffs/DiffsCommon.scala index bc5ccd8c90..fba24bac95 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/DiffsCommon.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/DiffsCommon.scala @@ -15,9 +15,9 @@ import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 import com.wavesplatform.lang.v1.estimator.{ScriptEstimator, ScriptEstimatorV1} import com.wavesplatform.lang.v1.traits.domain.* -import com.wavesplatform.state.reader.LeaseDetails -import com.wavesplatform.state.{AssetVolumeInfo, Blockchain, LeaseBalance, Portfolio, SponsorshipValue, StateSnapshot} +import com.wavesplatform.state.{AssetVolumeInfo, Blockchain, LeaseBalance, LeaseDetails, LeaseStaticInfo, Portfolio, SponsorshipValue, StateSnapshot} import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} +import com.wavesplatform.transaction.TxPositiveAmount import com.wavesplatform.transaction.TxValidationError.GenericError object DiffsCommon { @@ -134,7 +134,7 @@ object DiffsCommon { def processLease( blockchain: Blockchain, - amount: Long, + amount: TxPositiveAmount, sender: PublicKey, recipient: AddressOrAlias, fee: Long, @@ -156,21 +156,20 @@ object DiffsCommon { ) leaseBalance = blockchain.leaseBalance(senderAddress) senderBalance = blockchain.balance(senderAddress, Waves) - requiredBalance = if (blockchain.isFeatureActivated(BlockchainFeatures.SynchronousCalls)) amount + fee else amount + requiredBalance = if (blockchain.isFeatureActivated(BlockchainFeatures.SynchronousCalls)) amount.value + fee else amount.value _ <- Either.cond( senderBalance - leaseBalance.out >= requiredBalance, (), GenericError(s"Cannot lease more than own: Balance: $senderBalance, already leased: ${leaseBalance.out}") ) portfolioDiff = Map( - senderAddress -> Portfolio(-fee, LeaseBalance(0, amount)), - recipientAddress -> Portfolio(0, LeaseBalance(amount, 0)) + senderAddress -> Portfolio(-fee, LeaseBalance(0, amount.value)), + recipientAddress -> Portfolio(0, LeaseBalance(amount.value, 0)) ) - details = LeaseDetails(sender, recipient, amount, LeaseDetails.Status.Active, txId, blockchain.height) snapshot <- StateSnapshot.build( blockchain, portfolios = portfolioDiff, - leaseStates = Map(leaseId -> details) + newLeases = Map(leaseId -> LeaseStaticInfo(sender, recipientAddress, amount, txId, blockchain.height)) ) } yield snapshot } @@ -185,8 +184,7 @@ object DiffsCommon { ): Either[ValidationError, StateSnapshot] = { val allowedTs = blockchain.settings.functionalitySettings.allowMultipleLeaseCancelTransactionUntilTimestamp for { - lease <- blockchain.leaseDetails(leaseId).toRight(GenericError(s"Lease with id=$leaseId not found")) - recipient <- blockchain.resolveAlias(lease.recipient) + lease <- blockchain.leaseDetails(leaseId).toRight(GenericError(s"Lease with id=$leaseId not found")) _ <- Either.cond( lease.isActive || time <= allowedTs, (), @@ -200,14 +198,13 @@ object DiffsCommon { s"time=$time > allowMultipleLeaseCancelTransactionUntilTimestamp=$allowedTs" ) ) - senderPortfolio = Map[Address, Portfolio](sender.toAddress -> Portfolio(-fee, LeaseBalance(0, -lease.amount))) - recipientPortfolio = Map(recipient -> Portfolio(0, LeaseBalance(-lease.amount, 0))) - actionInfo = lease.copy(status = LeaseDetails.Status.Cancelled(blockchain.height, Some(cancelTxId))) + senderPortfolio = Map[Address, Portfolio](sender.toAddress -> Portfolio(-fee, LeaseBalance(0, -lease.amount.value))) + recipientPortfolio = Map(lease.recipientAddress -> Portfolio(0, LeaseBalance(-lease.amount.value, 0))) portfolios <- Portfolio.combine(senderPortfolio, recipientPortfolio).leftMap(GenericError(_)) snapshot <- StateSnapshot.build( blockchain, portfolios = portfolios, - leaseStates = Map(leaseId -> actionInfo) + cancelledLeases = Map(leaseId -> LeaseDetails.Status.Cancelled(blockchain.height, Some(cancelTxId))) ) } yield snapshot } 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 382081b76d..2d3f5b39cb 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/EthereumTransactionDiff.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/EthereumTransactionDiff.scala @@ -8,7 +8,7 @@ import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.v1.serialization.SerdeV1 import com.wavesplatform.protobuf.transaction.{PBAmounts, PBRecipients} -import com.wavesplatform.state.diffs.invoke.InvokeScriptTransactionDiff +import com.wavesplatform.state.diffs.invoke.{InvokeDiffsCommon, InvokeScriptTransactionDiff} import com.wavesplatform.state.{Blockchain, StateSnapshot} import com.wavesplatform.transaction.EthereumTransaction import com.wavesplatform.transaction.TxValidationError.GenericError @@ -78,6 +78,7 @@ object EthereumTransactionDiff { for { _ <- checkLeadingZeros(tx, blockchain) invocation <- TracedResult(ei.toInvokeScriptLike(tx, blockchain)) + _ <- TracedResult(InvokeDiffsCommon.checkPayments(blockchain, invocation.payments)) snapshot <- InvokeScriptTransactionDiff(blockchain, currentBlockTs, limitedExecution, enableExecutionLog)(invocation) resultSnapshot <- TransactionDiffer.assetsVerifierDiff( blockchain, diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/FeeValidation.scala b/node/src/main/scala/com/wavesplatform/state/diffs/FeeValidation.scala index 95fcbaea82..3c52774e02 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/FeeValidation.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/FeeValidation.scala @@ -82,7 +82,7 @@ object FeeValidation { case tx: DataTransaction => val payloadLength = if (blockchain.isFeatureActivated(BlockchainFeatures.RideV6)) DataTxValidator.realUserPayloadSize(tx.data) - else if (tx.isProtobufVersion) tx.protoDataPayload.length + else if (PBSince.affects(tx)) tx.protoDataPayload.length else if (blockchain.isFeatureActivated(BlockchainFeatures.SmartAccounts)) tx.bodyBytes().length else tx.bytes().length 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 cedecbd0a5..f6ad69f6fa 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/LeaseTransactionsDiff.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/LeaseTransactionsDiff.scala @@ -7,7 +7,7 @@ import com.wavesplatform.transaction.lease.* object LeaseTransactionsDiff { def lease(blockchain: Blockchain)(tx: LeaseTransaction): Either[ValidationError, StateSnapshot] = DiffsCommon - .processLease(blockchain, tx.amount.value, tx.sender, tx.recipient, tx.fee.value, tx.id(), tx.id()) + .processLease(blockchain, tx.amount, tx.sender, tx.recipient, tx.fee.value, tx.id(), tx.id()) def leaseCancel(blockchain: Blockchain, time: Long)(tx: LeaseCancelTransaction): Either[ValidationError, StateSnapshot] = DiffsCommon 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 e154bb12c4..f8c2e1b477 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 @@ -8,7 +8,7 @@ import com.wavesplatform.account.{Address, AddressOrAlias, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.features.BlockchainFeatures.{BlockRewardDistribution, BlockV5, RideV6, SynchronousCalls} +import com.wavesplatform.features.BlockchainFeatures.* import com.wavesplatform.features.EstimatorProvider.* import com.wavesplatform.features.InvokeScriptSelfPaymentPolicyProvider.* import com.wavesplatform.features.ScriptTransferValidationProvider.* @@ -24,12 +24,13 @@ import com.wavesplatform.lang.v1.traits.domain.Tx.{BurnPseudoTx, ReissuePseudoTx import com.wavesplatform.state.* import com.wavesplatform.state.diffs.FeeValidation.* import com.wavesplatform.state.diffs.{BalanceDiffValidation, DiffsCommon} -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.* import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.transaction.smart.* import com.wavesplatform.transaction.smart.DAppEnvironment.ActionLimits +import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.smart.script.ScriptRunner import com.wavesplatform.transaction.smart.script.ScriptRunner.TxOrd import com.wavesplatform.transaction.smart.script.trace.AssetVerifierTrace.AssetContext @@ -345,6 +346,15 @@ object InvokeDiffsCommon { ) } + def checkPayments(blockchain: Blockchain, payments: Seq[Payment]): Either[GenericError, Unit] = + payments + .collectFirstSome { + case Payment(_, IssuedAsset(id)) => InvokeDiffsCommon.checkAsset(blockchain, id).swap.toOption + case Payment(_, Waves) => None + } + .map(GenericError(_)) + .toLeft(()) + def checkAsset(blockchain: Blockchain, assetId: ByteStr): Either[String, Unit] = if (blockchain.isFeatureActivated(BlockchainFeatures.SynchronousCalls)) if (assetId.size != AssetIdLength) @@ -367,7 +377,7 @@ object InvokeDiffsCommon { tx.enableEmptyKeys || dataEntries.forall(_.key.nonEmpty), (), { val versionInfo = tx.root match { - case s: PBSince => s" in tx version >= ${s.protobufVersion}" + case s: PBSince => s" in tx version >= ${PBSince.version(s)}" case _ => "" } s"Empty keys aren't allowed$versionInfo" @@ -464,8 +474,8 @@ object InvokeDiffsCommon { if (remainingLimit < Int.MaxValue) remainingLimit - currentSnapshot.scriptsComplexity.toInt else remainingLimit - val blockchain = SnapshotBlockchain(sblockchain, currentSnapshot) - val actionSender = Recipient.Address(ByteStr(dAppAddress.bytes)) + val blockchain = SnapshotBlockchain(sblockchain, currentSnapshot) + val actionSender = Recipient.Address(ByteStr(dAppAddress.bytes)) def applyTransfer(transfer: AssetTransfer, pk: PublicKey): TracedResult[ValidationError, StateSnapshot] = { val AssetTransfer(addressRepr, recipient, amount, asset) = transfer @@ -504,17 +514,17 @@ object InvokeDiffsCommon { e ) ) - ).flatMap(nextDiff => + ).flatMap(portfolioSnapshot => blockchain .assetScript(a) .fold { val r = checkAsset(blockchain, id) - .map(_ => nextDiff) + .map(_ => portfolioSnapshot) .leftMap(FailedTransactionError.dAppExecution(_, 0): ValidationError) TracedResult(r) } { case AssetScriptInfo(script, complexity) => val assetVerifierSnapshot = - if (blockchain.disallowSelfPayment) nextDiff + if (blockchain.disallowSelfPayment) portfolioSnapshot else StateSnapshot .build( @@ -539,9 +549,11 @@ object InvokeDiffsCommon { tx.timestamp, tx.txId ) - val assetValidationDiff = for { - _ <- BalanceDiffValidation.cond(blockchain, _.isFeatureActivated(BlockchainFeatures.RideV6))(assetVerifierSnapshot) - assetValidationDiff <- validatePseudoTxWithSmartAssetScript(blockchain, tx)( + val assetValidationSnapshot = for { + _ <- BalanceDiffValidation + .cond(blockchain, _.isFeatureActivated(RideV6))(assetVerifierSnapshot) + .leftMap(e => if (blockchain.isFeatureActivated(LightNode)) FailedTransactionError.asFailedScriptError(e) else e) + snapshot <- validatePseudoTxWithSmartAssetScript(blockchain, tx)( pseudoTx, a.id, assetVerifierSnapshot, @@ -550,10 +562,10 @@ object InvokeDiffsCommon { complexityLimit, enableExecutionLog ) - } yield assetValidationDiff - val errorOpt = assetValidationDiff.fold(Some(_), _ => None) + } yield snapshot + val errorOpt = assetValidationSnapshot.fold(Some(_), _ => None) TracedResult( - assetValidationDiff.map(d => nextDiff.setScriptsComplexity(d.scriptsComplexity)), + assetValidationSnapshot.map(d => portfolioSnapshot.setScriptsComplexity(d.scriptsComplexity)), List(AssetVerifierTrace(id, errorOpt, AssetContext.Transfer)) ) } @@ -628,10 +640,10 @@ object InvokeDiffsCommon { def applyLease(l: Lease): TracedResult[ValidationError, StateSnapshot] = for { - _ <- TracedResult(LeaseTxValidator.validateAmount(l.amount)) - recipient <- TracedResult(AddressOrAlias.fromRide(l.recipient)) + validAmount <- TracedResult(LeaseTxValidator.validateAmount(l.amount)) + recipient <- TracedResult(AddressOrAlias.fromRide(l.recipient)) leaseId = Lease.calculateId(l, tx.txId) - diff <- DiffsCommon.processLease(blockchain, l.amount, pk, recipient, fee = 0, leaseId, tx.txId) + diff <- DiffsCommon.processLease(blockchain, validAmount, pk, recipient, fee = 0, leaseId, tx.txId) } yield diff def applyLeaseCancel(l: LeaseCancel): TracedResult[ValidationError, StateSnapshot] = 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 f9f0a9c3aa..427ae759f8 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 @@ -28,7 +28,7 @@ import com.wavesplatform.metrics.* import com.wavesplatform.state.* import com.wavesplatform.state.diffs.BalanceDiffValidation import com.wavesplatform.state.diffs.invoke.CallArgumentPolicy.* -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.* import com.wavesplatform.transaction.smart.DAppEnvironment.ActionLimits @@ -400,7 +400,8 @@ object InvokeScriptDiff { limit, blockchain.correctFunctionCallScope, blockchain.newEvaluatorMode, - enableExecutionLog + enableExecutionLog, + blockchain.isFeatureActivated(LightNode) ) .map( _.leftMap[ValidationError] { 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 5673c35cdb..70ccce8b8a 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 @@ -8,7 +8,7 @@ import com.wavesplatform.account.* import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.features.BlockchainFeatures.RideV6 +import com.wavesplatform.features.BlockchainFeatures.{LightNode, RideV6} import com.wavesplatform.features.EstimatorProvider.* import com.wavesplatform.features.EvaluatorFixProvider.* import com.wavesplatform.features.FunctionCallPolicyProvider.* @@ -31,7 +31,7 @@ import com.wavesplatform.metrics.TxProcessingStats.TxTimerExt import com.wavesplatform.protobuf.dapp.DAppMeta import com.wavesplatform.state.* import com.wavesplatform.state.diffs.invoke.CallArgumentPolicy.* -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.transaction.TransactionBase import com.wavesplatform.transaction.TxValidationError.* import com.wavesplatform.transaction.smart.DAppEnvironment.ActionLimits @@ -334,7 +334,8 @@ object InvokeScriptTransactionDiff { startLimit, blockchain.correctFunctionCallScope, blockchain.newEvaluatorMode, - enableExecutionLog + enableExecutionLog, + blockchain.isFeatureActivated(LightNode) ) .runAttempt() .leftMap(error => (error.getMessage: ExecutionError, 0, Nil: Log[Id])) diff --git a/node/src/main/scala/com/wavesplatform/state/patch/CancelAllLeases.scala b/node/src/main/scala/com/wavesplatform/state/patch/CancelAllLeases.scala index 7a7462691e..80b4302592 100644 --- a/node/src/main/scala/com/wavesplatform/state/patch/CancelAllLeases.scala +++ b/node/src/main/scala/com/wavesplatform/state/patch/CancelAllLeases.scala @@ -1,11 +1,10 @@ package com.wavesplatform.state.patch import cats.implicits.catsSyntaxSemigroup -import com.wavesplatform.account.{Address, PublicKey} +import com.wavesplatform.account.Address import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.* -import com.wavesplatform.state.reader.LeaseDetails -import com.wavesplatform.state.{Blockchain, LeaseBalance, StateSnapshot} +import com.wavesplatform.state.{Blockchain, LeaseBalance, LeaseDetails, StateSnapshot} import play.api.libs.json.{Json, OFormat} case object CancelAllLeases extends PatchAtHeight('W' -> 462000, 'T' -> 51500) { @@ -13,11 +12,8 @@ case object CancelAllLeases extends PatchAtHeight('W' -> 462000, 'T' -> 51500) { private[patch] case class CancelledLeases(balances: Map[Address, LeaseBalance], cancelledLeases: Seq[LeaseData]) { private[this] val height: Int = patchHeight.getOrElse(0) - val leaseStates: Map[ByteStr, LeaseDetails] = cancelledLeases.map { data => - val sender = PublicKey(ByteStr.decodeBase58(data.senderPublicKey).get) - val recipient = Address.fromString(data.recipient).explicitGet() - val id = ByteStr.decodeBase58(data.id).get - (id, LeaseDetails(sender, recipient, data.amount, status = LeaseDetails.Status.Expired(height), id, height)) + val leaseStates: Map[ByteStr, LeaseDetails.Status.Inactive] = cancelledLeases.map { data => + (ByteStr.decodeBase58(data.id).get, LeaseDetails.Status.Expired(height)) }.toMap } @@ -28,6 +24,6 @@ case object CancelAllLeases extends PatchAtHeight('W' -> 462000, 'T' -> 51500) { def apply(blockchain: Blockchain): StateSnapshot = { val patch = readPatchData[CancelledLeases]() - StateSnapshot.ofLeaseBalances(patch.balances, blockchain).explicitGet() |+| StateSnapshot(leaseStates = patch.leaseStates) + StateSnapshot.ofLeaseBalances(patch.balances, blockchain).explicitGet() |+| StateSnapshot(cancelledLeases = patch.leaseStates) } } diff --git a/node/src/main/scala/com/wavesplatform/state/patch/CancelLeaseOverflow.scala b/node/src/main/scala/com/wavesplatform/state/patch/CancelLeaseOverflow.scala index eeb2db63c5..f4727c101c 100644 --- a/node/src/main/scala/com/wavesplatform/state/patch/CancelLeaseOverflow.scala +++ b/node/src/main/scala/com/wavesplatform/state/patch/CancelLeaseOverflow.scala @@ -8,6 +8,6 @@ import com.wavesplatform.state.{Blockchain, StateSnapshot} case object CancelLeaseOverflow extends PatchAtHeight('W' -> 795000) { def apply(blockchain: Blockchain): StateSnapshot = { val patch = readPatchData[CancelledLeases]() - StateSnapshot.ofLeaseBalances(patch.balances, blockchain).explicitGet() |+| StateSnapshot(leaseStates = patch.leaseStates) + StateSnapshot.ofLeaseBalances(patch.balances, blockchain).explicitGet() |+| StateSnapshot(cancelledLeases = patch.leaseStates) } } diff --git a/node/src/main/scala/com/wavesplatform/state/patch/CancelLeasesToDisabledAliases.scala b/node/src/main/scala/com/wavesplatform/state/patch/CancelLeasesToDisabledAliases.scala index 1dc9349c6e..b86dabff15 100644 --- a/node/src/main/scala/com/wavesplatform/state/patch/CancelLeasesToDisabledAliases.scala +++ b/node/src/main/scala/com/wavesplatform/state/patch/CancelLeasesToDisabledAliases.scala @@ -1,12 +1,11 @@ package com.wavesplatform.state.patch -import cats.implicits.{catsSyntaxAlternativeSeparate, catsSyntaxSemigroup, toFoldableOps} -import com.wavesplatform.account.{Address, Alias, PublicKey} +import cats.implicits.{catsSyntaxSemigroup, toFoldableOps} +import com.wavesplatform.account.{Address, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, EitherExt2} import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.state.reader.LeaseDetails -import com.wavesplatform.state.{Blockchain, LeaseBalance, Portfolio, StateSnapshot} +import com.wavesplatform.state.{Blockchain, LeaseBalance, LeaseDetails, Portfolio, StateSnapshot} import play.api.libs.json.{Json, Reads} case object CancelLeasesToDisabledAliases extends PatchOnFeature(BlockchainFeatures.SynchronousCalls, Set('W')) { @@ -19,40 +18,33 @@ case object CancelLeasesToDisabledAliases extends PatchOnFeature(BlockchainFeatu height: Int ) - def patchData: Map[ByteStr, (LeaseDetails, Address)] = { + def patchData: Map[ByteStr, (Map[Address, Portfolio], Address)] = { implicit val cancelDetailsReads: Reads[CancelDetails] = Json.reads readPatchData[Seq[CancelDetails]]().map { cancelDetails => val leaseId = ByteStr(Base58.decode(cancelDetails.id)) val sender = PublicKey(Base58.decode(cancelDetails.senderPublicKey)) - val recipientAlias = Alias.fromString(cancelDetails.recipientAlias).explicitGet() val recipientAddress = Address.fromString(cancelDetails.recipientAddress).explicitGet() - leaseId -> (LeaseDetails( - sender, - recipientAlias, - cancelDetails.amount, - LeaseDetails.Status.Expired(0), - leaseId, - cancelDetails.height - ) -> recipientAddress) + leaseId -> (Portfolio + .combine( + Map(sender.toAddress -> Portfolio(lease = LeaseBalance(0, -cancelDetails.amount))), + Map(recipientAddress -> Portfolio(lease = LeaseBalance(-cancelDetails.amount, 0))) + ) + .explicitGet(), + recipientAddress) }.toMap } override def apply(blockchain: Blockchain): StateSnapshot = { val (leaseBalances, leaseStates) = - patchData.toSeq.map { case (id, (ld, recipientAddress)) => + patchData.toSeq.map { case (id, (pf, _)) => ( - Portfolio - .combine( - Map(ld.sender.toAddress -> Portfolio(lease = LeaseBalance(0, -ld.amount))), - Map(recipientAddress -> Portfolio(lease = LeaseBalance(-ld.amount, 0))) - ) - .explicitGet(), + pf, StateSnapshot( - leaseStates = Map(id -> ld.copy(status = LeaseDetails.Status.Expired(blockchain.height))) + cancelledLeases = Map(id -> LeaseDetails.Status.Expired(blockchain.height)) ) ) - }.separate + }.unzip val combinedLeaseBalances = leaseBalances.reduce(Portfolio.combine(_, _).explicitGet()) val leaseBalancesSnapshot = StateSnapshot.ofLeaseBalances(combinedLeaseBalances.view.mapValues(_.lease).toMap, blockchain) leaseBalancesSnapshot.explicitGet() |+| leaseStates.combineAll diff --git a/node/src/main/scala/com/wavesplatform/transaction/CreateAliasTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/CreateAliasTransaction.scala index 55f63e4bca..1a9787e9ca 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/CreateAliasTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/CreateAliasTransaction.scala @@ -24,7 +24,7 @@ final case class CreateAliasTransaction( chainId: Byte ) extends Transaction(TransactionType.CreateAlias) with SigProofsSwitch - with VersionedTransaction + with Versioned.ToV3 with TxWithFee.InWaves with PBSince.V3 { @@ -45,8 +45,7 @@ final case class CreateAliasTransaction( object CreateAliasTransaction extends TransactionParser { type TransactionT = CreateAliasTransaction - val supportedVersions: Set[TxVersion] = Set(1, 2, 3) - val typeId: TxType = 10: Byte + val typeId: TxType = 10: Byte implicit val validator: TxValidator[CreateAliasTransaction] = CreateAliasTxValidator diff --git a/node/src/main/scala/com/wavesplatform/transaction/DataTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/DataTransaction.scala index 79ae84d100..eb041144a2 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/DataTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/DataTransaction.scala @@ -1,17 +1,17 @@ package com.wavesplatform.transaction -import scala.util.Try - import com.wavesplatform.account.{AddressScheme, KeyPair, PrivateKey, PublicKey} import com.wavesplatform.crypto import com.wavesplatform.lang.ValidationError import com.wavesplatform.protobuf.transaction.PBTransactions -import com.wavesplatform.state._ +import com.wavesplatform.state.* import com.wavesplatform.transaction.serialization.impl.DataTxSerializer import com.wavesplatform.transaction.validation.TxValidator import com.wavesplatform.transaction.validation.impl.DataTxValidator import monix.eval.Coeval -import play.api.libs.json._ +import play.api.libs.json.* + +import scala.util.Try case class DataTransaction( version: TxVersion, @@ -23,7 +23,7 @@ case class DataTransaction( chainId: Byte ) extends Transaction(TransactionType.Data) with ProvenTransaction - with VersionedTransaction + with Versioned.ToV2 with TxWithFee.InWaves with FastHashId with PBSince.V2 { @@ -38,13 +38,12 @@ case class DataTransaction( object DataTransaction extends TransactionParser { type TransactionT = DataTransaction - val MaxBytes: Int = 150 * 1024 // uses for RIDE CONST_STRING and CONST_BYTESTR - val MaxProtoBytes: Int = 165890 // uses for RIDE CONST_BYTESTR - val MaxRideV6Bytes: Int = 165835 // (DataEntry.MaxPBKeySize + DataEntry.MaxValueSize) * 5 - val MaxEntryCount: Int = 100 + val MaxBytes: Int = 150 * 1024 // uses for RIDE CONST_STRING and CONST_BYTESTR + val MaxProtoBytes: Int = 165890 // uses for RIDE CONST_BYTESTR + val MaxRideV6Bytes: Int = 165835 // (DataEntry.MaxPBKeySize + DataEntry.MaxValueSize) * 5 + val MaxEntryCount: Int = 100 - override val typeId: TxType = 12: Byte - override val supportedVersions: Set[TxVersion] = Set(1, 2) + override val typeId: TxType = 12: Byte implicit val validator: TxValidator[DataTransaction] = DataTxValidator diff --git a/node/src/main/scala/com/wavesplatform/transaction/EthereumTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/EthereumTransaction.scala index d30e63acbf..39bc2f1543 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/EthereumTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/EthereumTransaction.scala @@ -39,7 +39,6 @@ final case class EthereumTransaction( override val chainId: Byte ) extends Transaction(TransactionType.Ethereum) with Authorized - with VersionedTransaction.ConstV1 with PBSince.V1 { self => import EthereumTransaction.* diff --git a/node/src/main/scala/com/wavesplatform/transaction/GenesisTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/GenesisTransaction.scala index ae5758263e..b57bfd7849 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/GenesisTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/GenesisTransaction.scala @@ -26,8 +26,7 @@ case class GenesisTransaction(recipient: Address, amount: TxNonNegativeAmount, t object GenesisTransaction extends TransactionParser { type TransactionT = GenesisTransaction - override val typeId: TxType = 1: Byte - override val supportedVersions: Set[TxVersion] = Set(1) + override val typeId: TxType = 1: Byte override def parseBytes(bytes: Array[TxVersion]): Try[GenesisTransaction] = GenesisTxSerializer.parseBytes(bytes) diff --git a/node/src/main/scala/com/wavesplatform/transaction/PBSince.scala b/node/src/main/scala/com/wavesplatform/transaction/PBSince.scala index 69ebdc017e..3ab627c9d2 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/PBSince.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/PBSince.scala @@ -1,25 +1,19 @@ package com.wavesplatform.transaction -import com.wavesplatform.protobuf.transaction.PBTransactions - -trait PBSince { self: Transaction with VersionedTransaction => - def protobufVersion: TxVersion - final def isProtobufVersion: Boolean = self.version >= protobufVersion - - override def bytesSize: Int = - if (isProtobufVersion) PBTransactions.protobuf(self).serializedSize else bytes().length -} +sealed trait PBSince object PBSince { - trait V1 extends PBSince { self: Transaction with VersionedTransaction => - override def protobufVersion: TxVersion = TxVersion.V1 - } + trait V1 extends PBSince + trait V2 extends PBSince + trait V3 extends PBSince - trait V2 extends PBSince { self: Transaction with VersionedTransaction => - override def protobufVersion: TxVersion = TxVersion.V2 - } + def version(tx: PBSince): TxVersion = + tx match { + case _: V1 => TxVersion.V1 + case _: V2 => TxVersion.V2 + case _: V3 => TxVersion.V3 + } - trait V3 extends PBSince { self: Transaction with VersionedTransaction => - override def protobufVersion: TxVersion = TxVersion.V3 - } + def affects(tx: PBSince & Versioned): Boolean = + tx.version >= version(tx) } diff --git a/node/src/main/scala/com/wavesplatform/transaction/PaymentTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/PaymentTransaction.scala index 0f24102e8e..84f3cafeb1 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/PaymentTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/PaymentTransaction.scala @@ -1,6 +1,5 @@ package com.wavesplatform.transaction -import scala.util.Try import com.wavesplatform.account.{Address, KeyPair, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto @@ -11,6 +10,8 @@ import com.wavesplatform.transaction.validation.impl.PaymentTxValidator import monix.eval.Coeval import play.api.libs.json.JsObject +import scala.util.Try + case class PaymentTransaction( sender: PublicKey, recipient: Address, @@ -36,8 +37,7 @@ case class PaymentTransaction( object PaymentTransaction extends TransactionParser { type TransactionT = PaymentTransaction - override val typeId: TxType = 2: Byte - override val supportedVersions: Set[TxVersion] = Set(1) + override val typeId: TxType = 2: Byte override def parseBytes(bytes: Array[TxVersion]): Try[PaymentTransaction] = PaymentTxSerializer.parseBytes(bytes) diff --git a/node/src/main/scala/com/wavesplatform/transaction/SigProofsSwitch.scala b/node/src/main/scala/com/wavesplatform/transaction/SigProofsSwitch.scala index d4241a1886..a49c374369 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/SigProofsSwitch.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/SigProofsSwitch.scala @@ -1,6 +1,6 @@ package com.wavesplatform.transaction -trait SigProofsSwitch extends ProvenTransaction { self: Transaction with VersionedTransaction => +trait SigProofsSwitch extends ProvenTransaction { self: Transaction with Versioned => def usesLegacySignature: Boolean = self.version == Transaction.V1 } diff --git a/node/src/main/scala/com/wavesplatform/transaction/TransactionParser.scala b/node/src/main/scala/com/wavesplatform/transaction/TransactionParser.scala index c93747e2f6..90477cf20c 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/TransactionParser.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/TransactionParser.scala @@ -9,8 +9,6 @@ trait TransactionParser { def typeId: TxType - def supportedVersions: Set[TxVersion] - def parseBytes(bytes: Array[Byte]): Try[TransactionT] implicit def validator: TxValidator[TransactionT] diff --git a/node/src/main/scala/com/wavesplatform/transaction/TransactionParsers.scala b/node/src/main/scala/com/wavesplatform/transaction/TransactionParsers.scala index afeb7cd4ef..50045bc80a 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/TransactionParsers.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/TransactionParsers.scala @@ -3,13 +3,13 @@ package com.wavesplatform.transaction import com.wavesplatform.transaction.assets.* import com.wavesplatform.transaction.assets.exchange.ExchangeTransaction import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction} -import com.wavesplatform.transaction.smart.{InvokeExpressionTransaction, InvokeScriptTransaction, SetScriptTransaction} +import com.wavesplatform.transaction.smart.{InvokeScriptTransaction, SetScriptTransaction} import com.wavesplatform.transaction.transfer.* import scala.util.{Failure, Try} object TransactionParsers { - private[this] val old: Map[Byte, TransactionParser] = Seq[TransactionParser]( + private[this] val old: Map[TxType, TransactionParser] = Seq[TransactionParser]( GenesisTransaction, PaymentTransaction, IssueTransaction, @@ -21,11 +21,9 @@ object TransactionParsers { CreateAliasTransaction, MassTransferTransaction, TransferTransaction - ).map { x => - x.typeId -> x - }.toMap + ).map { x => x.typeId -> x }.toMap - private[this] val modern: Map[(Byte, Byte), TransactionParser] = Seq[TransactionParser]( + private[this] val modern: Map[TxType, TransactionParser] = Seq[TransactionParser]( DataTransaction, SetScriptTransaction, IssueTransaction, @@ -38,22 +36,10 @@ object TransactionParsers { SponsorFeeTransaction, SetAssetScriptTransaction, InvokeScriptTransaction, - TransferTransaction, - InvokeExpressionTransaction, - UpdateAssetInfoTransaction - ).flatMap { x => - x.supportedVersions.map { version => - ((x.typeId, version), x) - } - }.toMap - - val all: Map[(Byte, Byte), TransactionParser] = old.flatMap { case (typeId, builder) => - builder.supportedVersions.map { version => - ((typeId, version), builder) - } - } ++ modern + TransferTransaction + ).map { x => (x.typeId, x) }.toMap - def by(typeId: Byte, version: TxVersion): Option[TransactionParser] = all.get((typeId, version)) + val all: Map[TxType, TransactionParser] = old ++ modern def parseBytes(bytes: Array[Byte]): Try[Transaction] = { def validate(parser: TransactionParser)(tx: parser.TransactionT): Try[Transaction] = { @@ -63,7 +49,7 @@ object TransactionParsers { def modernParseBytes: Try[Transaction] = { val typeId = bytes(1) val version = bytes(2) - modern.get((typeId, version)) match { + modern.get(typeId) match { case Some(parser) => parser.parseBytes(bytes).flatMap(validate(parser)) case None => Failure[Transaction](UnknownTypeAndVersion(typeId, version)) } @@ -74,13 +60,9 @@ object TransactionParsers { case None => Failure[Transaction](UnknownType(bytes(0))) } } - for { _ <- Either.cond(bytes.length > 2, (), BufferUnderflow).toTry tx <- if (bytes(0) == 0) modernParseBytes else oldParseBytes } yield tx } - - def versionIsCorrect(tx: Transaction & VersionedTransaction): Boolean = - TransactionParsers.all.contains((tx.tpe.id.toByte, tx.version)) } diff --git a/node/src/main/scala/com/wavesplatform/transaction/TxValidationError.scala b/node/src/main/scala/com/wavesplatform/transaction/TxValidationError.scala index 5d4f1cabc5..ff057fd7f7 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/TxValidationError.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/TxValidationError.scala @@ -29,11 +29,11 @@ object TxValidationError { case object MissingSenderPrivateKey extends ValidationError case object UnsupportedTransactionType extends ValidationError case object InvalidRequestSignature extends ValidationError - case class BlockFromFuture(ts: Long) extends ValidationError + case class BlockFromFuture(blockTs: Long, localTs: Long) extends ValidationError case class AlreadyInTheState(txId: ByteStr, txHeight: Int) extends ValidationError case class AccountBalanceError(errs: Map[Address, String]) extends ValidationError - case class AliasDoesNotExist(a: Alias) extends ValidationError { override def toString: String = s"Alias '$a' does not exists." } - case class AliasIsDisabled(a: Alias) extends ValidationError + case class AliasDoesNotExist(a: Alias) extends ValidationError { override def toString: String = s"Alias '$a' does not exist." } + case class AliasIsDisabled(a: Alias) extends ValidationError case class OrderValidationError(order: Order, err: String) extends ValidationError case class SenderIsBlacklisted(addr: String) extends ValidationError case class Mistiming(err: String) extends ValidationError diff --git a/node/src/main/scala/com/wavesplatform/transaction/Versioned.scala b/node/src/main/scala/com/wavesplatform/transaction/Versioned.scala new file mode 100644 index 0000000000..eb0b7b4dc6 --- /dev/null +++ b/node/src/main/scala/com/wavesplatform/transaction/Versioned.scala @@ -0,0 +1,20 @@ +package com.wavesplatform.transaction + +sealed trait Versioned { + val version: TxVersion +} + +object Versioned { + trait ConstV1 extends Versioned { + val version: TxVersion = TxVersion.V1 + } + trait ToV2 extends Versioned + trait ToV3 extends Versioned + + def maxVersion(tx: Versioned): TxVersion = + tx match { + case _: ConstV1 => TxVersion.V1 + case _: ToV2 => TxVersion.V2 + case _: ToV3 => TxVersion.V3 + } +} diff --git a/node/src/main/scala/com/wavesplatform/transaction/VersionedTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/VersionedTransaction.scala deleted file mode 100644 index 79a93b97a5..0000000000 --- a/node/src/main/scala/com/wavesplatform/transaction/VersionedTransaction.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.wavesplatform.transaction - -trait VersionedTransaction { - def version: TxVersion -} - -object VersionedTransaction { - trait ConstV1 extends VersionedTransaction { - def version: TxVersion = TxVersion.V1 - } -} \ No newline at end of file diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/BurnTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/BurnTransaction.scala index d0461c9034..4e036cff38 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/BurnTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/BurnTransaction.scala @@ -22,8 +22,9 @@ final case class BurnTransaction( timestamp: TxTimestamp, proofs: Proofs, chainId: Byte -) extends Transaction(TransactionType.Burn, Seq(asset)) with ProvenTransaction - with VersionedTransaction +) extends Transaction(TransactionType.Burn, Seq(asset)) + with ProvenTransaction + with Versioned.ToV3 with SigProofsSwitch with TxWithFee.InWaves with FastHashId @@ -36,9 +37,7 @@ final case class BurnTransaction( object BurnTransaction extends TransactionParser { type TransactionT = BurnTransaction - - override val typeId: TxType = 6: Byte - override val supportedVersions: Set[TxVersion] = Set(1, 2, 3) + override val typeId: TxType = 6: Byte implicit val validator: TxValidator[BurnTransaction] = BurnTxValidator diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/IssueTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/IssueTransaction.scala index af1ca6b0f2..193ecc0d41 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/IssueTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/IssueTransaction.scala @@ -30,7 +30,7 @@ case class IssueTransaction( proofs: Proofs, chainId: Byte ) extends Transaction(TransactionType.Issue) - with VersionedTransaction + with Versioned.ToV3 with ProvenTransaction with FastHashId with SigProofsSwitch @@ -50,8 +50,7 @@ object IssueTransaction extends TransactionParser { val MaxAssetDescriptionLength = 1000 val MaxAssetDecimals = 8 - override val typeId: TxType = 3: Byte - override val supportedVersions: Set[TxVersion] = Set(1, 2, 3) + override val typeId: TxType = 3: Byte implicit val validator: TxValidator[IssueTransaction] = IssueTxValidator implicit def sign(tx: IssueTransaction, privateKey: PrivateKey): IssueTransaction = diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/ReissueTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/ReissueTransaction.scala index 42db148d6d..f73402d749 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/ReissueTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/ReissueTransaction.scala @@ -24,7 +24,7 @@ case class ReissueTransaction( proofs: Proofs, chainId: Byte ) extends Transaction(TransactionType.Reissue, Seq(asset)) - with VersionedTransaction + with Versioned.ToV3 with ProvenTransaction with SigProofsSwitch with TxWithFee.InWaves @@ -39,8 +39,7 @@ case class ReissueTransaction( object ReissueTransaction extends TransactionParser { type TransactionT = ReissueTransaction - override val typeId: TxType = 5: Byte - override def supportedVersions: Set[TxVersion] = Set(1, 2, 3) + override val typeId: TxType = 5: Byte implicit val validator: TxValidator[ReissueTransaction] = ReissueTxValidator implicit def sign(tx: ReissueTransaction, privateKey: PrivateKey): ReissueTransaction = diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/SetAssetScriptTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/SetAssetScriptTransaction.scala index afe6c8d1ff..c445da9fe9 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/SetAssetScriptTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/SetAssetScriptTransaction.scala @@ -1,11 +1,11 @@ package com.wavesplatform.transaction.assets -import com.wavesplatform.account._ +import com.wavesplatform.account.* import com.wavesplatform.crypto import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.script.Script +import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.IssuedAsset -import com.wavesplatform.transaction._ import com.wavesplatform.transaction.serialization.impl.SetAssetScriptTxSerializer import com.wavesplatform.transaction.validation.TxValidator import com.wavesplatform.transaction.validation.impl.SetAssetScriptTxValidator @@ -24,7 +24,7 @@ case class SetAssetScriptTransaction( proofs: Proofs, chainId: Byte ) extends Transaction(TransactionType.SetAssetScript, Seq(asset)) - with VersionedTransaction + with Versioned.ToV2 with ProvenTransaction with TxWithFee.InWaves with FastHashId @@ -37,9 +37,7 @@ case class SetAssetScriptTransaction( object SetAssetScriptTransaction extends TransactionParser { type TransactionT = SetAssetScriptTransaction - - override val typeId: TxType = 15: Byte - override val supportedVersions: Set[TxVersion] = Set(1, 2) + override val typeId: TxType = 15: Byte implicit val validator: TxValidator[SetAssetScriptTransaction] = SetAssetScriptTxValidator diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/SponsorFeeTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/SponsorFeeTransaction.scala index 42ff42a7f4..02a0d53e18 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/SponsorFeeTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/SponsorFeeTransaction.scala @@ -24,8 +24,9 @@ case class SponsorFeeTransaction( timestamp: TxTimestamp, proofs: Proofs, chainId: Byte -) extends Transaction(TransactionType.SponsorFee, Seq(asset)) with ProvenTransaction - with VersionedTransaction +) extends Transaction(TransactionType.SponsorFee, Seq(asset)) + with ProvenTransaction + with Versioned.ToV2 with TxWithFee.InWaves with FastHashId with PBSince.V2 { @@ -37,9 +38,7 @@ case class SponsorFeeTransaction( object SponsorFeeTransaction extends TransactionParser { type TransactionT = SponsorFeeTransaction - - override val typeId: TxType = 14: Byte - override val supportedVersions: Set[TxVersion] = Set(1, 2) + override val typeId: TxType = 14: Byte implicit val validator: TxValidator[SponsorFeeTransaction] = SponsorFeeTxValidator diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/UpdateAssetInfoTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/UpdateAssetInfoTransaction.scala index dacd992375..75dc0c047d 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/UpdateAssetInfoTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/UpdateAssetInfoTransaction.scala @@ -15,7 +15,7 @@ import play.api.libs.json.{JsObject, Json} import scala.util.{Failure, Success, Try} case class UpdateAssetInfoTransaction( - version: TxVersion, + override val version: TxVersion, sender: PublicKey, assetId: IssuedAsset, name: String, @@ -26,7 +26,7 @@ case class UpdateAssetInfoTransaction( proofs: Proofs, chainId: Byte ) extends Transaction(TransactionType.UpdateAssetInfo, Seq(assetId)) - with VersionedTransaction + with Versioned.ConstV1 with FastHashId with ProvenTransaction with PBSince.V1 { self => @@ -49,9 +49,7 @@ case class UpdateAssetInfoTransaction( object UpdateAssetInfoTransaction extends TransactionParser { type TransactionT = UpdateAssetInfoTransaction - - override val typeId: TxType = 17: Byte - override val supportedVersions: Set[TxVersion] = Set(1) + override val typeId: TxType = 17: Byte implicit def sign(tx: UpdateAssetInfoTransaction, privateKey: PrivateKey): UpdateAssetInfoTransaction = tx.copy(proofs = Proofs(crypto.sign(privateKey, tx.bodyBytes()))) diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/ExchangeTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/ExchangeTransaction.scala index 3e97ec447c..37e221117c 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/ExchangeTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/exchange/ExchangeTransaction.scala @@ -27,7 +27,7 @@ case class ExchangeTransaction( proofs: Proofs, chainId: Byte ) extends Transaction(TransactionType.Exchange, order1.assetPair.checkedAssets) - with VersionedTransaction + with Versioned.ToV3 with ProvenTransaction with TxWithFee.InWaves with FastHashId @@ -65,8 +65,6 @@ object ExchangeTransaction extends TransactionParser { override def parseBytes(bytes: Array[TxVersion]): Try[ExchangeTransaction] = ExchangeTxSerializer.parseBytes(bytes) - override def supportedVersions: Set[TxVersion] = Set(1, 2, 3) - val typeId: TxType = 7: Byte def create( diff --git a/node/src/main/scala/com/wavesplatform/transaction/lease/LeaseCancelTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/lease/LeaseCancelTransaction.scala index 11bc5838e0..70607f6eae 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/lease/LeaseCancelTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/lease/LeaseCancelTransaction.scala @@ -4,7 +4,7 @@ import com.wavesplatform.account.{AddressScheme, KeyPair, PrivateKey, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto import com.wavesplatform.lang.ValidationError -import com.wavesplatform.transaction._ +import com.wavesplatform.transaction.* import com.wavesplatform.transaction.serialization.impl.LeaseCancelTxSerializer import com.wavesplatform.transaction.validation.TxValidator import com.wavesplatform.transaction.validation.impl.LeaseCancelTxValidator @@ -23,7 +23,7 @@ final case class LeaseCancelTransaction( chainId: Byte ) extends Transaction(TransactionType.LeaseCancel) with SigProofsSwitch - with VersionedTransaction + with Versioned.ToV3 with TxWithFee.InWaves with FastHashId with PBSince.V3 { @@ -35,8 +35,7 @@ final case class LeaseCancelTransaction( object LeaseCancelTransaction extends TransactionParser { type TransactionT = LeaseCancelTransaction - val supportedVersions: Set[TxVersion] = Set(1, 2, 3) - val typeId: TxType = 9: Byte + val typeId: TxType = 9: Byte implicit val validator: TxValidator[LeaseCancelTransaction] = LeaseCancelTxValidator diff --git a/node/src/main/scala/com/wavesplatform/transaction/lease/LeaseTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/lease/LeaseTransaction.scala index b81bbbc4cc..aea45ea010 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/lease/LeaseTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/lease/LeaseTransaction.scala @@ -23,7 +23,7 @@ final case class LeaseTransaction( chainId: Byte ) extends Transaction(TransactionType.Lease) with SigProofsSwitch - with VersionedTransaction + with Versioned.ToV3 with TxWithFee.InWaves with FastHashId with PBSince.V3 { @@ -35,8 +35,7 @@ final case class LeaseTransaction( object LeaseTransaction extends TransactionParser { type TransactionT = LeaseTransaction - val supportedVersions: Set[TxVersion] = Set(1, 2, 3) - val typeId: TxType = 8: Byte + val typeId: TxType = 8: Byte implicit val validator: TxValidator[LeaseTransaction] = LeaseTxValidator diff --git a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/BaseTxJson.scala b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/BaseTxJson.scala index ff6ff28cbf..26ee50dbac 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/BaseTxJson.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/BaseTxJson.scala @@ -1,6 +1,6 @@ package com.wavesplatform.transaction.serialization.impl -import com.wavesplatform.transaction.{PBSince, ProvenTransaction, SigProofsSwitch, Transaction, VersionedTransaction} +import com.wavesplatform.transaction.{EthereumTransaction, PBSince, ProvenTransaction, SigProofsSwitch, Transaction, Versioned} import play.api.libs.json.{JsArray, JsObject, JsString, Json} object BaseTxJson { @@ -12,15 +12,17 @@ object BaseTxJson { "feeAssetId" -> tx.assetFee._1.maybeBase58Repr, "timestamp" -> tx.timestamp ) ++ (tx match { - case v: VersionedTransaction => Json.obj("version" -> v.version) - case _ => Json.obj() + case v: Versioned => Json.obj("version" -> v.version) + case _: EthereumTransaction => Json.obj("version" -> 1) + case _ => Json.obj() }) ++ (tx match { - case pbs: PBSince if pbs.isProtobufVersion => Json.obj("chainId" -> tx.chainId) - case _ => Json.obj() + case pbs: PBSince with Versioned if PBSince.affects(pbs) => Json.obj("chainId" -> tx.chainId) + case e: EthereumTransaction => Json.obj("chainId" -> e.chainId) + case _ => Json.obj() }) ++ (tx match { case p: ProvenTransaction => Json.obj( - "sender" -> p.sender.toAddress(p.chainId), + "sender" -> p.sender.toAddress(p.chainId), "senderPublicKey" -> p.sender, "proofs" -> JsArray(p.proofs.proofs.map(p => JsString(p.toString))) ) ++ (tx match { diff --git a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/DataTxSerializer.scala b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/DataTxSerializer.scala index 6b4eb1038e..e429ee299c 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/DataTxSerializer.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/DataTxSerializer.scala @@ -5,10 +5,10 @@ import java.nio.charset.StandardCharsets.UTF_8 import com.google.common.primitives.{Bytes, Longs, Shorts} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.account.AddressScheme -import com.wavesplatform.serialization._ +import com.wavesplatform.serialization.* import com.wavesplatform.state.DataEntry.Type import com.wavesplatform.state.{BinaryDataEntry, BooleanDataEntry, DataEntry, IntegerDataEntry, StringDataEntry} -import com.wavesplatform.transaction.{DataTransaction, TxPositiveAmount, TxVersion} +import com.wavesplatform.transaction.{DataTransaction, PBSince, TxPositiveAmount, TxVersion} import com.wavesplatform.utils.StringBytes import play.api.libs.json.{JsObject, Json} @@ -51,7 +51,7 @@ object DataTxSerializer { } def toBytes(tx: DataTransaction): Array[Byte] = - if (tx.isProtobufVersion) PBTransactionSerializer.bytes(tx) + if (PBSince.affects(tx)) PBTransactionSerializer.bytes(tx) else Bytes.concat(Array(0: Byte), this.bodyBytes(tx), tx.proofs.bytes()) def parseBytes(bytes: Array[Byte]): Try[DataTransaction] = Try { 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 7086a56794..4059d42e61 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,7 +1,6 @@ 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.* @@ -11,7 +10,7 @@ import com.wavesplatform.lang.v1.serialization.SerdeV1 import com.wavesplatform.serialization.* import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment -import com.wavesplatform.transaction.{Asset, TxPositiveAmount, TxVersion} +import com.wavesplatform.transaction.{Asset, PBSince, TxPositiveAmount, TxVersion} import play.api.libs.json.{JsArray, JsObject, JsString, Json} import scala.util.Try @@ -69,7 +68,7 @@ object InvokeScriptTxSerializer { } def toBytes(tx: InvokeScriptTransaction): Array[Byte] = - if (tx.isProtobufVersion) PBTransactionSerializer.bytes(tx) + if (PBSince.affects(tx)) PBTransactionSerializer.bytes(tx) else Bytes.concat(Array(0: Byte), this.bodyBytes(tx), tx.proofs.bytes()) def parseBytes(bytes: Array[Byte]): Try[InvokeScriptTransaction] = Try { diff --git a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/MassTransferTxSerializer.scala b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/MassTransferTxSerializer.scala index f1b7125c59..121acbb987 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/MassTransferTxSerializer.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/MassTransferTxSerializer.scala @@ -4,8 +4,8 @@ import java.nio.ByteBuffer import com.google.common.primitives.{Bytes, Longs, Shorts} import com.wavesplatform.account.AddressScheme import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.serialization._ -import com.wavesplatform.transaction.{TxNonNegativeAmount, TxPositiveAmount, TxVersion} +import com.wavesplatform.serialization.* +import com.wavesplatform.transaction.{PBSince, TxNonNegativeAmount, TxPositiveAmount, TxVersion} import com.wavesplatform.transaction.transfer.MassTransferTransaction import com.wavesplatform.transaction.transfer.MassTransferTransaction.{ParsedTransfer, Transfer} import com.wavesplatform.utils.byteStrFormat @@ -51,7 +51,7 @@ object MassTransferTxSerializer { } def toBytes(tx: MassTransferTransaction): Array[Byte] = - if (tx.isProtobufVersion) PBTransactionSerializer.bytes(tx) + if (PBSince.affects(tx)) PBTransactionSerializer.bytes(tx) else Bytes.concat(this.bodyBytes(tx), tx.proofs.bytes()) // No zero mark def parseBytes(bytes: Array[Byte]): Try[MassTransferTransaction] = Try { diff --git a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SetAssetScriptTxSerializer.scala b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SetAssetScriptTxSerializer.scala index 2d9b268026..5122a1c8b6 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SetAssetScriptTxSerializer.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SetAssetScriptTxSerializer.scala @@ -4,7 +4,7 @@ import java.nio.ByteBuffer import com.google.common.primitives.{Bytes, Longs} import com.wavesplatform.account.AddressScheme import com.wavesplatform.serialization.{ByteBufferOps, Deser} -import com.wavesplatform.transaction.{TxPositiveAmount, TxVersion} +import com.wavesplatform.transaction.{PBSince, TxPositiveAmount, TxVersion} import com.wavesplatform.transaction.assets.SetAssetScriptTransaction import play.api.libs.json.{JsObject, Json} @@ -38,7 +38,7 @@ object SetAssetScriptTxSerializer { } def toBytes(tx: SetAssetScriptTransaction): Array[Byte] = - if (tx.isProtobufVersion) PBTransactionSerializer.bytes(tx) + if (PBSince.affects(tx)) PBTransactionSerializer.bytes(tx) else Bytes.concat(Array(0: Byte), this.bodyBytes(tx), tx.proofs.bytes()) def parseBytes(bytes: Array[Byte]): Try[SetAssetScriptTransaction] = Try { diff --git a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SetScriptTxSerializer.scala b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SetScriptTxSerializer.scala index 3475e0867c..77e406ab37 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SetScriptTxSerializer.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SetScriptTxSerializer.scala @@ -4,7 +4,7 @@ import java.nio.ByteBuffer import com.google.common.primitives.{Bytes, Longs} import com.wavesplatform.account.AddressScheme import com.wavesplatform.serialization.{ByteBufferOps, Deser} -import com.wavesplatform.transaction.{TxPositiveAmount, TxVersion} +import com.wavesplatform.transaction.{PBSince, TxPositiveAmount, TxVersion} import com.wavesplatform.transaction.smart.SetScriptTransaction import play.api.libs.json.{JsObject, Json} @@ -36,7 +36,7 @@ object SetScriptTxSerializer { } def toBytes(tx: SetScriptTransaction): Array[Byte] = { - if (tx.isProtobufVersion) PBTransactionSerializer.bytes(tx) + if (PBSince.affects(tx)) PBTransactionSerializer.bytes(tx) else Bytes.concat(Array(0: Byte), this.bodyBytes(tx), tx.proofs.bytes()) } diff --git a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SponsorFeeTxSerializer.scala b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SponsorFeeTxSerializer.scala index 6501d79345..6a6db4be5e 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SponsorFeeTxSerializer.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/serialization/impl/SponsorFeeTxSerializer.scala @@ -4,7 +4,7 @@ import java.nio.ByteBuffer import com.google.common.primitives.{Bytes, Longs} import com.wavesplatform.account.AddressScheme import com.wavesplatform.serialization.ByteBufferOps -import com.wavesplatform.transaction.{TxPositiveAmount, TxVersion} +import com.wavesplatform.transaction.{PBSince, TxPositiveAmount, TxVersion} import com.wavesplatform.transaction.assets.SponsorFeeTransaction import play.api.libs.json.{JsObject, Json} @@ -38,7 +38,7 @@ object SponsorFeeTxSerializer { } def toBytes(tx: SponsorFeeTransaction): Array[Byte] = { - if (tx.isProtobufVersion) PBTransactionSerializer.bytes(tx) + if (PBSince.affects(tx)) PBTransactionSerializer.bytes(tx) else Bytes.concat(Array(0: Byte, tx.tpe.id.toByte, tx.version), this.bodyBytes(tx), tx.proofs.bytes()) // [typeId, version] appears twice } diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeExpressionTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeExpressionTransaction.scala index 669eac1e01..ce03551608 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeExpressionTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeExpressionTransaction.scala @@ -16,7 +16,7 @@ import play.api.libs.json.{JsObject, Json} import scala.util.{Failure, Success, Try} case class InvokeExpressionTransaction( - version: TxVersion, + override val version: TxVersion, sender: PublicKey, expression: ExprScript, fee: TxPositiveAmount, @@ -26,6 +26,7 @@ case class InvokeExpressionTransaction( chainId: Byte ) extends Transaction(TransactionType.InvokeExpression, Nil) with InvokeTransaction + with Versioned.ConstV1 with PBSince.V1 { lazy val expressionBytes: ByteStr = expression.bytes.value() @@ -50,8 +51,7 @@ case class InvokeExpressionTransaction( object InvokeExpressionTransaction extends TransactionParser { type TransactionT = InvokeExpressionTransaction - override val typeId: TxType = 18: Byte - override val supportedVersions: Set[TxVersion] = Set(1) + override val typeId: TxType = 18: Byte implicit def sign(tx: InvokeExpressionTransaction, privateKey: PrivateKey): InvokeExpressionTransaction = tx.copy(proofs = Proofs(crypto.sign(privateKey, tx.bodyBytes()))) diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeScriptTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeScriptTransaction.scala index 7f84764e81..e848fa972d 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeScriptTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeScriptTransaction.scala @@ -16,7 +16,7 @@ import play.api.libs.json.* import scala.util.Try case class InvokeScriptTransaction( - version: TxVersion, + override val version: TxVersion, sender: PublicKey, dApp: AddressOrAlias, funcCallOpt: Option[FUNCTION_CALL], @@ -28,6 +28,7 @@ case class InvokeScriptTransaction( chainId: Byte ) extends Transaction(TransactionType.InvokeScript, payments.collect(InvokeScriptLike.IssuedAssets)) with InvokeTransaction + with Versioned.ToV2 with PBSince.V2 { override def root: InvokeScriptTransactionLike = this @@ -42,8 +43,7 @@ case class InvokeScriptTransaction( object InvokeScriptTransaction extends TransactionParser { type TransactionT = InvokeScriptTransaction - override val typeId: TxType = 16: Byte - override val supportedVersions: Set[TxVersion] = Set(1, 2) + override val typeId: TxType = 16: Byte implicit val validator: TxValidator[InvokeScriptTransaction] = InvokeScriptTxValidator diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeTransaction.scala index b78fb3e34f..c8aee7d3c8 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/InvokeTransaction.scala @@ -3,15 +3,9 @@ import com.wavesplatform.lang.v1.FunctionHeader.User import com.wavesplatform.lang.v1.compiler.Terms.FUNCTION_CALL import com.wavesplatform.lang.v1.evaluator.ContractEvaluator import com.wavesplatform.state.diffs.invoke.InvokeScriptTransactionLike -import com.wavesplatform.transaction.{Asset, FastHashId, ProvenTransaction, Transaction, TxWithFee, VersionedTransaction} +import com.wavesplatform.transaction.{Asset, FastHashId, ProvenTransaction, Transaction, TxWithFee} -trait InvokeTransaction - extends Transaction - with InvokeScriptTransactionLike - with ProvenTransaction - with TxWithFee.InCustomAsset - with FastHashId - with VersionedTransaction { +trait InvokeTransaction extends Transaction with InvokeScriptTransactionLike with ProvenTransaction with TxWithFee.InCustomAsset with FastHashId { override val checkedAssets: Seq[Asset.IssuedAsset] = super[InvokeScriptTransactionLike].checkedAssets } diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/RealTransactionWrapper.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/RealTransactionWrapper.scala index 9aa5819bf6..d07174266f 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/RealTransactionWrapper.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/RealTransactionWrapper.scala @@ -21,7 +21,7 @@ object RealTransactionWrapper { private def header(tx: Transaction, txIdOpt: Option[ByteStr] = None): Header = { val v = tx match { case _: EthereumTransaction => 0.toByte - case vt: VersionedTransaction => vt.version + case vt: Versioned => vt.version case _ => TxVersion.V1 } Header(txIdOpt.getOrElse(ByteStr(tx.id().arr)), tx.fee, tx.timestamp, v) diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/SetScriptTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/SetScriptTransaction.scala index 9df4c37d73..8f21e7ac3b 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/SetScriptTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/SetScriptTransaction.scala @@ -21,8 +21,9 @@ case class SetScriptTransaction( timestamp: TxTimestamp, proofs: Proofs, chainId: Byte -) extends Transaction(TransactionType.SetScript) with ProvenTransaction - with VersionedTransaction +) extends Transaction(TransactionType.SetScript) + with ProvenTransaction + with Versioned.ToV2 with TxWithFee.InWaves with FastHashId with PBSince.V2 { @@ -35,8 +36,7 @@ case class SetScriptTransaction( object SetScriptTransaction extends TransactionParser { type TransactionT = SetScriptTransaction - override val typeId: TxType = 13: Byte - override val supportedVersions: Set[TxVersion] = Set(1, 2) + override val typeId: TxType = 13: Byte implicit val validator: TxValidator[SetScriptTransaction] = SetScriptTxValidator 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 0c4269f9c3..f8bbd054bf 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala @@ -26,7 +26,7 @@ import com.wavesplatform.state.* import com.wavesplatform.state.BlockRewardCalculator.CurrentBlockRewardPart import com.wavesplatform.state.diffs.invoke.InvokeScriptDiff.validateIntermediateBalances import com.wavesplatform.state.diffs.invoke.{InvokeScript, InvokeScriptDiff, InvokeScriptTransactionLike} -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.transaction.Asset.* import com.wavesplatform.transaction.TxValidationError.{FailedTransactionError, GenericError} import com.wavesplatform.transaction.assets.exchange.Order diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptRunner.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptRunner.scala index 1b6802082c..3d19d95f3d 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptRunner.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptRunner.scala @@ -54,7 +54,8 @@ object ScriptRunner { blockchain.newEvaluatorMode, blockchain.isFeatureActivated(RideV6), enableExecutionLog, - blockchain.isFeatureActivated(ConsensusImprovements) + blockchain.isFeatureActivated(ConsensusImprovements), + blockchain.isFeatureActivated(LightNode) ) def applyGeneric( @@ -72,7 +73,8 @@ object ScriptRunner { newEvaluatorMode: Boolean, checkWeakPk: Boolean, enableExecutionLog: Boolean, - fixBigScriptField: Boolean + fixBigScriptField: Boolean, + fixedThrownError: Boolean ): (Log[Id], Int, Either[ExecutionError, EVALUATED]) = { def evalVerifier( @@ -135,7 +137,8 @@ object ScriptRunner { correctFunctionCallScope = checkEstimatorSumOverflow, newMode = newEvaluatorMode, onExceed, - enableExecutionLog + enableExecutionLog, + fixedThrownError ) (log, limit - unusedComplexity, result) diff --git a/node/src/main/scala/com/wavesplatform/transaction/transfer/MassTransferTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/transfer/MassTransferTransaction.scala index 2d24c24068..f1cf5c1b8b 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/transfer/MassTransferTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/transfer/MassTransferTransaction.scala @@ -1,6 +1,5 @@ package com.wavesplatform.transaction.transfer -import scala.util.{Either, Try} import cats.instances.list.* import cats.syntax.traverse.* import com.wavesplatform.account.* @@ -17,6 +16,8 @@ import com.wavesplatform.transaction.validation.impl.MassTransferTxValidator import monix.eval.Coeval import play.api.libs.json.{JsObject, Json, OFormat} +import scala.util.{Either, Try} + case class MassTransferTransaction( version: TxVersion, sender: PublicKey, @@ -27,12 +28,15 @@ case class MassTransferTransaction( attachment: ByteStr, proofs: Proofs, chainId: Byte -) extends Transaction(TransactionType.MassTransfer, assetId match { - case Waves => Seq() - case a: IssuedAsset => Seq(a) - }) +) extends Transaction( + TransactionType.MassTransfer, + assetId match { + case Waves => Seq() + case a: IssuedAsset => Seq(a) + } + ) with ProvenTransaction - with VersionedTransaction + with Versioned.ToV2 with TxWithFee.InWaves with FastHashId with PBSince.V2 { @@ -57,8 +61,7 @@ object MassTransferTransaction extends TransactionParser { val MaxTransferCount = 100 - override val typeId: TxType = 11: Byte - override val supportedVersions: Set[TxVersion] = Set(1, 2) + override val typeId: TxType = 11: Byte implicit val validator: TxValidator[MassTransferTransaction] = MassTransferTxValidator @@ -121,14 +124,13 @@ object MassTransferTransaction extends TransactionParser { signed(version, sender.publicKey, assetId, transfers, fee, timestamp, attachment, sender.privateKey, chainId) def parseTransfersList(transfers: List[Transfer]): Validation[List[ParsedTransfer]] = - transfers.traverse { - case Transfer(recipient, amount) => - for { - addressOrAlias <- AddressOrAlias.fromString(recipient) - transferAmount <- TxNonNegativeAmount(amount)(NegativeAmount(amount, "asset")) - } yield { - ParsedTransfer(addressOrAlias, transferAmount) - } + transfers.traverse { case Transfer(recipient, amount) => + for { + addressOrAlias <- AddressOrAlias.fromString(recipient) + transferAmount <- TxNonNegativeAmount(amount)(NegativeAmount(amount, "asset")) + } yield { + ParsedTransfer(addressOrAlias, transferAmount) + } } } diff --git a/node/src/main/scala/com/wavesplatform/transaction/transfer/TransferTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/transfer/TransferTransaction.scala index e96535f6e4..b67c2aae32 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/transfer/TransferTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/transfer/TransferTransaction.scala @@ -1,13 +1,13 @@ package com.wavesplatform.transaction.transfer -import com.wavesplatform.account._ +import com.wavesplatform.account.* import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto import com.wavesplatform.lang.ValidationError +import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} -import com.wavesplatform.transaction._ import com.wavesplatform.transaction.serialization.impl.TransferTxSerializer -import com.wavesplatform.transaction.validation._ +import com.wavesplatform.transaction.validation.* import com.wavesplatform.transaction.validation.impl.TransferTxValidator import com.wavesplatform.utils.base58Length import monix.eval.Coeval @@ -27,12 +27,15 @@ case class TransferTransaction( timestamp: TxTimestamp, proofs: Proofs, chainId: Byte -) extends Transaction(TransactionType.Transfer, assetId match { - case Waves => Seq() - case a: IssuedAsset => Seq(a) - }) +) extends Transaction( + TransactionType.Transfer, + assetId match { + case Waves => Seq() + case a: IssuedAsset => Seq(a) + } + ) with TransferTransactionLike - with VersionedTransaction + with Versioned.ToV3 with FastHashId with SigProofsSwitch with TxWithFee.InCustomAsset @@ -57,8 +60,7 @@ object TransferTransaction extends TransactionParser { val MaxAttachmentSize = 140 val MaxAttachmentStringSize: Int = base58Length(MaxAttachmentSize) - val typeId: TxType = 4: Byte - val supportedVersions: Set[TxVersion] = Set(1, 2, 3) + val typeId: TxType = 4: Byte implicit val validator: TxValidator[TransferTransaction] = TransferTxValidator diff --git a/node/src/main/scala/com/wavesplatform/transaction/validation/TxConstraints.scala b/node/src/main/scala/com/wavesplatform/transaction/validation/TxConstraints.scala index f6fea245ae..406605bb97 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/validation/TxConstraints.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/validation/TxConstraints.scala @@ -10,7 +10,7 @@ import com.wavesplatform.lang.ValidationError import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.transaction.transfer.TransferTransaction -import com.wavesplatform.transaction.{Asset, TxValidationError, TxVersion, VersionedTransaction} +import com.wavesplatform.transaction.{Asset, TxValidationError, TxVersion, Versioned} import scala.util.Try @@ -29,14 +29,14 @@ object TxConstraints { if (cond) Valid(()) else Invalid(err).toValidatedNel - def byVersionSet[T <: VersionedTransaction](tx: T)(f: (Set[TxVersion], () => ValidatedV[Any])*): ValidatedV[T] = { + def byVersionSet[T <: Versioned](tx: T)(f: (Set[TxVersion], () => ValidatedV[Any])*): ValidatedV[T] = { seq(tx)(f.collect { case (v, func) if v.contains(tx.version) => func() }*) } - def byVersion[T <: VersionedTransaction](tx: T)(f: (TxVersion, () => ValidatedV[Any])*): ValidatedV[T] = + def byVersion[T <: Versioned](tx: T)(f: (TxVersion, () => ValidatedV[Any])*): ValidatedV[T] = byVersionSet(tx)(f.map { case (v, f) => (Set(v), f) }*) def fee(fee: Long): ValidatedV[Long] = { diff --git a/node/src/main/scala/com/wavesplatform/transaction/validation/impl/ExchangeTxValidator.scala b/node/src/main/scala/com/wavesplatform/transaction/validation/impl/ExchangeTxValidator.scala index 51bf02b562..ffd8a212b6 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/validation/impl/ExchangeTxValidator.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/validation/impl/ExchangeTxValidator.scala @@ -3,19 +3,19 @@ package com.wavesplatform.transaction.validation.impl import cats.data.ValidatedNel import com.wavesplatform.lang.ValidationError import com.wavesplatform.transaction.TxValidationError.{GenericError, OrderValidationError} -import com.wavesplatform.transaction.TxVersion import com.wavesplatform.transaction.assets.exchange.{ExchangeTransaction, Order, OrderType} import com.wavesplatform.transaction.validation.TxValidator +import com.wavesplatform.transaction.{PBSince, TxVersion} object ExchangeTxValidator extends TxValidator[ExchangeTransaction] { override def validate(tx: ExchangeTransaction): ValidatedNel[ValidationError, ExchangeTransaction] = { - import tx._ + import tx.* V.seq(tx)( V.cond(sellMatcherFee <= Order.MaxAmount, GenericError("sellMatcherFee too large")), V.cond(buyMatcherFee <= Order.MaxAmount, GenericError("buyMatcherFee too large")), V.cond(fee.value <= Order.MaxAmount, GenericError("fee too large")), - V.cond(isProtobufVersion || order1.orderType == OrderType.BUY, GenericError("order1 should have OrderType.BUY")), + V.cond(PBSince.affects(tx) || order1.orderType == OrderType.BUY, GenericError("order1 should have OrderType.BUY")), V.cond(buyOrder.orderType == OrderType.BUY, GenericError("buyOrder should has OrderType.BUY")), V.cond(sellOrder.orderType == OrderType.SELL, GenericError("sellOrder should has OrderType.SELL")), V.cond(buyOrder.matcherPublicKey == sellOrder.matcherPublicKey, GenericError("buyOrder.matcher should be the same as sellOrder.matcher")), diff --git a/node/src/main/scala/com/wavesplatform/transaction/validation/impl/InvokeScriptTxValidator.scala b/node/src/main/scala/com/wavesplatform/transaction/validation/impl/InvokeScriptTxValidator.scala index 0621ca97ac..333a5916bc 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/validation/impl/InvokeScriptTxValidator.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/validation/impl/InvokeScriptTxValidator.scala @@ -2,15 +2,16 @@ package com.wavesplatform.transaction.validation.impl import cats.data.NonEmptyList import cats.data.Validated.{Invalid, Valid} -import cats.syntax.either._ +import cats.syntax.either.* import com.wavesplatform.lang.v1.compiler.Terms.FUNCTION_CALL import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader} import com.wavesplatform.protobuf.transaction.PBTransactions +import com.wavesplatform.transaction.PBSince import com.wavesplatform.transaction.TxValidationError.{GenericError, NonPositiveAmount} import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.validation.{TxValidator, ValidatedNV, ValidatedV} -import com.wavesplatform.utils._ +import com.wavesplatform.utils.* import scala.util.Try @@ -27,7 +28,7 @@ object InvokeScriptTxValidator extends TxValidator[InvokeScriptTransaction] { def checkLength: Either[GenericError, Unit] = { val length = - if (tx.isProtobufVersion) + if (PBSince.affects(tx)) PBTransactions.toPBInvokeScriptData(tx.dApp, tx.funcCallOpt, tx.payments).toByteArray.length else tx.bytes().length diff --git a/node/src/main/scala/com/wavesplatform/transaction/validation/impl/LeaseTxValidator.scala b/node/src/main/scala/com/wavesplatform/transaction/validation/impl/LeaseTxValidator.scala index 7c21869f2a..c1f5548f4f 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/validation/impl/LeaseTxValidator.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/validation/impl/LeaseTxValidator.scala @@ -4,11 +4,11 @@ import cats.data.ValidatedNel import com.wavesplatform.lang.ValidationError import com.wavesplatform.transaction.lease.LeaseTransaction import com.wavesplatform.transaction.validation.TxValidator -import com.wavesplatform.transaction.TxValidationError +import com.wavesplatform.transaction.{TxPositiveAmount, TxValidationError} object LeaseTxValidator extends TxValidator[LeaseTransaction] { override def validate(tx: LeaseTransaction): ValidatedNel[ValidationError, LeaseTransaction] = { - import tx._ + import tx.* V.seq(tx)( V.noOverflow(amount.value, fee.value), V.cond(sender.toAddress != recipient, TxValidationError.ToSelf), @@ -16,6 +16,6 @@ object LeaseTxValidator extends TxValidator[LeaseTransaction] { ) } - def validateAmount(amount: Long) = - Either.cond(amount > 0, (), TxValidationError.NonPositiveAmount(amount, "waves")) + def validateAmount(amount: Long): Either[ValidationError, TxPositiveAmount] = + TxPositiveAmount.from(amount).left.map[ValidationError](_ => TxValidationError.NonPositiveAmount(amount, "waves")) } diff --git a/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala b/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala index c8b1cecde7..23adf485ff 100644 --- a/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala +++ b/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala @@ -15,7 +15,7 @@ import com.wavesplatform.state.TxStateSnapshotHashBuilder.TxStatusInfo import com.wavesplatform.state.diffs.BlockDiffer.CurrentBlockFeePart import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationError import com.wavesplatform.state.diffs.{BlockDiffer, TransactionDiffer} -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.state.{Blockchain, Portfolio, StateSnapshot, TxStateSnapshotHashBuilder} import com.wavesplatform.transaction.* import com.wavesplatform.transaction.TxValidationError.{AlreadyInTheState, GenericError, SenderIsBlacklisted, WithLog} diff --git a/node/src/main/scala/com/wavesplatform/utx/UtxPriorityPool.scala b/node/src/main/scala/com/wavesplatform/utx/UtxPriorityPool.scala index fd4a77d208..ec1000cee0 100644 --- a/node/src/main/scala/com/wavesplatform/utx/UtxPriorityPool.scala +++ b/node/src/main/scala/com/wavesplatform/utx/UtxPriorityPool.scala @@ -3,7 +3,7 @@ package com.wavesplatform.utx import cats.implicits.toFoldableOps import com.wavesplatform.ResponsivenessLogs import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.state.{Blockchain, StateSnapshot} import com.wavesplatform.transaction.Transaction import com.wavesplatform.utils.{OptimisticLockable, ScorexLogging} diff --git a/node/src/test/scala/com/wavesplatform/BlockchainStubHelpers.scala b/node/src/test/scala/com/wavesplatform/BlockchainStubHelpers.scala index 55ff44fe6b..bb990d1d03 100644 --- a/node/src/test/scala/com/wavesplatform/BlockchainStubHelpers.scala +++ b/node/src/test/scala/com/wavesplatform/BlockchainStubHelpers.scala @@ -6,7 +6,6 @@ import com.wavesplatform.block.SignedBlockHeader import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.* import com.wavesplatform.features.{BlockchainFeature, BlockchainFeatures} -import com.wavesplatform.history.SnapshotOps.* import com.wavesplatform.lagonaki.mocks.TestBlock import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.script.Script @@ -148,12 +147,9 @@ trait BlockchainStubHelpers { self: MockFactoryBase => ) } - def transactionDiffer(time: Time = SystemTime, withFailed: Boolean = false)(tx: Transaction): TracedResult[ValidationError, Diff] = { - val snapshot = - if (withFailed) TransactionDiffer(Some(time.correctedTime()), time.correctedTime())(blockchain, tx) - else TransactionDiffer.forceValidate(Some(time.correctedTime()), time.correctedTime())(blockchain, tx) - snapshot.map(_.toDiff(blockchain)) - } + def transactionDiffer(time: Time = SystemTime, withFailed: Boolean = false)(tx: Transaction): TracedResult[ValidationError, StateSnapshot] = + if (withFailed) TransactionDiffer(Some(time.correctedTime()), time.correctedTime())(blockchain, tx) + else TransactionDiffer.forceValidate(Some(time.correctedTime()), time.correctedTime())(blockchain, tx) def transactionPublisher(time: Time = SystemTime): TransactionPublisher = (tx: Transaction, _: Option[Channel]) => { val differ = transactionDiffer(time) _ diff --git a/node/src/test/scala/com/wavesplatform/RxScheduler.scala b/node/src/test/scala/com/wavesplatform/RxScheduler.scala index efbc6c0ca7..af54b50af8 100644 --- a/node/src/test/scala/com/wavesplatform/RxScheduler.scala +++ b/node/src/test/scala/com/wavesplatform/RxScheduler.scala @@ -24,10 +24,10 @@ trait RxScheduler extends BeforeAndAfterAll { _: Suite => def test[A](f: => Future[A]): A = Await.result(f, 10.seconds) - def send[A](p: Observer[A])(a: A): Future[Ack] = + def send[A](p: Observer[A], timeout: Int = 500)(a: A): Future[Ack] = p.onNext(a) .map(ack => { - Thread.sleep(500) + Thread.sleep(timeout) ack }) diff --git a/node/src/test/scala/com/wavesplatform/TestValues.scala b/node/src/test/scala/com/wavesplatform/TestValues.scala index 6623c6b0e2..e25d435d1e 100644 --- a/node/src/test/scala/com/wavesplatform/TestValues.scala +++ b/node/src/test/scala/com/wavesplatform/TestValues.scala @@ -23,7 +23,7 @@ object TestValues { def invokeFee(scripts: Int = 0, issues: Int = 0): Long = invokeFee + scripts * ScriptExtraFee + issues * FeeConstants(TransactionType.Issue) * FeeUnit - val (script, scriptComplexity) = ScriptCompiler + lazy val (script, scriptComplexity) = ScriptCompiler .compile( """ |{-# STDLIB_VERSION 2 #-} @@ -35,7 +35,7 @@ object TestValues { ) .explicitGet() - val (assetScript, assetScriptComplexity) = ScriptCompiler + lazy val (assetScript, assetScriptComplexity) = ScriptCompiler .compile( """ |{-# STDLIB_VERSION 2 #-} @@ -47,7 +47,7 @@ object TestValues { ) .explicitGet() - val (rejectAssetScript, rejectAssetScriptComplexity) = ScriptCompiler + lazy val (rejectAssetScript, rejectAssetScriptComplexity) = ScriptCompiler .compile( """ |{-# STDLIB_VERSION 2 #-} diff --git a/node/src/test/scala/com/wavesplatform/WithNewDBForEachTest.scala b/node/src/test/scala/com/wavesplatform/WithNewDBForEachTest.scala index 8bd5f4ac88..beb28b0702 100644 --- a/node/src/test/scala/com/wavesplatform/WithNewDBForEachTest.scala +++ b/node/src/test/scala/com/wavesplatform/WithNewDBForEachTest.scala @@ -10,7 +10,7 @@ import org.scalatest.{BeforeAndAfterEach, Suite} trait WithNewDBForEachTest extends BeforeAndAfterEach with DBCacheSettings { this: Suite => - private val path = Files.createTempDirectory("rocks").toAbsolutePath + private val path = Files.createTempDirectory(s"rocks-${getClass.getSimpleName}").toAbsolutePath private var currentDBInstance: RDB = _ protected val ignoreBlockchainUpdateTriggers: BlockchainUpdateTriggers = BlockchainUpdateTriggers.noop diff --git a/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala b/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala index f266896a79..a8a48c170f 100644 --- a/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala +++ b/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala @@ -5,24 +5,14 @@ import akka.http.scaladsl.model.MediaTypes.`application/json` import akka.http.scaladsl.model.headers.Accept import akka.http.scaladsl.server.Route import akka.http.scaladsl.testkit.ScalatestRouteTest -import com.wavesplatform.api.common.{CommonAccountsApi, CommonAssetsApi, CommonTransactionsApi, TransactionMeta} import com.wavesplatform.api.http.assets.AssetsApiRoute -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.history.DefaultBlockchainSettings -import com.wavesplatform.http.{ApiErrorMatchers, RestAPISettingsHelper} -import com.wavesplatform.network.TransactionPublisher -import com.wavesplatform.state.TxMeta.Status -import com.wavesplatform.state.reader.{LeaseDetails, SnapshotBlockchain} -import com.wavesplatform.state.{Blockchain, Height} -import com.wavesplatform.test.PropSpec -import com.wavesplatform.transaction.Asset -import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.db.WithState.AddrWithBalance +import com.wavesplatform.http.{ApiErrorMatchers, DummyTransactionPublisher, RestAPISettingsHelper} +import com.wavesplatform.settings.WavesSettings +import com.wavesplatform.test.* +import com.wavesplatform.transaction.TxHelpers import com.wavesplatform.utils.SharedSchedulerMixin -import com.wavesplatform.utx.UtxPool -import com.wavesplatform.{NTPTime, TestWallet} import org.scalactic.source.Position -import org.scalamock.scalatest.PathMockFactory import play.api.libs.json.* import scala.concurrent.duration.DurationInt @@ -31,24 +21,17 @@ import scala.reflect.ClassTag class CustomJsonMarshallerSpec extends PropSpec with RestAPISettingsHelper - with PathMockFactory - with TestWallet - with NTPTime with ScalatestRouteTest with ApiErrorMatchers with ApiMarshallers - with SharedSchedulerMixin { - private val blockchain = mock[Blockchain] - private val utx = mock[UtxPool] - private val publisher = mock[TransactionPublisher] - private val transactionsApi = mock[CommonTransactionsApi] - private val accountsApi = mock[CommonAccountsApi] - private val assetsApi = mock[CommonAssetsApi] + with SharedDomain + with SharedSchedulerMixin { private val numberFormat = Accept(`application/json`.withParams(Map("large-significand-format" -> "string"))) + private val richAccount = TxHelpers.signer(55) - (() => blockchain.activatedFeatures).expects().returning(BlockchainFeatures.implemented.map(_ -> 0).toMap).anyNumberOfTimes() - (() => blockchain.settings).expects().returning(DefaultBlockchainSettings).anyNumberOfTimes() + override def genesisBalances: Seq[AddrWithBalance] = Seq(AddrWithBalance(richAccount.toAddress, 50000.waves)) + override def settings: WavesSettings = DomainPresets.BlockRewardDistribution private def ensureFieldsAre[A: ClassTag](v: JsObject, fields: String*)(implicit pos: Position): Unit = for (f <- fields) (v \ f).get shouldBe a[A] @@ -66,47 +49,32 @@ class CustomJsonMarshallerSpec private val transactionsRoute = TransactionsApiRoute( restAPISettings, - transactionsApi, - testWallet, - blockchain, - mock[() => SnapshotBlockchain], - () => utx.size, - publisher, + domain.transactionsApi, + domain.wallet, + domain.blockchain, + () => domain.blockchain, + () => domain.utxPool.size, + DummyTransactionPublisher.accepting, ntpTime, new RouteTimeout(60.seconds)(sharedScheduler) ).route property("/transactions/info/{id}") { - forAll(leaseGen) { lt => - val height: Height = Height(1) - (transactionsApi.transactionById _).expects(lt.id()).returning(Some(TransactionMeta.Default(height, lt, Status.Succeeded, 0L))).twice() - (blockchain.leaseDetails _) - .expects(lt.id()) - .returning(Some(LeaseDetails(lt.sender, lt.recipient, lt.amount.value, LeaseDetails.Status.Active, lt.id(), 1))) - .twice() - checkRoute(Get(s"/transactions/info/${lt.id()}"), transactionsRoute, "amount") - } + // todo: add other transaction types + val leaseTx = TxHelpers.lease(sender = richAccount, TxHelpers.address(80), 25.waves) + domain.appendBlock(leaseTx) + checkRoute(Get(s"/transactions/info/${leaseTx.id()}"), transactionsRoute, "amount") } property("/transactions/calculateFee") { - (() => blockchain.height).expects().returning(1000).anyNumberOfTimes() - (blockchain.assetScript _).expects(*).returning(None).anyNumberOfTimes() - - forAll(transferV2Gen) { tx => - (transactionsApi.calculateFee _).expects(*).returning(Right((Asset.Waves, 1, 1))).twice() - checkRoute(Post("/transactions/calculateFee", tx.json()), transactionsRoute, "feeAmount") - } + val tx = TxHelpers.transfer(richAccount, TxHelpers.address(81), 5.waves) + checkRoute(Post("/transactions/calculateFee", tx.json()), transactionsRoute, "feeAmount") } - private val rewardRoute = RewardApiRoute(blockchain).route + private val rewardRoute = RewardApiRoute(domain.blockchain).route property("/blockchain/rewards") { - (() => blockchain.height).expects().returning(1000).anyNumberOfTimes() - (blockchain.blockReward _).expects(*).returning(Some(1000)).twice() - (blockchain.wavesAmount _).expects(*).returning(BigInt(10000000)).twice() - (blockchain.blockRewardVotes _).expects(1000).returning(Seq(100L)).twice() - - checkRoute(Get("/blockchain/rewards/1000"), rewardRoute, "totalWavesAmount", "currentReward", "minIncrement") + checkRoute(Get("/blockchain/rewards/2"), rewardRoute, "totalWavesAmount", "currentReward", "minIncrement") } property("/debug/stateWaves") { @@ -115,13 +83,13 @@ class CustomJsonMarshallerSpec private val assetsRoute = AssetsApiRoute( restAPISettings, - testWallet, - publisher, - blockchain, - mock[() => SnapshotBlockchain], + 60.seconds, + domain.wallet, + domain.blockchain, + () => domain.blockchain, ntpTime, - accountsApi, - assetsApi, + domain.accountsApi, + domain.assetsApi, 1000, new RouteTimeout(60.seconds)(sharedScheduler) ).route @@ -131,9 +99,8 @@ class CustomJsonMarshallerSpec } property("/assets/balance/{address}/{assetId}") { - forAll(accountGen, bytes32gen.map(b => IssuedAsset(ByteStr(b)))) { case (keyPair, assetId) => - (blockchain.balance _).expects(keyPair.toAddress, assetId).returning(1000L).twice() - checkRoute(Get(s"/assets/balance/${keyPair.publicKey.toAddress}/${assetId.id}"), assetsRoute, "balance") - } + val issue = TxHelpers.issue(richAccount, 100000_00, 2.toByte) + domain.appendBlock(issue) + checkRoute(Get(s"/assets/balance/${richAccount.toAddress}/${issue.id()}"), assetsRoute, "balance") } } diff --git a/node/src/test/scala/com/wavesplatform/api/http/requests/RequestsSpec.scala b/node/src/test/scala/com/wavesplatform/api/http/requests/RequestsSpec.scala index b1d3fbb1ff..aa4ea164ee 100644 --- a/node/src/test/scala/com/wavesplatform/api/http/requests/RequestsSpec.scala +++ b/node/src/test/scala/com/wavesplatform/api/http/requests/RequestsSpec.scala @@ -3,10 +3,9 @@ package com.wavesplatform.api.http.requests import com.wavesplatform.account.KeyPair import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.test.FreeSpec -import com.wavesplatform.transaction.transfer.TransferTransaction import org.scalacheck.Gen import org.scalatest.OptionValues -import play.api.libs.json._ +import play.api.libs.json.* class RequestsSpec extends FreeSpec with OptionValues { private def transferRequestGen(version: Int): Gen[(KeyPair, JsObject)] = @@ -33,7 +32,7 @@ class RequestsSpec extends FreeSpec with OptionValues { "TransferRequest" - { "accepts proofs for version >= 2" in { - TransferTransaction.supportedVersions.filter(_ >= 2).foreach { version => + Seq(2, 3).foreach { version => forAll(transferRequestGen(version)) { case (sender, json) => val request = json.as[TransferRequest] diff --git a/node/src/test/scala/com/wavesplatform/consensus/FPPoSSelectorTest.scala b/node/src/test/scala/com/wavesplatform/consensus/FPPoSSelectorTest.scala index 3b536e78fa..543e8faa19 100644 --- a/node/src/test/scala/com/wavesplatform/consensus/FPPoSSelectorTest.scala +++ b/node/src/test/scala/com/wavesplatform/consensus/FPPoSSelectorTest.scala @@ -282,7 +282,7 @@ class FPPoSSelectorTest extends FreeSpec with WithNewDBForEachTest with DBCacheS val settings0 = WavesSettings.fromRootConfig(loadConfig(ConfigFactory.load())) val settings = settings0.copy(featuresSettings = settings0.featuresSettings.copy(autoShutdownOnUnsupportedFeature = false)) val bcu = - new BlockchainUpdaterImpl(defaultWriter, settings, ntpTime, ignoreBlockchainUpdateTriggers, (_, _) => Seq.empty) + new BlockchainUpdaterImpl(defaultWriter, settings, ntpTime, ignoreBlockchainUpdateTriggers, (_, _) => Map.empty) val pos = PoSSelector(bcu, settings.synchronizationSettings.maxBaseTarget) try { val (accounts, blocks) = gen(ntpTime).sample.get @@ -295,7 +295,7 @@ class FPPoSSelectorTest extends FreeSpec with WithNewDBForEachTest with DBCacheS bcu.shutdown() } finally { bcu.shutdown() - db.close() + rdb.close() TestHelpers.deleteRecursively(path) } } diff --git a/node/src/test/scala/com/wavesplatform/db/ScriptCacheTest.scala b/node/src/test/scala/com/wavesplatform/db/ScriptCacheTest.scala index f37a7bfa9a..e1d08d1d7a 100644 --- a/node/src/test/scala/com/wavesplatform/db/ScriptCacheTest.scala +++ b/node/src/test/scala/com/wavesplatform/db/ScriptCacheTest.scala @@ -138,7 +138,7 @@ class ScriptCacheTest extends FreeSpec with WithNewDBForEachTest { TestFunctionalitySettings.Stub ) val bcu = - new BlockchainUpdaterImpl(defaultWriter, settings, ntpTime, ignoreBlockchainUpdateTriggers, (_, _) => Seq.empty) + new BlockchainUpdaterImpl(defaultWriter, settings, ntpTime, ignoreBlockchainUpdateTriggers, (_, _) => Map.empty) try { val (accounts, blocks) = gen(ntpTime).sample.get diff --git a/node/src/test/scala/com/wavesplatform/db/WithState.scala b/node/src/test/scala/com/wavesplatform/db/WithState.scala index 42bba1cb50..66532bd141 100644 --- a/node/src/test/scala/com/wavesplatform/db/WithState.scala +++ b/node/src/test/scala/com/wavesplatform/db/WithState.scala @@ -12,8 +12,7 @@ import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.events.BlockchainUpdateTriggers import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.features.BlockchainFeatures.LightNode -import com.wavesplatform.history.SnapshotOps.TransactionStateSnapshotExt -import com.wavesplatform.history.{Domain, SnapshotOps} +import com.wavesplatform.history.Domain import com.wavesplatform.lagonaki.mocks.TestBlock import com.wavesplatform.lagonaki.mocks.TestBlock.BlockWithSigner import com.wavesplatform.lang.ValidationError @@ -22,10 +21,12 @@ 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.reader.SnapshotBlockchain +import com.wavesplatform.state.SnapshotBlockchain import com.wavesplatform.state.utils.TestRocksDB -import com.wavesplatform.state.{Blockchain, BlockchainUpdaterImpl, Diff, NgState, Portfolio, StateSnapshot, TxStateSnapshotHashBuilder} +import com.wavesplatform.state.{Blockchain, BlockchainUpdaterImpl, NgState, 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} @@ -39,14 +40,14 @@ import scala.concurrent.duration.* trait WithState extends BeforeAndAfterAll with DBCacheSettings with Matchers with NTPTime { _: Suite => protected val ignoreBlockchainUpdateTriggers: BlockchainUpdateTriggers = BlockchainUpdateTriggers.noop - private val path = Files.createTempDirectory("rocks-temp").toAbsolutePath + 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("rocks-temp").toAbsolutePath + val path = Files.createTempDirectory(s"rocks-temp-tmp-${getClass.getSimpleName}").toAbsolutePath val rdb = RDB.open(dbSettings.copy(directory = path.toAbsolutePath.toString)) try { f(rdb) @@ -112,7 +113,7 @@ trait WithState extends BeforeAndAfterAll with DBCacheSettings with Matchers wit fs: FunctionalitySettings = TFS.Enabled, enableExecutionLog: Boolean = false )( - assertion: Either[ValidationError, Diff] => Unit + assertion: Either[ValidationError, StateSnapshot] => Unit ): Unit = withTestState(fs) { (bcu, state) => assertDiffEi(preconditions, block, bcu, state, enableExecutionLog)(assertion) } @@ -124,7 +125,7 @@ trait WithState extends BeforeAndAfterAll with DBCacheSettings with Matchers wit state: RocksDBWriter, enableExecutionLog: Boolean )( - assertion: Either[ValidationError, Diff] => Unit + assertion: Either[ValidationError, StateSnapshot] => Unit ): Unit = { def differ(blockchain: Blockchain, b: Block) = BlockDiffer.fromBlock( @@ -150,8 +151,11 @@ trait WithState extends BeforeAndAfterAll with DBCacheSettings with Matchers wit preconditionBlock ) } - val totalDiff1 = blockWithComputedStateHash(block.block, block.signer, bcu).resultE.flatMap(differ(state, _)) - assertion(totalDiff1.map(_.snapshot.toDiff(state))) + val snapshot = + blockWithComputedStateHash(block.block, block.signer, bcu).resultE + .flatMap(differ(state, _)) + .map(_.snapshot) + assertion(snapshot) } def assertDiffEiTraced( @@ -160,7 +164,7 @@ trait WithState extends BeforeAndAfterAll with DBCacheSettings with Matchers wit fs: FunctionalitySettings = TFS.Enabled, enableExecutionLog: Boolean = false )( - assertion: TracedResult[ValidationError, Diff] => Unit + assertion: TracedResult[ValidationError, StateSnapshot] => Unit ): Unit = withTestState(fs) { (bcu, state) => def getCompBlockchain(blockchain: Blockchain) = { val reward = if (blockchain.height > 0) bcu.computeNextReward else None @@ -196,17 +200,17 @@ trait WithState extends BeforeAndAfterAll with DBCacheSettings with Matchers wit ) } - val totalDiff1 = + 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(totalDiff1.map(_.snapshot.toDiff(state))) + assertion(snapshot1.map(_.snapshot)) } private def assertDiffAndState(preconditions: Seq[BlockWithSigner], block: BlockWithSigner, fs: FunctionalitySettings, withNg: Boolean)( - assertion: (Diff, Blockchain) => Unit + assertion: (StateSnapshot, Blockchain) => Unit ): Unit = withTestState(fs) { (bcu, state) => def getCompBlockchain(blockchain: Blockchain) = if (withNg && fs.preActivatedFeatures.get(BlockchainFeatures.BlockReward.id).exists(_ <= blockchain.height)) { @@ -226,8 +230,8 @@ trait WithState extends BeforeAndAfterAll with DBCacheSettings with Matchers wit preconditions.foldLeft[Option[Block]](None) { (prevBlock, curBlock) => val preconditionBlock = blockWithComputedStateHash(curBlock.block, curBlock.signer, bcu).resultE.explicitGet() - val BlockDiffer.Result(diff, fees, totalFee, _, _, computedStateHash) = differ(state, prevBlock, preconditionBlock).explicitGet() - state.append(diff, fees, totalFee, None, preconditionBlock.header.generationSignature, computedStateHash, preconditionBlock) + 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) } @@ -245,22 +249,19 @@ trait WithState extends BeforeAndAfterAll with DBCacheSettings with Matchers wit checkedBlock.header.generationSignature, Map() ) - val cb = SnapshotBlockchain(state, ngState) - val diff = snapshot.toDiff(state) - assertion(diff, cb) - + val cb = SnapshotBlockchain(state, ngState) + assertion(snapshot, cb) state.append(snapshot, fees, totalFee, None, checkedBlock.header.generationSignature, computedStateHash, checkedBlock) - - assertion(diff, state) + assertion(snapshot, state) } def assertNgDiffState(preconditions: Seq[BlockWithSigner], block: BlockWithSigner, fs: FunctionalitySettings = TFS.Enabled)( - assertion: (Diff, Blockchain) => Unit + assertion: (StateSnapshot, Blockchain) => Unit ): Unit = assertDiffAndState(preconditions, block, fs, withNg = true)(assertion) def assertDiffAndState(preconditions: Seq[BlockWithSigner], block: BlockWithSigner, fs: FunctionalitySettings = TFS.Enabled)( - assertion: (Diff, Blockchain) => Unit + assertion: (StateSnapshot, Blockchain) => Unit ): Unit = assertDiffAndState(preconditions, block, fs, withNg = false)(assertion) @@ -288,9 +289,8 @@ trait WithState extends BeforeAndAfterAll with DBCacheSettings with Matchers wit val checkedBlock = blockWithComputedStateHash(block.block, block.signer, bcu).resultE.explicitGet() differ(state, checkedBlock).map { result => - val snapshot = SnapshotOps.fromDiff(result.snapshot.toDiff(state), state).explicitGet() state.append( - snapshot, + result.snapshot, result.carry, result.totalFee, None, @@ -302,11 +302,15 @@ trait WithState extends BeforeAndAfterAll with DBCacheSettings with Matchers wit }) } - def assertBalanceInvariant(diff: Diff): Unit = { - val portfolioDiff = diff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - portfolioDiff.balance shouldBe 0 - portfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0 - all(portfolioDiff.assets.values) shouldBe 0 + 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 = diff --git a/node/src/test/scala/com/wavesplatform/features/RideV5LimitsChangeTest.scala b/node/src/test/scala/com/wavesplatform/features/RideV5LimitsChangeTest.scala index a8b6be7063..79c3dcc7fd 100644 --- a/node/src/test/scala/com/wavesplatform/features/RideV5LimitsChangeTest.scala +++ b/node/src/test/scala/com/wavesplatform/features/RideV5LimitsChangeTest.scala @@ -13,9 +13,8 @@ import com.wavesplatform.state.diffs.BlockDiffer import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.SettingsFromDefaultConfig import com.wavesplatform.transaction.TxHelpers -import org.scalamock.scalatest.PathMockFactory -class RideV5LimitsChangeTest extends FlatSpec with WithDomain with PathMockFactory { +class RideV5LimitsChangeTest extends FlatSpec with WithDomain { "Blockchain" should "reject block with >1kk complexity before SynchronousCalls activated" in { val contractSigner = TxHelpers.secondSigner val contractAddress = contractSigner.toAddress diff --git a/node/src/test/scala/com/wavesplatform/features/RideV6FailRejectTest.scala b/node/src/test/scala/com/wavesplatform/features/RideV6FailRejectTest.scala index 53d9a623e2..a3d1f9edf9 100644 --- a/node/src/test/scala/com/wavesplatform/features/RideV6FailRejectTest.scala +++ b/node/src/test/scala/com/wavesplatform/features/RideV6FailRejectTest.scala @@ -888,8 +888,8 @@ class RideV6FailRejectTest extends FreeSpec with WithDomain with OptionValues wi def failTxTest(invoke: Transaction): Unit = { val complexity = ContractLimits.FailFreeInvokeComplexity + 1 test(complexity) { d => - val diff = d.createDiffE(invoke).value - val (_, scriptResult) = diff.scriptResults.headOption.value + val snapshot = d.createDiffE(invoke).value + val (_, scriptResult) = snapshot.scriptResults.headOption.value scriptResult.error.value.text should include(testCase.rejectError) d.appendBlock(invoke) diff --git a/node/src/test/scala/com/wavesplatform/history/BlockRewardSpec.scala b/node/src/test/scala/com/wavesplatform/history/BlockRewardSpec.scala index 70a25d1eae..1ace799fb1 100644 --- a/node/src/test/scala/com/wavesplatform/history/BlockRewardSpec.scala +++ b/node/src/test/scala/com/wavesplatform/history/BlockRewardSpec.scala @@ -268,8 +268,8 @@ class BlockRewardSpec extends FreeSpec with WithDomain { "when NG state is empty" in forAll(ngEmptyScenario) { case (miner1, miner2, b2s, b3, m3s) => withDomain(rewardSettings) { d => b2s.foldLeft[Option[Block]](None) { (prevBlock, curBlock) => - val BlockDiffer.Result(diff, carryFee, totalFee, _, _, computedStateHash) = differ(d.rocksDBWriter, prevBlock, curBlock) - d.rocksDBWriter.append(diff, carryFee, totalFee, None, curBlock.header.generationSignature, computedStateHash, curBlock) + val BlockDiffer.Result(snapshot, carryFee, totalFee, _, _, computedStateHash) = differ(d.rocksDBWriter, prevBlock, curBlock) + d.rocksDBWriter.append(snapshot, carryFee, totalFee, None, curBlock.header.generationSignature, computedStateHash, curBlock) Some(curBlock) } diff --git a/node/src/test/scala/com/wavesplatform/history/Domain.scala b/node/src/test/scala/com/wavesplatform/history/Domain.scala index 8ddd0a031d..eacb987fda 100644 --- a/node/src/test/scala/com/wavesplatform/history/Domain.scala +++ b/node/src/test/scala/com/wavesplatform/history/Domain.scala @@ -15,7 +15,6 @@ import com.wavesplatform.database.{DBExt, Keys, RDB, RocksDBWriter} import com.wavesplatform.events.BlockchainUpdateTriggers import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.features.BlockchainFeatures.{BlockV5, LightNode, RideV6} -import com.wavesplatform.history.SnapshotOps.TransactionStateSnapshotExt import com.wavesplatform.lagonaki.mocks.TestBlock import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.script.Script @@ -26,7 +25,6 @@ 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.state.reader.SnapshotBlockchain import com.wavesplatform.test.TestTime import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} @@ -60,15 +58,14 @@ case class Domain(rdb: RDB, blockchainUpdater: BlockchainUpdaterImpl, rocksDBWri val posSelector: PoSSelector = PoSSelector(blockchainUpdater, None) - val transactionDiffer: Transaction => TracedResult[ValidationError, Diff] = - TransactionDiffer(blockchain.lastBlockTimestamp, System.currentTimeMillis())(blockchain, _).map(_.toDiff(blockchain)) + val transactionDiffer: Transaction => TracedResult[ValidationError, StateSnapshot] = + TransactionDiffer(blockchain.lastBlockTimestamp, System.currentTimeMillis())(blockchain, _) - val transactionDifferWithLog: Transaction => TracedResult[ValidationError, Diff] = + val transactionDifferWithLog: Transaction => TracedResult[ValidationError, StateSnapshot] = TransactionDiffer(blockchain.lastBlockTimestamp, System.currentTimeMillis(), enableExecutionLog = true)(blockchain, _) - .map(_.toDiff(blockchain)) - def createDiffE(tx: Transaction): Either[ValidationError, Diff] = transactionDiffer(tx).resultE - def createDiff(tx: Transaction): Diff = createDiffE(tx).explicitGet() + 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) @@ -123,7 +120,7 @@ case class Domain(rdb: RDB, blockchainUpdater: BlockchainUpdaterImpl, rocksDBWri def commonTransactionsApi(challenger: Option[BlockChallenger]): CommonTransactionsApi = CommonTransactionsApi( - blockchainUpdater.bestLiquidSnapshot.map(diff => Height(blockchainUpdater.height) -> diff), + blockchainUpdater.bestLiquidSnapshot.map(Height(blockchainUpdater.height) -> _), rdb, blockchain, utxPool, @@ -175,8 +172,8 @@ case class Domain(rdb: RDB, blockchainUpdater: BlockchainUpdaterImpl, rocksDBWri .getOrElse(TestBlock.create(Nil).block) } - def liquidDiff: Diff = - blockchainUpdater.bestLiquidSnapshot.orEmpty.toDiff(rocksDBWriter) + def liquidSnapshot: StateSnapshot = + blockchainUpdater.bestLiquidSnapshot.orEmpty def microBlocks: Vector[MicroBlock] = blockchain.microblockIds.reverseIterator.flatMap(blockchain.microBlock).to(Vector) @@ -211,7 +208,7 @@ case class Domain(rdb: RDB, blockchainUpdater: BlockchainUpdaterImpl, rocksDBWri AddressTransactions .allAddressTransactions( rdb, - blockchainUpdater.bestLiquidSnapshot.map(diff => Height(blockchainUpdater.height) -> diff), + blockchainUpdater.bestLiquidSnapshot.map(Height(blockchainUpdater.height) -> _), address, None, Set.empty, @@ -259,7 +256,7 @@ case class Domain(rdb: RDB, blockchainUpdater: BlockchainUpdaterImpl, rocksDBWri def appendAndAssertFailed(tx: Transaction, message: String): Block = { appendBlock(tx) assert(!blockchain.transactionSucceeded(tx.id()), s"should fail: $tx") - liquidDiff.errorMessage(tx.id()).get.text should include(message) + liquidSnapshot.errorMessage(tx.id()).get.text should include(message) lastBlock } diff --git a/node/src/test/scala/com/wavesplatform/history/SnapshotOps.scala b/node/src/test/scala/com/wavesplatform/history/SnapshotOps.scala deleted file mode 100644 index d1d6195c51..0000000000 --- a/node/src/test/scala/com/wavesplatform/history/SnapshotOps.scala +++ /dev/null @@ -1,108 +0,0 @@ -package com.wavesplatform.history - -import cats.data.Ior -import com.wavesplatform.account.Address -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.lang.ValidationError -import com.wavesplatform.protobuf.ByteStringExt -import com.wavesplatform.state.* -import com.wavesplatform.transaction.Asset.IssuedAsset - -import scala.collection.immutable.VectorMap - -object SnapshotOps { - implicit class TransactionStateSnapshotExt(val s: StateSnapshot) extends AnyVal { - def toDiff(blockchain: Blockchain): Diff = - Diff.withTransactions( - s.transactions.values.toVector, - portfolios(blockchain), - issuedAssets, - updatedAssets, - s.aliases, - orderFills(blockchain), - s.leaseStates, - s.accountScripts, - s.assetScripts.view.mapValues(Some(_)).toMap, - s.accountData, - s.sponsorships, - scriptsRun = 0, - s.scriptsComplexity, - s.scriptResults, - s.ethereumTransactionMeta - ) - - private def portfolios(blockchain: Blockchain): Map[Address, Portfolio] = - Portfolio.combine(balancePortfolios(blockchain), leasePortfolios(blockchain)).explicitGet() - - private def balancePortfolios(blockchain: Blockchain): Map[Address, Portfolio] = - s.balances - .foldLeft(Map[Address, Portfolio]()) { case (portfolios, ((address, asset), balance)) => - val balanceDiff = balance - blockchain.balance(address, asset) - if (balanceDiff != 0) { - val portfolio = Portfolio.build(asset, balanceDiff) - Portfolio.combine(portfolios, Map(address -> portfolio)).explicitGet() - } else - portfolios - } - - private def leasePortfolios(blockchain: Blockchain): Map[Address, Portfolio] = - s.leaseBalances - .map { case (address, current) => - val init = blockchain.leaseBalance(address) - address -> Portfolio(lease = LeaseBalance(in = current.in - init.in, out = current.out - init.out)) - } - - private def issuedAssets: VectorMap[IssuedAsset, NewAssetInfo] = - VectorMap[IssuedAsset, NewAssetInfo]() ++ s.assetStatics.map { case (asset, pbStatic) => - val static = AssetStaticInfo( - asset.id, - pbStatic.sourceTransactionId.toTxId, - pbStatic.issuerPublicKey.toPublicKey, - pbStatic.decimals, - pbStatic.nft - ) - asset -> NewAssetInfo(static, s.assetNamesAndDescriptions(asset), s.assetVolumes(asset)) - } - - private def updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]] = - (s.assetVolumes.keySet ++ s.assetNamesAndDescriptions.keySet) - .filterNot(issuedAssets.contains) - .map { asset => - val info = - (s.assetNamesAndDescriptions.get(asset), s.assetVolumes.get(asset)) match { - case (Some(dynamic), Some(volume)) => Ior.Both(dynamic, volume) - case (Some(dynamic), None) => Ior.Left(dynamic) - case (None, Some(volume)) => Ior.Right(volume) - case _ => ??? - } - asset -> info - } - .toMap - - private def orderFills(blockchain: Blockchain): Map[ByteStr, VolumeAndFee] = - s.orderFills.map { case (orderId, info) => - val init = blockchain.filledVolumeAndFee(orderId) - orderId -> VolumeAndFee(info.volume - init.volume, info.fee - init.fee) - } - } - - def fromDiff(diff: Diff, blockchain: Blockchain): Either[ValidationError, StateSnapshot] = - StateSnapshot.build( - blockchain, - diff.portfolios, - diff.orderFills, - diff.issuedAssets, - diff.updatedAssets, - diff.assetScripts.collect { case (asset, Some(info)) => (asset, info) }, - diff.sponsorship, - diff.leaseState, - diff.aliases, - diff.accountData, - diff.scripts, - diff.scriptResults, - diff.ethereumTransactionMeta, - diff.scriptsComplexity, - VectorMap() ++ diff.transactions.map(info => info.transaction.id() -> info).toMap - ) -} diff --git a/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala index a1e2a8ab0c..595ff39cb2 100644 --- a/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala @@ -57,8 +57,8 @@ class AssetsRouteSpec seal( AssetsApiRoute( restAPISettings, + 60.seconds, testWallet, - DummyTransactionPublisher.accepting, d.blockchain, () => d.blockchain.snapshotBlockchain, TestTime(), @@ -286,7 +286,7 @@ class AssetsRouteSpec checkDetails(route, issues(i), issues(i).id().toString, assetDesc.copy(sequenceInBlock = i)) } - d.appendBlock((7 to 10).map(issues): _*) + d.appendBlock((7 to 10).map(issues) *) (1 to 6).foreach { i => checkDetails(route, issues(i), issues(i).id().toString, assetDesc.copy(sequenceInBlock = i)) } diff --git a/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala index 52350e5698..56109d5002 100644 --- a/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala @@ -1,53 +1,39 @@ package com.wavesplatform.http -import java.util.concurrent.TimeUnit - import akka.http.scaladsl.model.{ContentTypes, HttpEntity, StatusCodes} import com.typesafe.config.ConfigObject -import com.wavesplatform.account.{Alias, KeyPair} -import com.wavesplatform.api.common.CommonTransactionsApi +import com.wavesplatform.* +import com.wavesplatform.account.KeyPair import com.wavesplatform.api.http.ApiError.ApiKeyNotValid import com.wavesplatform.api.http.DebugApiRoute.AccountMiningInfo -import com.wavesplatform.api.http.{DebugApiRoute, RouteTimeout, handleAllExceptions} +import com.wavesplatform.api.http.{DebugApiRoute, RouteTimeout} import com.wavesplatform.block.Block import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.common.utils.* -import com.wavesplatform.db.WithDomain import com.wavesplatform.db.WithState.AddrWithBalance -import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.history.Domain import com.wavesplatform.lagonaki.mocks.TestBlock -import com.wavesplatform.lang.directives.values.V6 -import com.wavesplatform.lang.script.v1.ExprScript -import com.wavesplatform.lang.v1.compiler.Terms.TRUE +import com.wavesplatform.lang.directives.values.{V4, V5, V6} import com.wavesplatform.lang.v1.compiler.TestCompiler -import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext import com.wavesplatform.lang.v1.traits.domain.Recipient.Address import com.wavesplatform.lang.v1.traits.domain.{Issue, Lease, Recipient} import com.wavesplatform.mining.{Miner, MinerDebugInfo} import com.wavesplatform.network.PeerDatabase -import com.wavesplatform.settings.{TestFunctionalitySettings, WalletSettings, WavesSettings} +import com.wavesplatform.settings.WavesSettings import com.wavesplatform.state.StateHash.SectionId -import com.wavesplatform.state.TxMeta.Status import com.wavesplatform.state.diffs.ENOUGH_AMT -import com.wavesplatform.state.reader.LeaseDetails -import com.wavesplatform.state.{AccountScriptInfo, AssetDescription, AssetScriptInfo, Blockchain, Height, NG, StateHash, TxMeta} +import com.wavesplatform.state.{Blockchain, StateHash} import com.wavesplatform.test.* import com.wavesplatform.transaction.TxHelpers.* import com.wavesplatform.transaction.assets.exchange.OrderType import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment -import com.wavesplatform.transaction.smart.script.ScriptCompiler -import com.wavesplatform.transaction.{ERC20Address, Transaction, TxHelpers, TxVersion} +import com.wavesplatform.transaction.{Transaction, TxHelpers, TxVersion} import com.wavesplatform.utils.SharedSchedulerMixin -import com.wavesplatform.wallet.Wallet -import com.wavesplatform.* import monix.eval.Task -import org.scalamock.scalatest.PathMockFactory -import org.scalatest.{Assertion, OptionValues} +import org.scalatest.OptionValues import play.api.libs.json.{JsArray, JsObject, JsValue, Json} +import java.util.concurrent.{ConcurrentHashMap, TimeUnit} import scala.concurrent.duration.* import scala.util.Random @@ -57,24 +43,18 @@ class DebugApiRouteSpec with RestAPISettingsHelper with TestWallet with NTPTime - with PathMockFactory - with BlockchainStubHelpers - with WithDomain + with SharedDomain with OptionValues with SharedSchedulerMixin { - import DomainPresets.* - val wavesSettings: WavesSettings = WavesSettings.default().copy(restAPISettings = restAPISettings) - val configObject: ConfigObject = wavesSettings.config.root() + override def settings: WavesSettings = DomainPresets.ContinuationTransaction.copy( + dbSettings = DomainPresets.ContinuationTransaction.dbSettings.copy(storeStateHashes = true), + restAPISettings = restAPISettings + ) + private val configObject: ConfigObject = settings.config.root() - trait Blockchain1 extends Blockchain with NG - val blockchain: Blockchain1 = stub[Blockchain1] - (blockchain.hasAccountScript _).when(*).returns(false) - (() => blockchain.microblockIds).when().returns(Seq.empty) - (blockchain.heightOf _).when(*).returns(None) - (() => blockchain.height).when().returns(0) - (blockchain.balanceSnapshots _).when(*, *, *).returns(Seq.empty) - (blockchain.effectiveBalanceBanHeights _).when(*).returns(Seq.empty) + private val richAccount = TxHelpers.signer(905) + override def genesisBalances: Seq[AddrWithBalance] = Seq(AddrWithBalance(richAccount.toAddress, 50_000.waves)) val miner: Miner & MinerDebugInfo = new Miner with MinerDebugInfo { override def scheduleMining(blockchain: Option[Blockchain]): Unit = () @@ -90,36 +70,33 @@ class DebugApiRouteSpec StateHash(randomHash, hashes) } - val wallet: Wallet = Wallet(WalletSettings(None, Some("password"), Some(ByteStr(TxHelpers.defaultSigner.seed)))) val debugApiRoute: DebugApiRoute = DebugApiRoute( - wavesSettings, + settings, ntpTime, - blockchain, - wallet, - null, - stub[CommonTransactionsApi], - null, + domain.blockchain, + domain.wallet, + domain.accountsApi, + domain.transactionsApi, + domain.assetsApi, PeerDatabase.NoOp, - null, + new ConcurrentHashMap(), (_, _) => Task.raiseError(new NotImplementedError("")), - null, + domain.utxPool, miner, null, null, null, null, configObject, - _ => Seq.empty, - { - case 2 => Some(testStateHash) - case _ => None - }, - () => Some(blockchain), + domain.rocksDBWriter.loadBalanceHistory, + domain.rocksDBWriter.loadStateHash, + () => Some(domain.blockchain), new RouteTimeout(60.seconds)(sharedScheduler), sharedScheduler ) - import debugApiRoute.* + + private val route = seal(debugApiRoute.route) routePath("/configInfo") - { "requires api-key header" in { @@ -129,38 +106,49 @@ class DebugApiRouteSpec } routePath("/balances/history/{address}") - { - val acc1 = TxHelpers.defaultSigner - val acc2 = TxHelpers.secondSigner + val acc1 = TxHelpers.signer(1001) + val acc2 = TxHelpers.signer(1002) - val initBalance = 5.waves + val initBalance = 10.waves - "works" in withDomain(balances = Seq(AddrWithBalance(acc2.toAddress, initBalance), AddrWithBalance(acc1.toAddress))) { d => + "works" in { val tx1 = TxHelpers.transfer(acc2, acc1.toAddress, 1.waves) val tx2 = TxHelpers.transfer(acc1, acc2.toAddress, 3.waves) val tx3 = TxHelpers.transfer(acc2, acc1.toAddress, 4.waves) val tx4 = TxHelpers.transfer(acc1, acc2.toAddress, 5.waves) - d.appendBlock(tx1) - d.appendBlock(tx2) - d.appendBlock() - d.appendBlock(tx3) - d.appendBlock(tx4) - d.appendBlock() + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq( + acc1.toAddress -> initBalance, + acc2.toAddress -> initBalance + ), + fee = 0.002.waves + ) + ) + val initialHeight = domain.blockchain.height + domain.appendBlock(tx1) + domain.appendBlock(tx2) + domain.appendBlock() + domain.appendBlock(tx3) + domain.appendBlock(tx4) + domain.appendBlock() val expectedBalance2 = initBalance - tx1.fee.value - tx1.amount.value val expectedBalance3 = expectedBalance2 + tx2.amount.value val expectedBalance5 = expectedBalance3 - tx3.fee.value - tx3.amount.value val expectedBalance6 = expectedBalance5 + tx4.amount.value - Get(routePath(s"/balances/history/${acc2.toAddress}")) ~> routeWithBlockchain(d) ~> check { + Get(routePath(s"/balances/history/${acc2.toAddress}")) ~> route ~> check { status shouldBe StatusCodes.OK responseAs[JsArray] shouldBe Json.toJson( Seq( - 6 -> expectedBalance6, - 5 -> expectedBalance5, - 3 -> expectedBalance3, - 2 -> expectedBalance2, - 1 -> initBalance + initialHeight + 5 -> expectedBalance6, + initialHeight + 4 -> expectedBalance5, + initialHeight + 2 -> expectedBalance3, + initialHeight + 1 -> expectedBalance2, + initialHeight + 0 -> initBalance ).map { case (height, balance) => Json.obj("height" -> height, "balance" -> balance) } @@ -171,37 +159,35 @@ class DebugApiRouteSpec routePath("/stateHash") - { "works" - { - val settingsWithStateHashes = DomainPresets.SettingsFromDefaultConfig.copy( - dbSettings = DomainPresets.SettingsFromDefaultConfig.dbSettings.copy(storeStateHashes = true) - ) - - "at nonexistent height" in withDomain(settingsWithStateHashes) { d => - d.appendBlock(TestBlock.create(Nil).block) - Get(routePath("/stateHash/2")) ~> routeWithBlockchain(d) ~> check { + "at nonexistent height" in { + Get(routePath(s"/stateHash/${domain.blockchain.height}")) ~> route ~> check { status shouldBe StatusCodes.NotFound } } - "at existing height" in expectStateHashAt2("2") - "last" in expectStateHashAt2("last") + "at existing height" in { + (0 until (3 - domain.blockchain.height).min(0)) foreach { _ => + domain.appendBlock() + } - def expectStateHashAt2(suffix: String): Assertion = withDomain(settingsWithStateHashes) { d => - val genesisBlock = TestBlock.create(Nil).block - d.appendBlock(genesisBlock) + val lastButOneHeight = domain.blockchain.height - 1 + val lastButOneHeader = domain.blockchain.blockHeader(lastButOneHeight).value + val lastButOneStateHash = domain.rocksDBWriter.loadStateHash(lastButOneHeight).value + val expectedResponse = Json.toJson(lastButOneStateHash).as[JsObject] ++ Json.obj( + "blockId" -> lastButOneHeader.id().toString, + "baseTarget" -> lastButOneHeader.header.baseTarget, + "height" -> lastButOneHeight, + "version" -> Version.VersionString + ) - val blockAt2 = TestBlock.create(0, genesisBlock.id(), Nil).block - d.appendBlock(blockAt2) - d.appendBlock(TestBlock.create(0, blockAt2.id(), Nil).block) + Get(routePath(s"/stateHash/last")) ~> route ~> check { + status shouldBe StatusCodes.OK + responseAs[JsObject] shouldBe expectedResponse + } - val stateHashAt2 = d.rocksDBWriter.loadStateHash(2).value - Get(routePath(s"/stateHash/$suffix")) ~> routeWithBlockchain(d) ~> check { + Get(routePath(s"/stateHash/$lastButOneHeight")) ~> route ~> check { status shouldBe StatusCodes.OK - responseAs[JsObject] shouldBe (Json.toJson(stateHashAt2).as[JsObject] ++ Json.obj( - "blockId" -> blockAt2.id().toString, - "baseTarget" -> blockAt2.header.baseTarget, - "height" -> 2, - "version" -> Version.VersionString - )) + responseAs[JsObject] shouldBe expectedResponse } } } @@ -211,11 +197,10 @@ class DebugApiRouteSpec def validatePost(tx: Transaction) = Post(routePath("/validate"), HttpEntity(ContentTypes.`application/json`, tx.json().toString())) - "takes the priority pool into account" in withDomain(balances = Seq(AddrWithBalance(TxHelpers.defaultAddress))) { d => - d.appendBlock(TxHelpers.transfer(to = TxHelpers.secondAddress, amount = 1.waves + TestValues.fee)) + "takes the priority pool into account" in { + domain.appendBlock(TxHelpers.transfer(to = TxHelpers.secondAddress, amount = 1.waves + TestValues.fee)) - val route = routeWithBlockchain(d.blockchain) - val tx = TxHelpers.transfer(TxHelpers.secondSigner, TestValues.address, 1.waves) + val tx = TxHelpers.transfer(TxHelpers.secondSigner, TestValues.address, 1.waves) validatePost(tx) ~> route ~> check { val json = responseAs[JsValue] (json \ "valid").as[Boolean] shouldBe true @@ -224,12 +209,6 @@ class DebugApiRouteSpec } "valid tx" in { - val blockchain = createBlockchainStub() - (blockchain.balance _).when(TxHelpers.defaultSigner.publicKey.toAddress, *).returns(Long.MaxValue) - (blockchain.wavesBalances _).when(Seq(TxHelpers.defaultAddress)).returns(Map(TxHelpers.defaultAddress -> Long.MaxValue)) - - val route = routeWithBlockchain(blockchain) - val tx = TxHelpers.transfer(TxHelpers.defaultSigner, TestValues.address, 1.waves) validatePost(tx) ~> route ~> check { val json = responseAs[JsValue] @@ -239,12 +218,6 @@ class DebugApiRouteSpec } "invalid tx" in { - val blockchain = createBlockchainStub() - (blockchain.balance _).when(TxHelpers.defaultSigner.publicKey.toAddress, *).returns(0) - (blockchain.wavesBalances _).when(Seq(TxHelpers.defaultAddress)).returns(Map(TxHelpers.defaultAddress -> 0)) - - val route = routeWithBlockchain(blockchain) - val tx = TxHelpers.transfer(TxHelpers.defaultSigner, TestValues.address, ENOUGH_AMT) validatePost(tx) ~> route ~> check { val json = responseAs[JsValue] @@ -255,159 +228,91 @@ class DebugApiRouteSpec } "NoSuchElementException" in { - val blockchain = createBlockchainStub { b => - (b.accountScript _).when(*).throws(new NoSuchElementException()) - } - val route = handleAllExceptions(routeWithBlockchain(blockchain)) validatePost(TxHelpers.invoke()) ~> route ~> check { - responseAs[ - String - ] shouldBe """{"error":0,"message":"Error is unknown"}""" - response.status shouldBe StatusCodes.InternalServerError + (responseAs[JsValue] \ "error").as[String] should include("No contract at address") + response.status shouldBe StatusCodes.OK } } "exchange tx with fail script" in { - val blockchain = createBlockchainStub { blockchain => - (blockchain.balance _).when(TxHelpers.defaultAddress, *).returns(Long.MaxValue) - (blockchain.wavesBalances _).when(Seq(TxHelpers.defaultAddress)).returns(Map(TxHelpers.defaultAddress -> Long.MaxValue)) - - val (assetScript, comp) = - ScriptCompiler.compile("if true then throw(\"error\") else false", ScriptEstimatorV3(fixOverflow = true, overhead = true)).explicitGet() - (blockchain.assetScript _).when(TestValues.asset).returns(Some(AssetScriptInfo(assetScript, comp))) - (blockchain.assetDescription _) - .when(TestValues.asset) - .returns( - Some( - AssetDescription( - null, - null, - null, - null, - 0, - reissuable = false, - null, - Height(1), - Some(AssetScriptInfo(assetScript, comp)), - 0, - nft = false, - 0, - Height(1) - ) - ) - ) - blockchain.stub.activateAllFeatures() - } + val assetIssuer = TxHelpers.signer(1021) + val o1sender = TxHelpers.signer(1022) + val o2sender = TxHelpers.signer(1023) + + val issue = TxHelpers.issue(assetIssuer, script = Some(TestCompiler(V5).compileExpression("true"))) + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq(o1sender.toAddress -> 1.waves, o2sender.toAddress -> 1.waves, assetIssuer.toAddress -> 3.waves), + fee = 0.003.waves + ), + issue, + TxHelpers.massTransfer(assetIssuer, Seq(o1sender.toAddress -> 50, o2sender.toAddress -> 50), issue.asset, fee = 0.006.waves), + TxHelpers.setAssetScript(assetIssuer, issue.asset, TestCompiler(V5).compileExpression("if true then throw(\"error\") else false")) + ) - val route = routeWithBlockchain(blockchain) - val tx = TxHelpers.exchangeFromOrders(TxHelpers.orderV3(OrderType.BUY, TestValues.asset), TxHelpers.orderV3(OrderType.SELL, TestValues.asset)) - jsonPost(routePath("/validate"), tx.json()) ~> route ~> check { + jsonPost( + routePath("/validate"), + TxHelpers.exchangeFromOrders(TxHelpers.orderV3(OrderType.BUY, issue.asset), TxHelpers.orderV3(OrderType.SELL, issue.asset)).json() + ) ~> route ~> check { val json = responseAs[JsValue] (json \ "valid").as[Boolean] shouldBe false (json \ "validationTime").as[Int] shouldBe 1000 +- 1000 (json \ "error").as[String] should include("not allowed by script of the asset") (json \ "trace").as[JsArray] shouldBe Json.parse( - "[{\"type\":\"asset\",\"context\":\"orderAmount\",\"id\":\"5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx\",\"result\":\"failure\",\"vars\":[{\"name\":\"throw.@args\",\"type\":\"Array\",\"value\":[{\"type\":\"String\",\"value\":\"error\"}]},{\"name\":\"throw.@complexity\",\"type\":\"Int\",\"value\":1},{\"name\":\"@complexityLimit\",\"type\":\"Int\",\"value\":2147483646}],\"error\":\"error\"}]" + s"[{\"type\":\"asset\",\"context\":\"orderAmount\",\"id\":\"${issue.id()}\",\"result\":\"failure\",\"vars\":[{\"name\":\"throw.@args\",\"type\":\"Array\",\"value\":[{\"type\":\"String\",\"value\":\"error\"}]},{\"name\":\"throw.@complexity\",\"type\":\"Int\",\"value\":1},{\"name\":\"@complexityLimit\",\"type\":\"Int\",\"value\":2147483646}],\"error\":\"error\"}]" ) } } "invoke tx with asset failing" in { - val blockchain = createBlockchainStub { blockchain => - (blockchain.balance _).when(*, *).returns(Long.MaxValue / 2) - (blockchain.wavesBalances _).when(*).returns(Map(TxHelpers.defaultAddress -> Long.MaxValue / 2)) - - val (assetScript, assetScriptComplexity) = ScriptCompiler - .compile( - "let test = true\n" + - "if test then throw(\"error\") else !test", - ScriptEstimatorV3(fixOverflow = true, overhead = true) - ) - .explicitGet() - - (blockchain.assetScript _).when(TestValues.asset).returns(Some(AssetScriptInfo(assetScript, assetScriptComplexity))) - - (blockchain.assetDescription _) - .when(TestValues.asset) - .returns( - Some( - AssetDescription( - TestValues.asset.id, - TxHelpers.defaultSigner.publicKey, - null, - null, - 0, - reissuable = true, - BigInt(1), - Height(1), - Some(AssetScriptInfo(assetScript, assetScriptComplexity)), - 0, - nft = false, - 0, - Height(1) - ) - ) - ) - - (blockchain.resolveERC20Address _).when(ERC20Address(TestValues.asset)).returns(Some(TestValues.asset)) - (blockchain.resolveERC20Address _).when(*).returns(None) - - val (dAppScript, _) = ScriptCompiler - .compile( - s""" - |{-# STDLIB_VERSION 4 #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - |{-# CONTENT_TYPE DAPP #-} - | - |@Callable(i) - |func default() = [] - | - |@Callable(i) - |func dataAndTransfer() = [ - | IntegerEntry("key", 1), - | BooleanEntry("key", true), - | StringEntry("key", "str"), - | BinaryEntry("key", base58''), - | DeleteEntry("key"), - | ScriptTransfer(Address(base58'${TxHelpers.secondAddress}'), 1, base58'${TestValues.asset}') - |] - | - |@Callable(i) - |func issue() = { - | let decimals = 4 - | [Issue("name", "description", 1000, decimals, true, unit, 0)] - |} - | - |@Callable(i) - |func reissue() = [Reissue(base58'${TestValues.asset}', 1, false)] - | - |@Callable(i) - |func burn() = [Burn(base58'${TestValues.asset}', 1)] - |""".stripMargin, - ScriptEstimatorV3(fixOverflow = true, overhead = true) - ) - .explicitGet() - - (blockchain.accountScript _) - .when(*) - .returns( - Some( - AccountScriptInfo( - TxHelpers.defaultSigner.publicKey, - dAppScript, - 0L, - Map(3 -> Seq("default", "dataAndTransfer", "issue", "reissue", "burn", "sponsorFee").map(_ -> 1L).toMap) - ) - ) - ) - - (blockchain.hasAccountScript _).when(*).returns(true) - blockchain.stub.activateAllFeatures() - } + val dapp = TxHelpers.signer(1031) + val invoker = TxHelpers.signer(1032) + + val failingAssetScript = TestCompiler(V4).compileExpression("""let test = true + |if (test) then throw("error") else !test + |""".stripMargin) + val issue = TxHelpers.issue(dapp, script = Some(TestCompiler(V4).compileExpression("true"))) + domain.appendBlock( + TxHelpers.transfer(richAccount, dapp.toAddress, 10.waves), + TxHelpers.transfer(richAccount, invoker.toAddress, 10.waves), + issue, + TxHelpers.transfer(dapp, invoker.toAddress, 1, issue.asset), + TxHelpers.setScript( + dapp, + TestCompiler(V4) + .compileContract(s"""@Callable(i) + |func default() = [] + | + |@Callable(i) + |func dataAndTransfer() = [ + | IntegerEntry("key", 1), + | BooleanEntry("key", true), + | StringEntry("key", "str"), + | BinaryEntry("key", base58''), + | DeleteEntry("key"), + | ScriptTransfer(Address(base58'${TxHelpers.secondAddress}'), 1, base58'${issue.asset}') + |] + | + |@Callable(i) + |func issue() = { + | let decimals = 4 + | [Issue("name", "description", 1000, decimals, true, unit, 0)] + |} + | + |@Callable(i) + |func reissue() = [Reissue(base58'${issue.asset}', 1, false)] + | + |@Callable(i) + |func burn() = [Burn(base58'${issue.asset}', 1)] + |""".stripMargin) + ), + TxHelpers.setScript(invoker, TestCompiler(V4).compileExpression("true")), + TxHelpers.setAssetScript(dapp, issue.asset, failingAssetScript) + ) - val route = routeWithBlockchain(blockchain) def testFunction(name: String, result: InvokeScriptTransaction => String) = withClue(s"function $name") { - val tx = TxHelpers.invoke(TxHelpers.defaultAddress, func = Some(name), fee = 102500000) + val tx = TxHelpers.invoke(dapp.toAddress, func = Some(name), fee = 102500000, invoker = invoker) jsonPost(routePath("/validate"), tx.json()) ~> route ~> check { val json = responseAs[JsValue] @@ -422,7 +327,7 @@ class DebugApiRouteSpec } def testPayment(result: InvokeScriptTransaction => String) = withClue("payment") { - val tx = TxHelpers.invoke(TxHelpers.secondAddress, fee = 1300000, payments = Seq(Payment(1L, TestValues.asset))) + val tx = TxHelpers.invoke(dapp.toAddress, fee = 1300000, payments = Seq(Payment(1L, issue.asset)), invoker = invoker) jsonPost(routePath("/validate"), tx.json()) ~> route ~> check { val json = responseAs[JsValue] @@ -438,12 +343,12 @@ class DebugApiRouteSpec testPayment(tx => s"""[ { | "type" : "verifier", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${invoker.toAddress}", | "result" : "success", | "error" : null |}, { | "type" : "dApp", - | "id" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", + | "id" : "${dapp.toAddress}", | "function" : "default", | "args" : [ ], | "invocations" : [ ], @@ -474,14 +379,14 @@ class DebugApiRouteSpec | }, | "assetId" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${invoker.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -496,7 +401,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${invoker.toAddress}" | } | } | }, @@ -516,12 +421,12 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 51994 + | "value" : 51998 | } ] |}, { | "type" : "asset", | "context" : "payment", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "id" : "${issue.id()}", | "result" : "failure", | "vars" : [ { | "name" : "test", @@ -550,12 +455,12 @@ class DebugApiRouteSpec "dataAndTransfer", tx => s"""[ { | "type" : "verifier", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${invoker.toAddress}", | "result" : "success", | "error" : null |}, { | "type" : "dApp", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${dapp.toAddress}", | "function" : "dataAndTransfer", | "args" : [ ], | "invocations" : [ ], @@ -582,7 +487,7 @@ class DebugApiRouteSpec | } ], | "transfers" : [ { | "address" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", - | "asset" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "asset" : "${issue.asset}", | "amount" : 1 | } ], | "issues" : [ ], @@ -604,7 +509,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${invoker.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -619,7 +524,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${invoker.toAddress}" | } | } | }, @@ -750,7 +655,7 @@ class DebugApiRouteSpec | "value" : 1 | }, { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } ] | }, { | "name" : "ScriptTransfer.@complexity", @@ -781,7 +686,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | }, { @@ -827,7 +732,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] @@ -883,7 +788,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] @@ -951,7 +856,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] @@ -1031,7 +936,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] @@ -1123,7 +1028,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] @@ -1140,7 +1045,7 @@ class DebugApiRouteSpec |}, { | "type" : "asset", | "context" : "transfer", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "id" : "${issue.asset}", | "result" : "failure", | "vars" : [ { | "name" : "test", @@ -1170,12 +1075,12 @@ class DebugApiRouteSpec "issue", tx => s"""[ { | "type" : "verifier", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${invoker.toAddress}", | "result" : "success", | "error" : null |}, { | "type" : "dApp", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${dapp.toAddress}", | "function" : "issue", | "args" : [ ], | "invocations" : [ ], @@ -1210,7 +1115,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${invoker.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -1225,7 +1130,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${invoker.toAddress}" | } | } | }, @@ -1330,12 +1235,12 @@ class DebugApiRouteSpec "reissue", tx => s"""[ { | "type" : "verifier", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${invoker.toAddress}", | "result" : "success", | "error" : null |}, { | "type" : "dApp", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${dapp.toAddress}", | "function" : "reissue", | "args" : [ ], | "invocations" : [ ], @@ -1344,7 +1249,7 @@ class DebugApiRouteSpec | "transfers" : [ ], | "issues" : [ ], | "reissues" : [ { - | "assetId" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "assetId" : "${issue.asset}", | "isReissuable" : false, | "quantity" : 1 | } ], @@ -1365,7 +1270,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${invoker.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -1380,7 +1285,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${invoker.toAddress}" | } | } | }, @@ -1398,7 +1303,7 @@ class DebugApiRouteSpec | "type" : "Array", | "value" : [ { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | }, { | "type" : "Int", | "value" : 1 @@ -1422,7 +1327,7 @@ class DebugApiRouteSpec | "value" : { | "assetId" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | }, | "quantity" : { | "type" : "Int", @@ -1449,7 +1354,7 @@ class DebugApiRouteSpec |}, { | "type" : "asset", | "context" : "reissue", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "id" : "${issue.asset}", | "result" : "failure", | "vars" : [ { | "name" : "test", @@ -1479,12 +1384,12 @@ class DebugApiRouteSpec "burn", tx => s"""[ { | "type" : "verifier", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${invoker.toAddress}", | "result" : "success", | "error" : null |}, { | "type" : "dApp", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${dapp.toAddress}", | "function" : "burn", | "args" : [ ], | "invocations" : [ ], @@ -1494,7 +1399,7 @@ class DebugApiRouteSpec | "issues" : [ ], | "reissues" : [ ], | "burns" : [ { - | "assetId" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "assetId" : "${issue.asset}", | "quantity" : 1 | } ], | "sponsorFees" : [ ], @@ -1513,7 +1418,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${invoker.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -1528,7 +1433,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${invoker.toAddress}" | } | } | }, @@ -1546,7 +1451,7 @@ class DebugApiRouteSpec | "type" : "Array", | "value" : [ { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | }, { | "type" : "Int", | "value" : 1 @@ -1567,7 +1472,7 @@ class DebugApiRouteSpec | "value" : { | "assetId" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | }, | "quantity" : { | "type" : "Int", @@ -1590,7 +1495,7 @@ class DebugApiRouteSpec |}, { | "type" : "asset", | "context" : "burn", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "id" : "${issue.asset}", | "result" : "failure", | "vars" : [ { | "name" : "test", @@ -1619,10 +1524,16 @@ class DebugApiRouteSpec } "invoke tx returning leases" in { - val dAppPk = TxHelpers.defaultSigner.publicKey - val dAppAddress = dAppPk.toAddress - val invoke = TxHelpers.invoke(dAppPk.toAddress) - val leaseCancelId = ByteStr(bytes32gen.sample.get) + val dAppPk = TxHelpers.signer(1040) + val dAppAddress = dAppPk.toAddress + + val leaseRecipient = TxHelpers.address(1041) + val aliasOwner = TxHelpers.signer(1042) + val caller = TxHelpers.signer(1043) + + val invoke = TxHelpers.invoke(dAppPk.toAddress, invoker = caller) + val originalLease = TxHelpers.lease(dAppPk, leaseRecipient) + val canceledLeaseId = originalLease.id() val amount1 = 100 val recipient1 = Recipient.Address(ByteStr.decodeBase58("3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd").get) @@ -1634,82 +1545,26 @@ class DebugApiRouteSpec val nonce2 = 2 val leaseId2 = Lease.calculateId(Lease(recipient2, amount2, nonce2), invoke.id()) - val leaseCancelAmount = 786 - - val blockchain = createBlockchainStub { blockchain => - (blockchain.balance _).when(*, *).returns(Long.MaxValue) - (blockchain.wavesBalances _).when(*).returns(Map(TxHelpers.defaultAddress -> Long.MaxValue)) - - (blockchain.resolveAlias _).when(Alias.create(recipient2.name).explicitGet()).returning(Right(TxHelpers.secondAddress)) - - blockchain.stub.activateAllFeatures() - - val (dAppScript, _) = ScriptCompiler - .compile( - s""" - |{-# STDLIB_VERSION 5 #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - |{-# CONTENT_TYPE DAPP #-} - | - |@Callable(i) - |func default() = { - | strict a = parseBigIntValue("${PureContext.BigIntMax}") - | let test = 1 - | if (test == 1) - | then - | [ - | Lease(Address(base58'${recipient1.bytes}'), $amount1, $nonce1), - | Lease(Alias("${recipient2.name}"), $amount2, $nonce2), - | LeaseCancel(base58'$leaseCancelId') - | ] - | else [] - |} - |""".stripMargin, - ScriptEstimatorV3(fixOverflow = true, overhead = true) - ) - .explicitGet() - - (blockchain.accountScript _) - .when(*) - .returns( - Some( - AccountScriptInfo( - dAppPk, - dAppScript, - 0L, - Map(3 -> Seq("default", "test1").map(_ -> 0L).toMap) - ) - ) - ) - - (blockchain.hasAccountScript _).when(*).returns(true) - - (blockchain.transactionMeta _) - .when(leaseCancelId) - .returns(Some(TxMeta(Height(1), Status.Succeeded, 0L))) - .anyNumberOfTimes() - - (blockchain.leaseDetails _) - .when(leaseCancelId) - .returns(Some(LeaseDetails(dAppPk, TxHelpers.defaultAddress, leaseCancelAmount, LeaseDetails.Status.Active, leaseCancelId, 1))) - .anyNumberOfTimes() - - (blockchain.leaseDetails _) - .when(*) - .returns(None) - .anyNumberOfTimes() - - (blockchain.resolveAlias _) - .when(*) - .returns(Right(accountGen.sample.get.toAddress)) - .anyNumberOfTimes() - } - val route = debugApiRoute - .copy( - blockchain = blockchain, - priorityPoolBlockchain = () => Some(blockchain) - ) - .route + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq(dAppPk.toAddress -> 20.waves, aliasOwner.toAddress -> 10.waves, caller.toAddress -> 5.waves), + fee = 0.003.waves + ), + originalLease, + TxHelpers.createAlias("some_alias", aliasOwner), + TxHelpers.setScript( + dAppPk, + TestCompiler(V6).compileContract(s"""@Callable(i) + |func default() = [ + | LeaseCancel(base58'$canceledLeaseId'), + | Lease(Address(base58'3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd'), 100, 0), + | Lease(Alias("some_alias"), 20, 2) + |] + |""".stripMargin) + ), + TxHelpers.setScript(caller, TestCompiler(V6).compileExpression("true")) + ) Post(routePath("/validate"), HttpEntity(ContentTypes.`application/json`, invoke.json().toString())) ~> route ~> check { val json = responseAs[JsValue] @@ -1727,7 +1582,7 @@ class DebugApiRouteSpec | "sender" : "$dAppAddress", | "recipient" : "${recipient1.bytes}", | "amount" : 100, - | "height" : 1, + | "height" : ${domain.blockchain.height}, | "status" : "active", | "cancelHeight" : null, | "cancelTransactionId" : null @@ -1735,36 +1590,35 @@ class DebugApiRouteSpec | "id" : "$leaseId2", | "originTransactionId" : "${invoke.id()}", | "sender" : "$dAppAddress", - | "recipient" : "${TxHelpers.secondAddress}", + | "recipient" : "${aliasOwner.toAddress}", | "amount" : 20, - | "height" : 1, + | "height" : ${domain.blockchain.height}, | "status" : "active", | "cancelHeight" : null, | "cancelTransactionId" : null | } ], | "leaseCancels" : [ { - | "id" : "$leaseCancelId", - | "originTransactionId" : "$leaseCancelId", + | "id" : "$canceledLeaseId", + | "originTransactionId" : "${originalLease.id()}", | "sender" : "$dAppAddress", - | "recipient" : "${TxHelpers.defaultAddress}", - | "amount" : $leaseCancelAmount, - | "height" : 1, + | "recipient" : "$leaseRecipient", + | "amount" : ${originalLease.amount.value}, + | "height" : ${domain.blockchain.height}, | "status" : "canceled", - | "cancelHeight" : 1, + | "cancelHeight" : ${domain.blockchain.height}, | "cancelTransactionId" : "${invoke.id()}" | } ], | "invokes" : [ ] |}""".stripMargin) (json \ "trace").as[JsArray] should matchJson( - s""" - |[ { + s"""[ { | "type" : "verifier", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${caller.toAddress}", | "result" : "success", | "error" : null |}, { | "type" : "dApp", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${dAppAddress}", | "function" : "default", | "args" : [ ], | "invocations" : [ ], @@ -1778,33 +1632,33 @@ class DebugApiRouteSpec | "leases" : [ { | "id" : "$leaseId1", | "originTransactionId" : "${invoke.id()}", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd", + | "sender" : "$dAppAddress", + | "recipient" : "${recipient1.bytes}", | "amount" : 100, - | "height" : 1, + | "height" : ${domain.blockchain.height}, | "status" : "active", | "cancelHeight" : null, | "cancelTransactionId" : null | }, { | "id" : "$leaseId2", | "originTransactionId" : "${invoke.id()}", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", + | "sender" : "$dAppAddress", + | "recipient" : "${aliasOwner.toAddress}", | "amount" : 20, - | "height" : 1, + | "height" : ${domain.blockchain.height}, | "status" : "active", | "cancelHeight" : null, | "cancelTransactionId" : null | } ], | "leaseCancels" : [ { - | "id" : "$leaseCancelId", - | "originTransactionId" : "$leaseCancelId", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "amount" : $leaseCancelAmount, - | "height" : 1, + | "id" : "$canceledLeaseId", + | "originTransactionId" : "${originalLease.id()}", + | "sender" : "$dAppAddress", + | "recipient" : "$leaseRecipient", + | "amount" : ${originalLease.amount.value}, + | "height" : ${domain.blockchain.height}, | "status" : "canceled", - | "cancelHeight" : 1, + | "cancelHeight" : ${domain.blockchain.height}, | "cancelTransactionId" : "${invoke.id()}" | } ], | "invokes" : [ ] @@ -1819,7 +1673,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${caller.toAddress}" | } | } | }, @@ -1829,7 +1683,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${caller.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -1837,7 +1691,7 @@ class DebugApiRouteSpec | }, | "originCallerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${caller.publicKey}" | }, | "transactionId" : { | "type" : "ByteVector", @@ -1848,7 +1702,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${caller.toAddress}" | } | } | }, @@ -1862,70 +1716,26 @@ class DebugApiRouteSpec | "type" : "Array", | "value" : [ ] | }, { - | "name" : "parseBigIntValue.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "String", - | "value" : "6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042047" - | } ] - | }, { - | "name" : "parseBigIntValue.@complexity", - | "type" : "Int", - | "value" : 65 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 25935 - | }, { - | "name" : "a", - | "type" : "BigInt", - | "value" : 6.703903964971298549787012499102923E+153 - | }, { - | "name" : "==.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "BigInt", - | "value" : 6.703903964971298549787012499102923E+153 - | }, { - | "type" : "BigInt", - | "value" : 6.703903964971298549787012499102923E+153 - | } ] - | }, { - | "name" : "==.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 25934 - | }, { - | "name" : "test", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "==.@args", + | "name" : "LeaseCancel.@args", | "type" : "Array", | "value" : [ { - | "type" : "Int", - | "value" : 1 - | }, { - | "type" : "Int", - | "value" : 1 + | "type" : "ByteVector", + | "value" : "${originalLease.id()}" | } ] | }, { - | "name" : "==.@complexity", + | "name" : "LeaseCancel.@complexity", | "type" : "Int", | "value" : 1 | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25933 + | "value" : 51999 | }, { | "name" : "Address.@args", | "type" : "Array", | "value" : [ { | "type" : "ByteVector", - | "value" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd" + | "value" : "${recipient1.bytes}" | } ] | }, { | "name" : "Address.@complexity", @@ -1934,7 +1744,7 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25932 + | "value" : 51998 | }, { | "name" : "Lease.@args", | "type" : "Array", @@ -1943,7 +1753,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd" + | "value" : "${recipient1.bytes}" | } | } | }, { @@ -1960,7 +1770,7 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25931 + | "value" : 51997 | }, { | "name" : "Alias.@args", | "type" : "Array", @@ -1975,7 +1785,7 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25930 + | "value" : 51996 | }, { | "name" : "Lease.@args", | "type" : "Array", @@ -2001,45 +1811,7 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25929 - | }, { - | "name" : "LeaseCancel.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "ByteVector", - | "value" : "$leaseCancelId" - | } ] - | }, { - | "name" : "LeaseCancel.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 25928 - | }, { - | "name" : "cons.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "LeaseCancel", - | "value" : { - | "leaseId" : { - | "type" : "ByteVector", - | "value" : "$leaseCancelId" - | } - | } - | }, { - | "type" : "Array", - | "value" : [ ] - | } ] - | }, { - | "name" : "cons.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 25927 + | "value" : 51995 | }, { | "name" : "cons.@args", | "type" : "Array", @@ -2066,15 +1838,7 @@ class DebugApiRouteSpec | } | }, { | "type" : "Array", - | "value" : [ { - | "type" : "LeaseCancel", - | "value" : { - | "leaseId" : { - | "type" : "ByteVector", - | "value" : "$leaseCancelId" - | } - | } - | } ] + | "value" : [ ] | } ] | }, { | "name" : "cons.@complexity", @@ -2083,7 +1847,7 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25926 + | "value" : 51994 | }, { | "name" : "cons.@args", | "type" : "Array", @@ -2095,7 +1859,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd" + | "value" : "${recipient1.bytes}" | } | } | }, @@ -2131,12 +1895,69 @@ class DebugApiRouteSpec | "value" : 2 | } | } + | } ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51993 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "${originalLease.id()}" + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "Lease", + | "value" : { + | "recipient" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "${recipient1.bytes}" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 100 + | }, + | "nonce" : { + | "type" : "Int", + | "value" : 0 + | } + | } | }, { - | "type" : "LeaseCancel", + | "type" : "Lease", | "value" : { - | "leaseId" : { - | "type" : "ByteVector", - | "value" : "$leaseCancelId" + | "recipient" : { + | "type" : "Alias", + | "value" : { + | "alias" : { + | "type" : "String", + | "value" : "some_alias" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 20 + | }, + | "nonce" : { + | "type" : "Int", + | "value" : 2 | } | } | } ] @@ -2148,695 +1969,674 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25925 + | "value" : 51992 | } ] |} ] - | - """.stripMargin + |""".stripMargin ) - (json \ "height").as[Int] shouldBe 1 + (json \ "height").as[Int] shouldBe domain.blockchain.height } } "invoke tx returning leases with error" in { - val dApp1Kp = signer(1) - val dApp2Kp = signer(2) - val leaseAddress = signer(3).toAddress + val dApp1Kp = signer(911) + val dApp2Kp = signer(912) + val leaseAddress = signer(913).toAddress val amount = 123 - withDomain(RideV6, AddrWithBalance.enoughBalances(dApp1Kp, dApp2Kp)) { d => - val dApp1 = TestCompiler(V6).compileContract( - s""" - | @Callable(i) - | func default() = { - | strict r = Address(base58'${dApp2Kp.toAddress}').invoke("default", [], []) - | if (true) then throw() else [] - | } + val dApp1 = TestCompiler(V6).compileContract( + s""" + | @Callable(i) + | func default() = { + | strict r = Address(base58'${dApp2Kp.toAddress}').invoke("default", [], []) + | if (true) then throw() else [] + | } """.stripMargin - ) - val leaseTx = lease(dApp2Kp, leaseAddress) - val dApp2 = TestCompiler(V6).compileContract( - s""" - | @Callable(i) - | func default() = { - | let lease = Lease(Address(base58'$leaseAddress'), $amount) - | let cancel1 = LeaseCancel(calculateLeaseId(lease)) - | let cancel2 = LeaseCancel(base58'${leaseTx.id()}') - | [lease, cancel1, cancel2] - | } + ) + val leaseTx = lease(dApp2Kp, leaseAddress) + val dApp2 = TestCompiler(V6).compileContract( + s""" + | @Callable(i) + | func default() = { + | let lease = Lease(Address(base58'$leaseAddress'), $amount) + | let cancel1 = LeaseCancel(calculateLeaseId(lease)) + | let cancel2 = LeaseCancel(base58'${leaseTx.id()}') + | [lease, cancel1, cancel2] + | } """.stripMargin - ) - d.appendBlock(leaseTx) - d.appendBlock(setScript(dApp1Kp, dApp1), setScript(dApp2Kp, dApp2)) + ) + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq( + dApp1Kp.toAddress -> 20.waves, + dApp2Kp.toAddress -> 20.waves + ), + fee = 0.002.waves + ), + leaseTx, + setScript(dApp1Kp, dApp1), + setScript(dApp2Kp, dApp2) + ) + val leaseTxHeight = domain.blockchain.height - val route = debugApiRoute.copy(blockchain = d.blockchain, priorityPoolBlockchain = () => Some(d.blockchain)).route - val invoke = TxHelpers.invoke(dApp1Kp.toAddress) - val leaseId = Lease.calculateId(Lease(Address(ByteStr(leaseAddress.bytes)), amount, 0), invoke.id()) + val invoke = TxHelpers.invoke(dApp1Kp.toAddress) + val leaseId = Lease.calculateId(Lease(Address(ByteStr(leaseAddress.bytes)), amount, 0), invoke.id()) - Post(routePath("/validate"), HttpEntity(ContentTypes.`application/json`, invoke.json().toString())) ~> route ~> check { - val json = responseAs[JsValue] - json should matchJson( - s""" - |{ - | "valid": false, - | "validationTime": ${(json \ "validationTime").as[Int]}, - | "trace": [ - | { - | "type": "dApp", - | "id": "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", - | "function": "default", - | "args": [], - | "invocations": [ - | { - | "type": "dApp", - | "id": "3MsY23LPQnvPZnBKpvs6YcnCvGjLVD42pSy", - | "function": "default", - | "args": [], - | "invocations": [], - | "result": { - | "data": [], - | "transfers": [], - | "issues": [], - | "reissues": [], - | "burns": [], - | "sponsorFees": [], - | "leases": [ - | { - | "id": "$leaseId", - | "originTransactionId": null, - | "sender": null, - | "recipient": "$leaseAddress", - | "amount": $amount, - | "height": null, - | "status": "canceled", - | "cancelHeight": null, - | "cancelTransactionId": null - | } - | ], - | "leaseCancels": [ - | { - | "id": "$leaseId", - | "originTransactionId": null, - | "sender": null, - | "recipient": null, - | "amount": null, - | "height": null, - | "status": "canceled", - | "cancelHeight": null, - | "cancelTransactionId": null - | }, { - | "id" : "${leaseTx.id()}", - | "originTransactionId" : "${leaseTx.id()}", - | "sender" : "${dApp2Kp.toAddress}", - | "recipient" : "$leaseAddress", - | "amount" : ${leaseTx.amount}, - | "height" : 2, - | "status" : "active", - | "cancelHeight" : null, - | "cancelTransactionId" : null - | } - | - | ], - | "invokes": [] - | }, - | "error": null, - | "vars": [ - | { - | "name": "i", - | "type": "Invocation", - | "value": { - | "originCaller": { - | "type": "Address", - | "value": { - | "bytes": { - | "type": "ByteVector", - | "value": "$defaultAddress" - | } - | } - | }, - | "payments": { - | "type": "Array", - | "value": [] - | }, - | "callerPublicKey": { - | "type": "ByteVector", - | "value": "8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr" - | }, - | "feeAssetId": { - | "type": "Unit", - | "value": {} - | }, - | "originCallerPublicKey": { - | "type": "ByteVector", - | "value": "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" - | }, - | "transactionId": { - | "type": "ByteVector", - | "value": "${invoke.id()}" - | }, - | "caller": { - | "type": "Address", - | "value": { - | "bytes": { - | "type": "ByteVector", - | "value": "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" - | } - | } - | }, - | "fee": { - | "type": "Int", - | "value": 500000 - | } - | } - | }, - | { - | "name": "default.@args", - | "type": "Array", - | "value": [] - | }, - | { - | "name": "Address.@args", - | "type": "Array", - | "value": [ - | { - | "type": "ByteVector", - | "value": "$leaseAddress" - | } - | ] - | }, - | { - | "name": "Address.@complexity", - | "type": "Int", - | "value": 1 - | }, - | { - | "name": "@complexityLimit", - | "type": "Int", - | "value": 51923 - | }, - | { - | "name": "Lease.@args", - | "type": "Array", - | "value": [ - | { - | "type": "Address", - | "value": { - | "bytes": { - | "type": "ByteVector", - | "value": "$leaseAddress" - | } - | } - | }, - | { - | "type": "Int", - | "value": $amount - | } - | ] - | }, - | { - | "name": "Lease.@complexity", - | "type": "Int", - | "value": 1 - | }, - | { - | "name": "@complexityLimit", - | "type": "Int", - | "value": 51922 - | }, - | { - | "name": "lease", - | "type": "Lease", - | "value": { - | "recipient": { - | "type": "Address", - | "value": { - | "bytes": { - | "type": "ByteVector", - | "value": "$leaseAddress" - | } - | } - | }, - | "amount": { - | "type": "Int", - | "value": $amount - | }, - | "nonce": { - | "type": "Int", - | "value": 0 - | } - | } - | }, - | { - | "name": "calculateLeaseId.@args", - | "type": "Array", - | "value": [ - | { - | "type": "Lease", - | "value": { - | "recipient": { - | "type": "Address", - | "value": { - | "bytes": { - | "type": "ByteVector", - | "value": "$leaseAddress" - | } - | } - | }, - | "amount": { - | "type": "Int", - | "value": $amount - | }, - | "nonce": { - | "type": "Int", - | "value": 0 - | } - | } - | } - | ] - | }, - | { - | "name": "calculateLeaseId.@complexity", - | "type": "Int", - | "value": 1 - | }, - | { - | "name": "@complexityLimit", - | "type": "Int", - | "value": 51921 - | }, - | { - | "name": "LeaseCancel.@args", - | "type": "Array", - | "value": [ - | { - | "type": "ByteVector", - | "value": "$leaseId" - | } - | ] - | }, - | { - | "name": "cancel1", - | "type": "LeaseCancel", - | "value": { - | "leaseId": { - | "type": "ByteVector", - | "value": "$leaseId" - | } - | } - | }, - | { - | "name": "LeaseCancel.@complexity", - | "type": "Int", - | "value": 1 - | }, - | { - | "name": "@complexityLimit", - | "type": "Int", - | "value": 51920 - | }, - | { - | "name" : "LeaseCancel.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "ByteVector", - | "value" : "${leaseTx.id()}" - | } ] - | }, { - | "name" : "cancel2", - | "type" : "LeaseCancel", - | "value" : { - | "leaseId" : { - | "type" : "ByteVector", - | "value" : "${leaseTx.id()}" - | } - | } - | }, { - | "name" : "LeaseCancel.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 51919 - | }, - | { - | "name": "cons.@args", - | "type": "Array", - | "value": [ - | { - | "type": "LeaseCancel", - | "value": { - | "leaseId": { - | "type": "ByteVector", - | "value": "${leaseTx.id()}" - | } - | } - | }, - | { - | "type": "Array", - | "value": [] - | } - | ] - | }, - | { - | "name": "cons.@complexity", - | "type": "Int", - | "value": 1 - | }, - | { - | "name": "@complexityLimit", - | "type": "Int", - | "value": 51918 - | }, { - | "name" : "cons.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "LeaseCancel", - | "value" : { - | "leaseId" : { - | "type" : "ByteVector", - | "value" : "$leaseId" - | } - | } - | }, { - | "type" : "Array", - | "value" : [ { - | "type" : "LeaseCancel", - | "value" : { - | "leaseId" : { - | "type" : "ByteVector", - | "value" : "${leaseTx.id()}" - | } - | } - | } ] - | } ] - | }, { - | "name" : "cons.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 51917 - | }, { - | "name": "cons.@args", - | "type": "Array", - | "value": [ - | { - | "type": "Lease", - | "value": { - | "recipient": { - | "type": "Address", - | "value": { - | "bytes": { - | "type": "ByteVector", - | "value": "$leaseAddress" - | } - | } - | }, - | "amount": { - | "type": "Int", - | "value": $amount - | }, - | "nonce": { - | "type": "Int", - | "value": 0 - | } - | } - | }, { - | "type" : "Array", - | "value" : [ { - | "type" : "LeaseCancel", - | "value" : { - | "leaseId" : { - | "type" : "ByteVector", - | "value" : "$leaseId" - | } - | } - | }, { - | "type" : "LeaseCancel", - | "value" : { - | "leaseId" : { - | "type" : "ByteVector", - | "value" : "${leaseTx.id()}" - | } - | } - | } ] - | } ] - | }, { - | "name": "cons.@complexity", - | "type": "Int", - | "value": 1 - | }, - | { - | "name": "@complexityLimit", - | "type": "Int", - | "value": 51916 - | } - | ] - | } - | ], - | "result": "failure", - | "error": "InvokeRejectError(error = Explicit script termination)", - | "vars" : [ { - | "name" : "i", - | "type" : "Invocation", - | "value" : { - | "originCaller" : { - | "type" : "Address", - | "value" : { - | "bytes" : { - | "type" : "ByteVector", - | "value" : "$defaultAddress" - | } - | } - | }, - | "payments" : { - | "type" : "Array", - | "value" : [ ] - | }, - | "callerPublicKey" : { - | "type" : "ByteVector", - | "value" : "${defaultSigner.publicKey}" - | }, - | "feeAssetId" : { - | "type" : "Unit", - | "value" : { } - | }, - | "originCallerPublicKey" : { - | "type" : "ByteVector", - | "value" : "${defaultSigner.publicKey}" - | }, - | "transactionId" : { - | "type" : "ByteVector", - | "value" : "${invoke.id()}" - | }, - | "caller" : { - | "type" : "Address", - | "value" : { - | "bytes" : { - | "type" : "ByteVector", - | "value" : "$defaultAddress" - | } - | } - | }, - | "fee" : { - | "type" : "Int", - | "value" : 500000 - | } - | } - | }, { - | "name" : "default.@args", - | "type" : "Array", - | "value" : [ ] - | }, { - | "name" : "Address.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "ByteVector", - | "value" : "3MsY23LPQnvPZnBKpvs6YcnCvGjLVD42pSy" - | } ] - | }, { - | "name" : "Address.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 51999 - | }, { - | "name" : "invoke.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "Address", - | "value" : { - | "bytes" : { - | "type" : "ByteVector", - | "value" : "3MsY23LPQnvPZnBKpvs6YcnCvGjLVD42pSy" - | } - | } - | }, { - | "type" : "String", - | "value" : "default" - | }, { - | "type" : "Array", - | "value" : [ ] - | }, { - | "type" : "Array", - | "value" : [ ] - | } ] - | }, { - | "name" : "invoke.@complexity", - | "type" : "Int", - | "value" : 75 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 51924 - | }, { - | "name" : "default.@complexity", - | "type" : "Int", - | "value" : 8 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 51916 - | }, { - | "name" : "r", - | "type" : "Unit", - | "value" : { } - | }, { - | "name" : "==.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "Unit", - | "value" : { } - | }, { - | "type" : "Unit", - | "value" : { } - | } ] - | }, { - | "name" : "==.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 51915 - | }, { - | "name" : "throw.@args", - | "type" : "Array", - | "value" : [ ] - | }, { - | "name" : "throw.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 51914 - | }, { - | "name" : "throw.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "String", - | "value" : "Explicit script termination" - | } ] - | }, { - | "name" : "throw.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 51913 - | } ] - | } - | ], - | "height": 3, - | "error": "Error while executing dApp: Explicit script termination", - | "transaction": { - | "type": 16, - | "id": "${invoke.id()}", - | "fee": 500000, - | "feeAssetId": null, - | "timestamp": ${(json \ "transaction" \ "timestamp").as[Long]}, - | "version": 2, - | "chainId": 84, - | "sender": "$defaultAddress", - | "senderPublicKey": "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", - | "proofs": [ "${(json \ "transaction" \ "proofs" \ 0).as[String]}" ], - | "dApp": "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", - | "payment": [], - | "call": { - | "function": "default", - | "args": [] - | } - | } - |} + Post(routePath("/validate"), HttpEntity(ContentTypes.`application/json`, invoke.json().toString())) ~> route ~> check { + val json = responseAs[JsValue] + json should matchJson( + s""" + |{ + | "valid": false, + | "validationTime": ${(json \ "validationTime").as[Int]}, + | "trace": [ + | { + | "type": "dApp", + | "id": "3N79VJyPMGWTf1mgfqUZw3Ce6GKoERBoc6Y", + | "function": "default", + | "args": [], + | "invocations": [ + | { + | "type": "dApp", + | "id": "3NCMNUhdTDxGv1Q21ZC6Kuk6hGuPNFoMvmh", + | "function": "default", + | "args": [], + | "invocations": [], + | "result": { + | "data": [], + | "transfers": [], + | "issues": [], + | "reissues": [], + | "burns": [], + | "sponsorFees": [], + | "leases": [ + | { + | "id": "$leaseId", + | "originTransactionId": null, + | "sender": null, + | "recipient": "$leaseAddress", + | "amount": $amount, + | "height": null, + | "status": "canceled", + | "cancelHeight": null, + | "cancelTransactionId": null + | } + | ], + | "leaseCancels": [ + | { + | "id": "$leaseId", + | "originTransactionId": null, + | "sender": null, + | "recipient": null, + | "amount": null, + | "height": null, + | "status": "canceled", + | "cancelHeight": null, + | "cancelTransactionId": null + | }, { + | "id" : "${leaseTx.id()}", + | "originTransactionId" : "${leaseTx.id()}", + | "sender" : "${dApp2Kp.toAddress}", + | "recipient" : "$leaseAddress", + | "amount" : ${leaseTx.amount}, + | "height" : $leaseTxHeight, + | "status" : "active", + | "cancelHeight" : null, + | "cancelTransactionId" : null + | } + | + | ], + | "invokes": [] + | }, + | "error": null, + | "vars": [ + | { + | "name": "i", + | "type": "Invocation", + | "value": { + | "originCaller": { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "$defaultAddress" + | } + | } + | }, + | "payments": { + | "type": "Array", + | "value": [] + | }, + | "callerPublicKey": { + | "type": "ByteVector", + | "value": "FAGx8acozxdhVGLpKzZy2RqNsoPEjZUQu4zvizNzbZyX" + | }, + | "feeAssetId": { + | "type": "Unit", + | "value": {} + | }, + | "originCallerPublicKey": { + | "type": "ByteVector", + | "value": "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | }, + | "transactionId": { + | "type": "ByteVector", + | "value": "${invoke.id()}" + | }, + | "caller": { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "3N79VJyPMGWTf1mgfqUZw3Ce6GKoERBoc6Y" + | } + | } + | }, + | "fee": { + | "type": "Int", + | "value": 500000 + | } + | } + | }, + | { + | "name": "default.@args", + | "type": "Array", + | "value": [] + | }, + | { + | "name": "Address.@args", + | "type": "Array", + | "value": [ + | { + | "type": "ByteVector", + | "value": "$leaseAddress" + | } + | ] + | }, + | { + | "name": "Address.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 51923 + | }, + | { + | "name": "Lease.@args", + | "type": "Array", + | "value": [ + | { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "$leaseAddress" + | } + | } + | }, + | { + | "type": "Int", + | "value": $amount + | } + | ] + | }, + | { + | "name": "Lease.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 51922 + | }, + | { + | "name": "lease", + | "type": "Lease", + | "value": { + | "recipient": { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "$leaseAddress" + | } + | } + | }, + | "amount": { + | "type": "Int", + | "value": $amount + | }, + | "nonce": { + | "type": "Int", + | "value": 0 + | } + | } + | }, + | { + | "name": "calculateLeaseId.@args", + | "type": "Array", + | "value": [ + | { + | "type": "Lease", + | "value": { + | "recipient": { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "$leaseAddress" + | } + | } + | }, + | "amount": { + | "type": "Int", + | "value": $amount + | }, + | "nonce": { + | "type": "Int", + | "value": 0 + | } + | } + | } + | ] + | }, + | { + | "name": "calculateLeaseId.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 51921 + | }, + | { + | "name": "LeaseCancel.@args", + | "type": "Array", + | "value": [ + | { + | "type": "ByteVector", + | "value": "$leaseId" + | } + | ] + | }, + | { + | "name": "cancel1", + | "type": "LeaseCancel", + | "value": { + | "leaseId": { + | "type": "ByteVector", + | "value": "$leaseId" + | } + | } + | }, + | { + | "name": "LeaseCancel.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 51920 + | }, + | { + | "name" : "LeaseCancel.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ByteVector", + | "value" : "${leaseTx.id()}" + | } ] + | }, { + | "name" : "cancel2", + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "${leaseTx.id()}" + | } + | } + | }, { + | "name" : "LeaseCancel.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51919 + | }, + | { + | "name": "cons.@args", + | "type": "Array", + | "value": [ + | { + | "type": "LeaseCancel", + | "value": { + | "leaseId": { + | "type": "ByteVector", + | "value": "${leaseTx.id()}" + | } + | } + | }, + | { + | "type": "Array", + | "value": [] + | } + | ] + | }, + | { + | "name": "cons.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 51918 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "$leaseId" + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "${leaseTx.id()}" + | } + | } + | } ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51917 + | }, { + | "name": "cons.@args", + | "type": "Array", + | "value": [ + | { + | "type": "Lease", + | "value": { + | "recipient": { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "$leaseAddress" + | } + | } + | }, + | "amount": { + | "type": "Int", + | "value": $amount + | }, + | "nonce": { + | "type": "Int", + | "value": 0 + | } + | } + | }, { + | "type" : "Array", + | "value" : [ { + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "$leaseId" + | } + | } + | }, { + | "type" : "LeaseCancel", + | "value" : { + | "leaseId" : { + | "type" : "ByteVector", + | "value" : "${leaseTx.id()}" + | } + | } + | } ] + | } ] + | }, { + | "name": "cons.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 51916 + | } + | ] + | } + | ], + | "result": "failure", + | "error": "InvokeRejectError(error = Explicit script termination)", + | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "originCaller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "$defaultAddress" + | } + | } + | }, + | "payments" : { + | "type" : "Array", + | "value" : [ ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "${defaultSigner.publicKey}" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "originCallerPublicKey" : { + | "type" : "ByteVector", + | "value" : "${defaultSigner.publicKey}" + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${invoke.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "$defaultAddress" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 500000 + | } + | } + | }, { + | "name" : "default.@args", + | "type" : "Array", + | "value" : [ ] + | }, { + | "name" : "Address.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ByteVector", + | "value" : "3NCMNUhdTDxGv1Q21ZC6Kuk6hGuPNFoMvmh" + | } ] + | }, { + | "name" : "Address.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51999 + | }, { + | "name" : "invoke.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "3NCMNUhdTDxGv1Q21ZC6Kuk6hGuPNFoMvmh" + | } + | } + | }, { + | "type" : "String", + | "value" : "default" + | }, { + | "type" : "Array", + | "value" : [ ] + | }, { + | "type" : "Array", + | "value" : [ ] + | } ] + | }, { + | "name" : "invoke.@complexity", + | "type" : "Int", + | "value" : 75 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51924 + | }, { + | "name" : "default.@complexity", + | "type" : "Int", + | "value" : 8 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51916 + | }, { + | "name" : "r", + | "type" : "Unit", + | "value" : { } + | }, { + | "name" : "==.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Unit", + | "value" : { } + | }, { + | "type" : "Unit", + | "value" : { } + | } ] + | }, { + | "name" : "==.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51915 + | }, { + | "name" : "throw.@args", + | "type" : "Array", + | "value" : [ ] + | }, { + | "name" : "throw.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51914 + | }, { + | "name" : "throw.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "String", + | "value" : "Explicit script termination" + | } ] + | }, { + | "name" : "throw.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51913 + | } ] + | } + | ], + | "height": ${domain.blockchain.height}, + | "error": "Error while executing dApp: Explicit script termination", + | "transaction": { + | "type": 16, + | "id": "${invoke.id()}", + | "fee": 500000, + | "feeAssetId": null, + | "timestamp": ${invoke.timestamp}, + | "version": 2, + | "chainId": 84, + | "sender": "${invoke.senderAddress}", + | "senderPublicKey": "${invoke.sender}", + | "proofs": [ "${(json \ "transaction" \ "proofs" \ 0).as[String]}" ], + | "dApp": "3N79VJyPMGWTf1mgfqUZw3Ce6GKoERBoc6Y", + | "payment": [], + | "call": { + | "function": "default", + | "args": [] + | } + | } + |} """.stripMargin - ) - } + ) } } "invoke tx with nested call" in { - val dAppPk = TxHelpers.defaultSigner.publicKey - val dAppAddress = dAppPk.toAddress - val invoke = TxHelpers.invoke(dAppPk.toAddress, func = Some("test1")) - - val blockchain = createBlockchainStub { blockchain => - (blockchain.balance _).when(*, *).returns(Long.MaxValue) - (blockchain.wavesBalances _).when(*).returns(Map(TxHelpers.defaultAddress -> Long.MaxValue)) - blockchain.stub.activateAllFeatures() - - val (dAppScript, _) = ScriptCompiler - .compile( - s""" - |{-# STDLIB_VERSION 5 #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - |{-# CONTENT_TYPE DAPP #-} - | - |@Callable(i) - |func test() = { - | strict a = parseBigIntValue("${PureContext.BigIntMax}") - | let test = 1 - | if (test == 1) - | then [IntegerEntry("key", 1)] - | else [] - |} - | - |@Callable(i) - |func test1() = { - | strict result = reentrantInvoke(this, "test", [], []) - | if (result == unit) then [] else [] - |} - |""".stripMargin, - ScriptEstimatorV3(fixOverflow = true, overhead = true) - ) - .explicitGet() - - (blockchain.accountScript _) - .when(*) - .returns( - Some( - AccountScriptInfo( - dAppPk, - dAppScript, - 0L, - Map(3 -> Seq("test", "test1").map(_ -> 0L).toMap) - ) - ) - ) - - (blockchain.hasAccountScript _).when(dAppAddress).returns(true) - } - val route = debugApiRoute - .copy( - blockchain = blockchain, - priorityPoolBlockchain = () => Some(blockchain) - ) - .route + val dAppPk = TxHelpers.signer(1050) + val caller = TxHelpers.signer(1051) + val invoke = TxHelpers.invoke(dAppPk.toAddress, func = Some("test1"), invoker = caller) + + val dAppScript = TestCompiler(V5).compileContract( + s"""@Callable(i) + |func test() = { + | strict a = parseBigIntValue("${PureContext.BigIntMax}") + | let test = 1 + | if (test == 1) + | then [IntegerEntry("key", 1)] + | else [] + |} + | + |@Callable(i) + |func test1() = { + | strict result = reentrantInvoke(this, "test", [], []) + | if (result == unit) then [] else [] + |} + |""".stripMargin + ) + domain.appendBlock( + TxHelpers.transfer(richAccount, dAppPk.toAddress, 10.waves), + TxHelpers.transfer(richAccount, caller.toAddress, 10.waves), + TxHelpers.setScript(dAppPk, dAppScript), + TxHelpers.setScript(caller, TestCompiler(V5).compileExpression("true")) + ) Post(routePath("/validate"), HttpEntity(ContentTypes.`application/json`, invoke.json().toString())) ~> route ~> check { val json = responseAs[JsValue] @@ -2851,7 +2651,7 @@ class DebugApiRouteSpec | "leases" : [ ], | "leaseCancels" : [ ], | "invokes" : [ { - | "dApp" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "dApp" : "${dAppPk.toAddress}", | "call" : { | "function" : "test", | "args" : [ ] @@ -2879,17 +2679,17 @@ class DebugApiRouteSpec s""" |[ { | "type" : "verifier", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${caller.toAddress}", | "result" : "success", | "error" : null |}, { | "type" : "dApp", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${dAppPk.toAddress}", | "function" : "test1", | "args" : [ ], | "invocations" : [ { | "type" : "dApp", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "id" : "${dAppPk.toAddress}", | "function" : "test", | "args" : [ ], | "invocations" : [ ], @@ -2918,7 +2718,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${caller.toAddress}" | } | } | }, @@ -2928,7 +2728,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${dAppPk.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -2936,7 +2736,7 @@ class DebugApiRouteSpec | }, | "originCallerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${caller.publicKey}" | }, | "transactionId" : { | "type" : "ByteVector", @@ -2947,7 +2747,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${dAppPk.toAddress}" | } | } | }, @@ -3087,7 +2887,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${caller.toAddress}" | } | } | }, @@ -3097,7 +2897,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${caller.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -3105,7 +2905,7 @@ class DebugApiRouteSpec | }, | "originCallerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${caller.publicKey}" | }, | "transactionId" : { | "type" : "ByteVector", @@ -3116,7 +2916,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${caller.toAddress}" | } | } | }, @@ -3137,7 +2937,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${dAppPk.toAddress}" | } | } | }, { @@ -3210,53 +3010,30 @@ class DebugApiRouteSpec |} ] """.stripMargin ) - (json \ "height").as[Int] shouldBe 1 + (json \ "height").as[Int] shouldBe domain.blockchain.height } } "transfer transaction with asset fail" in { - val blockchain = createBlockchainStub { blockchain => - (blockchain.balance _).when(*, *).returns(Long.MaxValue / 2) - (blockchain.wavesBalances _).when(*).returns(Map(TxHelpers.defaultAddress -> Long.MaxValue / 2)) - - val (assetScript, assetScriptComplexity) = - ScriptCompiler.compile("false", ScriptEstimatorV3(fixOverflow = true, overhead = true)).explicitGet() - (blockchain.assetScript _).when(TestValues.asset).returns(Some(AssetScriptInfo(assetScript, assetScriptComplexity))) - (blockchain.assetDescription _) - .when(TestValues.asset) - .returns( - Some( - AssetDescription( - TestValues.asset.id, - TxHelpers.defaultSigner.publicKey, - null, - null, - 0, - reissuable = true, - BigInt(1), - Height(1), - Some(AssetScriptInfo(assetScript, assetScriptComplexity)), - 0, - nft = false, - 0, - Height(1) - ) - ) - ) - } - val route = routeWithBlockchain(blockchain) - val tx = TxHelpers.transfer(TxHelpers.defaultSigner, TxHelpers.defaultAddress, 1, TestValues.asset) + val assetOwner = TxHelpers.signer(1060) + val issue = TxHelpers.issue(assetOwner, script = Some(TestCompiler(V5).compileAsset("false"))) + domain.appendBlock( + TxHelpers.transfer(richAccount, assetOwner.toAddress, 5.waves), + issue + ) + + val tx = TxHelpers.transfer(assetOwner, TxHelpers.defaultAddress, 1, issue.asset) jsonPost(routePath("/validate"), tx.json()) ~> route ~> check { val json = responseAs[JsObject] - (json \ "trace").as[JsArray] should matchJson("""[ { - | "type" : "asset", - | "context" : "transfer", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", - | "result" : "failure", - | "vars" : [ ], - | "error" : null - | } ]""".stripMargin) + (json \ "trace").as[JsArray] should matchJson(s"""[ { + | "type" : "asset", + | "context" : "transfer", + | "id" : "${issue.id()}", + | "result" : "failure", + | "vars" : [ ], + | "error" : null + | } ]""".stripMargin) (json \ "valid").as[Boolean] shouldBe false (json \ "transaction").as[JsObject] shouldBe tx.json() @@ -3264,52 +3041,50 @@ class DebugApiRouteSpec } "txs with empty and small verifier" in { - val blockchain = createBlockchainStub { blockchain => - val settings = TestFunctionalitySettings.Enabled.copy( - featureCheckBlocksPeriod = 1, - blocksForFeatureActivation = 1, - preActivatedFeatures = Map( - BlockchainFeatures.SmartAccounts.id -> 0, - BlockchainFeatures.SmartAssets.id -> 0, - BlockchainFeatures.Ride4DApps.id -> 0, - BlockchainFeatures.FeeSponsorship.id -> 0, - BlockchainFeatures.DataTransaction.id -> 0, - BlockchainFeatures.BlockReward.id -> 0, - BlockchainFeatures.BlockV5.id -> 0, - BlockchainFeatures.SynchronousCalls.id -> 0 + val transferFee = 100000 + + val acc1 = TxHelpers.signer(1070) + val acc2 = TxHelpers.signer(1071) + val acc3 = TxHelpers.signer(1072) + + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq( + acc1.toAddress -> 2.waves, + acc2.toAddress -> 2.waves, + acc3.toAddress -> 2.waves + ), + fee = 0.003.waves + ), + TxHelpers.setScript( + acc2, + TestCompiler(V5).compileExpression( + "sigVerify(base58'', base58'', base58'') && sigVerify(base58'', base58'', base58'') && false" + ) + ), + TxHelpers.setScript( + acc3, + TestCompiler(V5).compileExpression( + "sigVerify_16Kb(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)" ) ) - (() => blockchain.settings).when().returns(WavesSettings.default().blockchainSettings.copy(functionalitySettings = settings)) - (() => blockchain.activatedFeatures).when().returns(settings.preActivatedFeatures) - (blockchain.balance _).when(*, *).returns(ENOUGH_AMT) - (blockchain.wavesBalances _) - .when(*) - .returns(Map(TxHelpers.defaultAddress -> ENOUGH_AMT, TxHelpers.secondAddress -> ENOUGH_AMT, TxHelpers.address(3) -> ENOUGH_AMT)) - - val script = ExprScript(TRUE).explicitGet() - def info(complexity: Int) = Some(AccountScriptInfo(TxHelpers.secondSigner.publicKey, script, complexity)) - - (blockchain.accountScript _).when(TxHelpers.defaultSigner.toAddress).returns(info(199)) - (blockchain.accountScript _).when(TxHelpers.secondSigner.toAddress).returns(info(201)) - (blockchain.accountScript _).when(TxHelpers.signer(3).toAddress).returns(None) - } - val route = routeWithBlockchain(blockchain) - val transferFee = 100000 + ) - val tx = TxHelpers.transfer(TxHelpers.defaultSigner, TxHelpers.secondSigner.toAddress, 1.waves, fee = transferFee, version = TxVersion.V2) + val tx = TxHelpers.transfer(acc1, TxHelpers.secondSigner.toAddress, 1.waves, fee = transferFee, version = TxVersion.V2) validatePost(tx) ~> route ~> check { val json = responseAs[JsValue] (json \ "valid").as[Boolean] shouldBe true } - val tx2 = TxHelpers.transfer(TxHelpers.secondSigner, TestValues.address, 1.waves, fee = transferFee, version = TxVersion.V2) + val tx2 = TxHelpers.transfer(acc2, TestValues.address, 1.waves, fee = transferFee, version = TxVersion.V2) validatePost(tx2) ~> route ~> check { val json = responseAs[JsValue] (json \ "valid").as[Boolean] shouldBe false (json \ "error").as[String] should include("Requires 400000 extra fee") } - val tx3 = TxHelpers.transfer(TxHelpers.signer(3), TestValues.address, 1.waves, fee = transferFee, version = TxVersion.V2) + val tx3 = TxHelpers.transfer(acc3, TestValues.address, 1.waves, fee = transferFee, version = TxVersion.V2) validatePost(tx3) ~> route ~> check { val json = responseAs[JsValue] (json \ "valid").as[Boolean] shouldBe true @@ -3317,178 +3092,166 @@ class DebugApiRouteSpec } "InvokeExpression" in { - def assert(wavesSettings: WavesSettings): Assertion = { - val blockchain = createBlockchainStub { blockchain => - val settings = wavesSettings.blockchainSettings.functionalitySettings - (() => blockchain.settings).when().returns(WavesSettings.default().blockchainSettings.copy(functionalitySettings = settings)) - (() => blockchain.activatedFeatures).when().returns(settings.preActivatedFeatures) - (blockchain.balance _).when(*, *).returns(ENOUGH_AMT) - (blockchain.accountScript _).when(*).returns(None) - (blockchain.assetScript _).when(*).returns(None) - (blockchain.assetDescription _).when(TestValues.asset).returns(Some(TestValues.assetDescription)) - } - val route = routeWithBlockchain(blockchain) - - val expression = TestCompiler(V6).compileFreeCall( - s""" - | let assetId = base58'${TestValues.asset}' - | [ Reissue(assetId, 1, true) ] + val sender = TxHelpers.signer(1080) + val issue = TxHelpers.issue(sender) + val expression = TestCompiler(V6).compileFreeCall( + s""" + | let assetId = base58'${issue.asset}' + | [ Reissue(assetId, 1, true) ] """.stripMargin - ) - val invokeExpression = TxHelpers.invokeExpression(expression) - jsonPost(routePath("/validate"), invokeExpression.json()) ~> route ~> check { - val json = responseAs[JsValue] - (json \ "expression").as[String] shouldBe expression.bytes.value().base64 - (json \ "valid").as[Boolean] shouldBe true - (json \ "trace").as[JsArray] should matchJson( - s""" - |[ { - | "type" : "dApp", - | "id" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "function" : "default", - | "args" : [ ], - | "invocations" : [ ], - | "result" : { - | "data" : [ ], - | "transfers" : [ ], - | "issues" : [ ], - | "reissues" : [ { - | "assetId" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", - | "isReissuable" : true, - | "quantity" : 1 - | } ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - | }, - | "error" : null, - | "vars" : [ { - | "name" : "i", - | "type" : "Invocation", - | "value" : { - | "originCaller" : { - | "type" : "Address", - | "value" : { - | "bytes" : { - | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" - | } - | } - | }, - | "payments" : { - | "type" : "Array", - | "value" : [ ] - | }, - | "callerPublicKey" : { - | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" - | }, - | "feeAssetId" : { - | "type" : "Unit", - | "value" : { } - | }, - | "originCallerPublicKey" : { - | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" - | }, - | "transactionId" : { - | "type" : "ByteVector", - | "value" : "${invokeExpression.id()}" - | }, - | "caller" : { - | "type" : "Address", - | "value" : { - | "bytes" : { - | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" - | } - | } - | }, - | "fee" : { - | "type" : "Int", - | "value" : 1000000 - | } - | } - | }, { - | "name" : "default.@args", - | "type" : "Array", - | "value" : [ ] - | }, { - | "name" : "assetId", - | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" - | }, { - | "name" : "Reissue.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" - | }, { - | "type" : "Int", - | "value" : 1 - | }, { - | "type" : "Boolean", - | "value" : true - | } ] - | }, { - | "name" : "Reissue.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 51999 - | }, { - | "name" : "cons.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "Reissue", - | "value" : { - | "assetId" : { - | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" - | }, - | "quantity" : { - | "type" : "Int", - | "value" : 1 - | }, - | "isReissuable" : { - | "type" : "Boolean", - | "value" : true - | } - | } - | }, { - | "type" : "Array", - | "value" : [ ] - | } ] - | }, { - | "name" : "cons.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 51998 - | } ] - |} ] - | + ) + domain.appendBlock( + TxHelpers.transfer(richAccount, sender.toAddress, 2.waves), + issue + ) + val invokeExpression = TxHelpers.invokeExpression(expression, sender) + jsonPost(routePath("/validate"), invokeExpression.json()) ~> route ~> check { + val json = responseAs[JsValue] + (json \ "expression").as[String] shouldBe expression.bytes.value().base64 + (json \ "valid").as[Boolean] shouldBe true + (json \ "trace").as[JsArray] should matchJson( + s""" + |[ { + | "type" : "dApp", + | "id" : "${sender.toAddress}", + | "function" : "default", + | "args" : [ ], + | "invocations" : [ ], + | "result" : { + | "data" : [ ], + | "transfers" : [ ], + | "issues" : [ ], + | "reissues" : [ { + | "assetId" : "${issue.id()}", + | "isReissuable" : true, + | "quantity" : 1 + | } ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + | }, + | "error" : null, + | "vars" : [ { + | "name" : "i", + | "type" : "Invocation", + | "value" : { + | "originCaller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "${sender.toAddress}" + | } + | } + | }, + | "payments" : { + | "type" : "Array", + | "value" : [ ] + | }, + | "callerPublicKey" : { + | "type" : "ByteVector", + | "value" : "${sender.publicKey}" + | }, + | "feeAssetId" : { + | "type" : "Unit", + | "value" : { } + | }, + | "originCallerPublicKey" : { + | "type" : "ByteVector", + | "value" : "${sender.publicKey}" + | }, + | "transactionId" : { + | "type" : "ByteVector", + | "value" : "${invokeExpression.id()}" + | }, + | "caller" : { + | "type" : "Address", + | "value" : { + | "bytes" : { + | "type" : "ByteVector", + | "value" : "${sender.toAddress}" + | } + | } + | }, + | "fee" : { + | "type" : "Int", + | "value" : 1000000 + | } + | } + | }, { + | "name" : "default.@args", + | "type" : "Array", + | "value" : [ ] + | }, { + | "name" : "assetId", + | "type" : "ByteVector", + | "value" : "${issue.id()}" + | }, { + | "name" : "Reissue.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "ByteVector", + | "value" : "${issue.id()}" + | }, { + | "type" : "Int", + | "value" : 1 + | }, { + | "type" : "Boolean", + | "value" : true + | } ] + | }, { + | "name" : "Reissue.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51999 + | }, { + | "name" : "cons.@args", + | "type" : "Array", + | "value" : [ { + | "type" : "Reissue", + | "value" : { + | "assetId" : { + | "type" : "ByteVector", + | "value" : "${issue.id()}" + | }, + | "quantity" : { + | "type" : "Int", + | "value" : 1 + | }, + | "isReissuable" : { + | "type" : "Boolean", + | "value" : true + | } + | } + | }, { + | "type" : "Array", + | "value" : [ ] + | } ] + | }, { + | "name" : "cons.@complexity", + | "type" : "Int", + | "value" : 1 + | }, { + | "name" : "@complexityLimit", + | "type" : "Int", + | "value" : 51998 + | } ] + |} ] + | """.stripMargin - ) - } + ) } - - assert(ContinuationTransaction) - intercept[Exception](assert(RideV6)).getMessage should include( - s"${BlockchainFeatures.ContinuationTransaction.description} feature has not been activated yet" - ) } } routePath("/minerInfo") - { "returns info from wallet if miner private keys not specified in config" in { - val acc = wallet.generateNewAccount() + val acc = domain.wallet.generateNewAccount() acc shouldBe defined Get(routePath("/minerInfo")) ~> ApiKeyHeader ~> route ~> check { @@ -3497,7 +3260,7 @@ class DebugApiRouteSpec } "returns info only for miner private keys from config when specified" in { - val minerAccs = Seq(TxHelpers.signer(1), TxHelpers.signer(2)) + val minerAccs = Seq(TxHelpers.signer(1090), TxHelpers.signer(1091)) val minerConfig = debugApiRoute.ws.minerSettings.copy(privateKeys = minerAccs.map(_.privateKey)) val debugRoute = debugApiRoute.copy(ws = debugApiRoute.ws.copy(minerSettings = minerConfig)) @@ -3507,19 +3270,6 @@ class DebugApiRouteSpec } } - private def routeWithBlockchain(blockchain: Blockchain & NG) = - debugApiRoute.copy(blockchain = blockchain, priorityPoolBlockchain = () => Some(blockchain)).route - - private def routeWithBlockchain(d: Domain) = - debugApiRoute - .copy( - blockchain = d.blockchain, - priorityPoolBlockchain = () => Some(d.blockchain), - loadBalanceHistory = d.rocksDBWriter.loadBalanceHistory, - loadStateHash = d.rocksDBWriter.loadStateHash - ) - .route - private[this] def jsonPost(path: String, json: JsValue) = { Post(path, HttpEntity(ContentTypes.`application/json`, json.toString())) } diff --git a/node/src/test/scala/com/wavesplatform/http/LeaseRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/LeaseRouteSpec.scala index d58f5337c4..3cbbe70820 100644 --- a/node/src/test/scala/com/wavesplatform/http/LeaseRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/LeaseRouteSpec.scala @@ -3,128 +3,93 @@ package com.wavesplatform.http import akka.http.scaladsl.model.{ContentTypes, FormData, HttpEntity} import akka.http.scaladsl.server.Route import com.wavesplatform.account.{Address, AddressOrAlias, KeyPair} -import com.wavesplatform.api.common.{CommonAccountsApi, LeaseInfo} +import com.wavesplatform.api.common.CommonAccountsApi import com.wavesplatform.api.http.RouteTimeout import com.wavesplatform.api.http.leasing.LeaseApiRoute import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.db.WithDomain +import com.wavesplatform.db.WithState import com.wavesplatform.db.WithState.AddrWithBalance -import com.wavesplatform.history.Domain import com.wavesplatform.lang.directives.values.{V5, V6} -import com.wavesplatform.lang.v1.FunctionHeader -import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BYTESTR, CONST_LONG, FUNCTION_CALL} +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BYTESTR, CONST_LONG} import com.wavesplatform.lang.v1.compiler.TestCompiler -import com.wavesplatform.network.TransactionPublisher import com.wavesplatform.settings.WavesSettings import com.wavesplatform.state.TxMeta.Status -import com.wavesplatform.state.diffs.ENOUGH_AMT -import com.wavesplatform.state.reader.LeaseDetails -import com.wavesplatform.state.{BinaryDataEntry, Blockchain, Height, TxMeta} +import com.wavesplatform.state.{BinaryDataEntry, Blockchain, LeaseDetails, LeaseStaticInfo} import com.wavesplatform.test.* -import com.wavesplatform.test.DomainPresets.* -import com.wavesplatform.transaction.TxHelpers.{defaultSigner, secondSigner, signer} -import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction} -import com.wavesplatform.transaction.smart.SetScriptTransaction -import com.wavesplatform.transaction.smart.script.trace.TracedResult -import com.wavesplatform.transaction.utils.EthConverters.* import com.wavesplatform.transaction.EthTxGenerator.Arg -import com.wavesplatform.transaction.utils.Signed -import com.wavesplatform.transaction.{Asset, Authorized, EthTxGenerator, Transaction, TxHelpers, TxVersion} -import com.wavesplatform.utils.{SharedSchedulerMixin, SystemTime} -import com.wavesplatform.wallet.Wallet -import com.wavesplatform.{NTPTime, TestWallet, TransactionGen} -import org.scalacheck.Gen -import org.scalamock.scalatest.PathMockFactory +import com.wavesplatform.transaction.TxHelpers.signer +import com.wavesplatform.transaction.lease.LeaseTransaction +import com.wavesplatform.transaction.utils.EthConverters.* +import com.wavesplatform.transaction.{Authorized, EthTxGenerator, Transaction, TxHelpers, TxPositiveAmount, TxVersion} +import com.wavesplatform.utils.SharedSchedulerMixin +import org.scalactic.source.Position +import org.scalatest.OptionValues import play.api.libs.json.{JsArray, JsObject, Json} -import scala.concurrent.Future import scala.concurrent.duration.* -class LeaseRouteSpec - extends RouteSpec("/leasing") - with TransactionGen - with RestAPISettingsHelper - with NTPTime - with WithDomain - with TestWallet - with PathMockFactory - with SharedSchedulerMixin { - private def route(domain: Domain) = - LeaseApiRoute( - restAPISettings, - testWallet, - domain.blockchain, - (_, _) => Future.successful(TracedResult(Right(true))), - ntpTime, - CommonAccountsApi(() => domain.blockchainUpdater.snapshotBlockchain, domain.rdb, domain.blockchain), - new RouteTimeout(60.seconds)(sharedScheduler) +class LeaseRouteSpec extends RouteSpec("/leasing") with OptionValues with RestAPISettingsHelper with SharedDomain with SharedSchedulerMixin { + private val richAccount = TxHelpers.signer(200) + + override def genesisBalances: Seq[WithState.AddrWithBalance] = Seq(AddrWithBalance(richAccount.toAddress, 500_000.waves)) + override def settings: WavesSettings = DomainPresets.ContinuationTransaction + + private val route = + seal( + LeaseApiRoute( + restAPISettings, + domain.wallet, + domain.blockchain, + DummyTransactionPublisher.accepting, + ntpTime, + CommonAccountsApi(() => domain.blockchainUpdater.snapshotBlockchain, domain.rdb, domain.blockchain), + new RouteTimeout(60.seconds)(sharedScheduler) + ).route ) - private def withRoute(balances: Seq[AddrWithBalance], settings: WavesSettings = mostRecent)(f: (Domain, Route) => Unit): Unit = - withDomain(settings = settings, balances = balances) { d => - f(d, route(d).route) - } + private val leaseContract = TestCompiler(V5) + .compileContract(""" + |{-# STDLIB_VERSION 4 #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |@Callable(inv) + |func leaseTo(recipient: ByteVector, amount: Int) = { + | let lease = Lease(Address(recipient), amount) + | [ + | lease, + | BinaryEntry("leaseId", lease.calculateLeaseId()) + | ] + |} + | + |@Callable(inv) + |func cancelLease(id: ByteVector) = { + | [ + | LeaseCancel(id) + | ] + |} + |""".stripMargin) private def setScriptTransaction(sender: KeyPair) = - SetScriptTransaction - .selfSigned( - TxVersion.V2, - sender, - Some(TestCompiler(V5).compileContract(""" - |{-# STDLIB_VERSION 4 #-} - |{-# CONTENT_TYPE DAPP #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - | - |@Callable(inv) - |func leaseTo(recipient: ByteVector, amount: Int) = { - | let lease = Lease(Address(recipient), amount) - | [ - | lease, - | BinaryEntry("leaseId", lease.calculateLeaseId()) - | ] - |} - | - |@Callable(inv) - |func cancelLease(id: ByteVector) = { - | [ - | LeaseCancel(id) - | ] - |} - |""".stripMargin)), - 0.01.waves, - ntpTime.getTimestamp() - ) - .explicitGet() + TxHelpers.setScript(sender, leaseContract) private def invokeLeaseCancel(sender: KeyPair, leaseId: ByteStr) = - Signed.invokeScript( - TxVersion.V2, - sender, - sender.toAddress, - Some( - FUNCTION_CALL( - FunctionHeader.User("cancelLease"), - List(CONST_BYTESTR(leaseId).explicitGet()) - ) - ), - Seq.empty, - 0.005.waves, - Asset.Waves, - ntpTime.getTimestamp() - ) + TxHelpers.invoke(sender.toAddress, Some("cancelLease"), Seq(CONST_BYTESTR(leaseId).explicitGet())) private def leaseCancelTransaction(sender: KeyPair, leaseId: ByteStr) = - LeaseCancelTransaction.selfSigned(TxVersion.V3, sender, leaseId, 0.001.waves, ntpTime.getTimestamp()).explicitGet() + TxHelpers.leaseCancel(leaseId, sender, version = TxVersion.V3) private def checkDetails(id: ByteStr, details: LeaseDetails, json: JsObject): Unit = { (json \ "id").as[ByteStr] shouldEqual id (json \ "originTransactionId").as[ByteStr] shouldEqual details.sourceId (json \ "sender").as[String] shouldEqual details.sender.toAddress.toString - (json \ "amount").as[Long] shouldEqual details.amount + (json \ "amount").as[Long] shouldEqual details.amount.value } - private def checkActiveLeasesFor(address: AddressOrAlias, route: Route, expectedDetails: Seq[(ByteStr, LeaseDetails)]): Unit = + private def checkActiveLeasesFor(address: AddressOrAlias, route: Route, expectedDetails: Seq[(ByteStr, LeaseDetails)])(implicit + pos: Position + ): Unit = Get(routePath(s"/active/$address")) ~> route ~> check { val resp = responseAs[Seq[JsObject]] resp.size shouldEqual expectedDetails.size @@ -133,193 +98,195 @@ class LeaseRouteSpec } } - private def toDetails(lt: LeaseTransaction) = LeaseDetails(lt.sender, lt.recipient, lt.amount.value, LeaseDetails.Status.Active, lt.id(), 1) - - private def leaseGen(sender: KeyPair, maxAmount: Long, timestamp: Long): Gen[LeaseTransaction] = - for { - fee <- smallFeeGen - recipient <- accountGen - amount <- Gen.chooseNum(1, (maxAmount - fee).max(1)) - version <- Gen.oneOf(1.toByte, 2.toByte, 3.toByte) - } yield LeaseTransaction.selfSigned(version, sender, recipient.toAddress, amount, fee, timestamp).explicitGet() + private def toDetails(lt: LeaseTransaction, blockchain: Blockchain) = + LeaseDetails( + LeaseStaticInfo(lt.sender, blockchain.resolveAlias(lt.recipient).explicitGet(), lt.amount, lt.id(), blockchain.height), + LeaseDetails.Status.Active + ) "returns active leases which were" - { - val sender = TxHelpers.signer(1) - val leaseTx = leaseGen(sender, ENOUGH_AMT, ntpTime.correctedTime()) - - "created and cancelled by Lease/LeaseCancel transactions" in forAll(leaseTx) { leaseTransaction => - withRoute(Seq(AddrWithBalance(sender.toAddress))) { (d, r) => - d.appendBlock(leaseTransaction) - val expectedDetails = Seq(leaseTransaction.id() -> toDetails(leaseTransaction)) - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(leaseTransaction.sender.toAddress, r, expectedDetails) - checkActiveLeasesFor(leaseTransaction.recipient, r, expectedDetails) + val transactionVersions = Table("lease transaction version", 1.toByte, 2.toByte, 3.toByte) + + "created and cancelled by Lease/LeaseCancel transactions" in { + val lessor = TxHelpers.signer(201) + val leaseRecipient = TxHelpers.address(202) + + domain.appendBlock(TxHelpers.transfer(richAccount, lessor.toAddress, 30.006.waves)) + + forAll(transactionVersions) { v => + val leaseTransaction = TxHelpers.lease(lessor, leaseRecipient, version = v) + val expectedDetails = Seq(leaseTransaction.id() -> toDetails(leaseTransaction, domain.blockchain)) + + domain.appendBlock(leaseTransaction) + + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(leaseTransaction.sender.toAddress, route, expectedDetails) + checkActiveLeasesFor(leaseTransaction.recipient, route, expectedDetails) } - d.appendMicroBlock(leaseCancelTransaction(sender, leaseTransaction.id())) + domain.appendMicroBlock(TxHelpers.leaseCancel(leaseTransaction.id(), lessor)) - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(leaseTransaction.sender.toAddress, r, Seq.empty) - checkActiveLeasesFor(leaseTransaction.recipient, r, Seq.empty) + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(leaseTransaction.sender.toAddress, route, Seq.empty) + checkActiveLeasesFor(leaseTransaction.recipient, route, Seq.empty) } } } - "created by LeaseTransaction and canceled by InvokeScriptTransaction" in forAll(leaseTx) { leaseTransaction => - withRoute(Seq(AddrWithBalance(sender.toAddress))) { (d, r) => - d.appendBlock(leaseTransaction) - val expectedDetails = Seq(leaseTransaction.id() -> toDetails(leaseTransaction)) + "created by LeaseTransaction and canceled by InvokeScriptTransaction" in { + val lessor = TxHelpers.signer(202) + val leaseRecipient = TxHelpers.address(203) + + domain.appendBlock(TxHelpers.transfer(richAccount, lessor.toAddress, 35.waves)) + forAll(transactionVersions) { v => + val leaseTransaction = TxHelpers.lease(lessor, leaseRecipient, version = v) + domain.appendBlock(leaseTransaction) + val expectedDetails = Seq(leaseTransaction.id() -> toDetails(leaseTransaction, domain.blockchain)) - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(leaseTransaction.sender.toAddress, r, expectedDetails) - checkActiveLeasesFor(leaseTransaction.recipient, r, expectedDetails) + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(leaseTransaction.sender.toAddress, route, expectedDetails) + checkActiveLeasesFor(leaseTransaction.recipient, route, expectedDetails) } - d.appendMicroBlock( - setScriptTransaction(sender), - invokeLeaseCancel(sender, leaseTransaction.id()) + domain.appendMicroBlock( + setScriptTransaction(lessor), + invokeLeaseCancel(lessor, leaseTransaction.id()) ) - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(leaseTransaction.sender.toAddress, r, Seq.empty) - checkActiveLeasesFor(leaseTransaction.recipient, r, Seq.empty) + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(leaseTransaction.sender.toAddress, route, Seq.empty) + checkActiveLeasesFor(leaseTransaction.recipient, route, Seq.empty) } } } - val setScriptAndInvoke = { - val sender = TxHelpers.signer(1) - val recipient = TxHelpers.signer(2) + val dappAddress = TxHelpers.signer(200) + val leaseRecipient = TxHelpers.address(201) + def setScriptAndInvoke = ( - sender, - setScriptTransaction(sender), - Signed - .invokeScript( - TxVersion.V2, - sender, - sender.toAddress, - Some( - FUNCTION_CALL( - FunctionHeader.User("leaseTo"), - List(CONST_BYTESTR(ByteStr(recipient.toAddress.bytes)).explicitGet(), CONST_LONG(10_000.waves)) - ) - ), - Seq.empty, - 0.005.waves, - Asset.Waves, - ntpTime.getTimestamp() - ), - recipient.toAddress + setScriptTransaction(dappAddress), + TxHelpers.invoke( + dappAddress.toAddress, + Some("leaseTo"), + Seq(CONST_BYTESTR(ByteStr(leaseRecipient.bytes)).explicitGet(), CONST_LONG(10_000.waves)), + invoker = dappAddress + ) ) - } - "created by InvokeScriptTransaction and canceled by CancelLeaseTransaction" in forAll(setScriptAndInvoke) { - case (sender, setScript, invoke, recipient) => - withRoute(Seq(AddrWithBalance(sender.toAddress))) { (d, r) => - d.appendBlock(setScript) - d.appendBlock(invoke) - val leaseId = d.blockchain - .accountData(sender.toAddress, "leaseId") - .collect { case i: BinaryDataEntry => - i.value - } - .get - val expectedDetails = Seq(leaseId -> LeaseDetails(setScript.sender, recipient, 10_000.waves, LeaseDetails.Status.Active, invoke.id(), 1)) - - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(sender.toAddress, r, expectedDetails) - checkActiveLeasesFor(recipient, r, expectedDetails) - } + "created by InvokeScriptTransaction and canceled by CancelLeaseTransaction" in { + val (setScript, invoke) = setScriptAndInvoke + domain.appendBlock(TxHelpers.transfer(richAccount, dappAddress.toAddress, 20_000.waves), setScript, invoke) + val leaseId = domain.blockchain + .accountData(dappAddress.toAddress, "leaseId") + .collect { case i: BinaryDataEntry => + i.value + } + .value - d.appendMicroBlock(leaseCancelTransaction(sender, leaseId)) + val expectedDetails = Seq( + leaseId -> LeaseDetails( + LeaseStaticInfo(dappAddress.publicKey, leaseRecipient, TxPositiveAmount(10_000_00000000L), invoke.id(), 1), + LeaseDetails.Status.Active + ) + ) - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(sender.toAddress, r, Seq.empty) - checkActiveLeasesFor(recipient, r, Seq.empty) - } - } + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(dappAddress.toAddress, route, expectedDetails) + checkActiveLeasesFor(leaseRecipient, route, expectedDetails) + } + + domain.appendMicroBlock(leaseCancelTransaction(dappAddress, leaseId)) + + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(dappAddress.toAddress, route, Seq.empty) + checkActiveLeasesFor(leaseRecipient, route, Seq.empty) + } } - "created and canceled by InvokeScriptTransaction" in forAll(setScriptAndInvoke) { case (sender, setScript, invoke, recipient) => - withRoute(Seq(AddrWithBalance(sender.toAddress))) { (d, r) => - d.appendBlock(setScript) - d.appendBlock(invoke) - val invokeStatus = d.blockchain.transactionMeta(invoke.id()).get.status - assert(invokeStatus == Status.Succeeded, "Invoke has failed") + "created and canceled by InvokeScriptTransaction" in { + val (setScript, invoke) = setScriptAndInvoke - val leaseId = d.blockchain - .accountData(sender.toAddress, "leaseId") - .collect { case i: BinaryDataEntry => - i.value - } - .get - val expectedDetails = Seq(leaseId -> LeaseDetails(setScript.sender, recipient, 10_000.waves, LeaseDetails.Status.Active, invoke.id(), 1)) + domain.appendBlock(TxHelpers.transfer(richAccount, dappAddress.toAddress, 20_000.waves), setScript, invoke) + val invokeStatus = domain.blockchain.transactionMeta(invoke.id()).get.status + assert(invokeStatus == Status.Succeeded, "Invoke has failed") - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(sender.toAddress, r, expectedDetails) - checkActiveLeasesFor(recipient, r, expectedDetails) + val leaseId = domain.blockchain + .accountData(dappAddress.toAddress, "leaseId") + .collect { case i: BinaryDataEntry => + i.value } + .get + val expectedDetails = + Seq( + leaseId -> LeaseDetails( + LeaseStaticInfo(dappAddress.publicKey, leaseRecipient, TxPositiveAmount(10_000_00000000L), invoke.id(), 1), + LeaseDetails.Status.Active + ) + ) - d.appendMicroBlock(invokeLeaseCancel(sender, leaseId)) + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(dappAddress.toAddress, route, expectedDetails) + checkActiveLeasesFor(leaseRecipient, route, expectedDetails) + } - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(sender.toAddress, r, Seq.empty) - checkActiveLeasesFor(recipient, r, Seq.empty) - } + domain.appendMicroBlock(invokeLeaseCancel(dappAddress, leaseId)) + + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(dappAddress.toAddress, route, Seq.empty) + checkActiveLeasesFor(leaseRecipient, route, Seq.empty) } } - val invokeExpression = for { - sender <- accountGen - recipient <- accountGen - invokeExp <- invokeExpressionTransactionGen( - sender, + "created by InvokeExpressionTransaction and canceled by CancelLeaseTransaction" in { + val sender = TxHelpers.signer(210) + val recipient = TxHelpers.address(211) + + val invoke = TxHelpers.invokeExpression( TestCompiler(V6).compileFreeCall( s""" - |let lease = Lease(Address(base58'${recipient.toAddress.toString}'), ${10000.waves}) + |let lease = Lease(Address(base58'${recipient.toString}'), ${10000.waves}) |[ | lease, | BinaryEntry("leaseId", lease.calculateLeaseId()) |]""".stripMargin ), - 0.01.waves + sender ) - } yield ( - sender, - invokeExp, - recipient.toAddress - ) - - "created by InvokeExpressionTransaction and canceled by CancelLeaseTransaction" in forAll(invokeExpression) { case (sender, invoke, recipient) => - withRoute(AddrWithBalance.enoughBalances(sender), DomainPresets.ContinuationTransaction) { (d, r) => - d.appendBlock(invoke) - val leaseId = d.blockchain - .accountData(sender.toAddress, "leaseId") - .collect { case i: BinaryDataEntry => - i.value - } - .get - val expectedDetails = Seq(leaseId -> LeaseDetails(sender.publicKey, recipient, 10_000.waves, LeaseDetails.Status.Active, invoke.id(), 1)) - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(sender.toAddress, r, expectedDetails) - checkActiveLeasesFor(recipient, r, expectedDetails) + domain.appendBlock(TxHelpers.transfer(richAccount, sender.toAddress, 10001.waves), invoke) + val leaseId = domain.blockchain + .accountData(sender.toAddress, "leaseId") + .collect { case i: BinaryDataEntry => + i.value } + .get + val expectedDetails = + Seq( + leaseId -> LeaseDetails( + LeaseStaticInfo(sender.publicKey, recipient, TxPositiveAmount(10_000_00000000L), invoke.id(), 1), + LeaseDetails.Status.Active + ) + ) - d.appendMicroBlock(leaseCancelTransaction(sender, leaseId)) + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(sender.toAddress, route, expectedDetails) + checkActiveLeasesFor(recipient, route, expectedDetails) + } - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(sender.toAddress, r, Seq.empty) - checkActiveLeasesFor(recipient, r, Seq.empty) - } + domain.appendMicroBlock(leaseCancelTransaction(sender, leaseId)) + + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(sender.toAddress, route, Seq.empty) + checkActiveLeasesFor(recipient, route, Seq.empty) } } "created by EthereumTransaction and canceled by CancelLeaseTransaction" in { - val sender = signer(2).toEthKeyPair - val dApp = defaultSigner - val recipient = secondSigner.toAddress + val sender = signer(211).toEthKeyPair + val dApp = signer(212) + val recipient = TxHelpers.address(213) + val invoke = EthTxGenerator.generateEthInvoke( keyPair = sender, @@ -328,112 +295,113 @@ class LeaseRouteSpec args = Seq(Arg.Bytes(ByteStr(recipient.bytes)), Arg.Integer(10000.waves)), payments = Seq.empty ) - withRoute(Seq(AddrWithBalance(dApp.toAddress), AddrWithBalance(invoke.sender.toAddress))) { (d, r) => - d.appendBlock(setScriptTransaction(dApp)) - d.appendBlock(invoke) - val leaseId = d.blockchain - .accountData(dApp.toAddress, "leaseId") - .collect { case i: BinaryDataEntry => - i.value - } - .get - val expectedDetails = Seq(leaseId -> LeaseDetails(dApp.publicKey, recipient, 10_000.waves, LeaseDetails.Status.Active, invoke.id(), 1)) - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(dApp.toAddress, r, expectedDetails) - checkActiveLeasesFor(recipient, r, expectedDetails) + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq( + sender.toWavesAddress -> 0.005.waves, + dApp.toAddress -> 10_0001.waves + ), + fee = 0.002.waves + ), + setScriptTransaction(dApp), + invoke + ) + val leaseId = domain.blockchain + .accountData(dApp.toAddress, "leaseId") + .collect { case i: BinaryDataEntry => + i.value } + .get + val expectedDetails = + Seq( + leaseId -> LeaseDetails( + LeaseStaticInfo(dApp.publicKey, recipient, TxPositiveAmount(10_000_00000000L), invoke.id(), 1), + LeaseDetails.Status.Active + ) + ) - d.appendMicroBlock(leaseCancelTransaction(dApp, leaseId)) - - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(dApp.toAddress, r, Seq.empty) - checkActiveLeasesFor(recipient, r, Seq.empty) - } + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(dApp.toAddress, route, expectedDetails) + checkActiveLeasesFor(recipient, route, expectedDetails) } - } - val nestedInvocation = { - val proxy = TxHelpers.signer(1) - val target = TxHelpers.signer(2) - val recipient = TxHelpers.signer(3) + domain.appendMicroBlock(leaseCancelTransaction(dApp, leaseId)) - ( - (proxy, target, recipient.toAddress), - Seq( - setScriptTransaction(target), - SetScriptTransaction - .selfSigned( - TxVersion.V2, - proxy, - Some(TestCompiler(V5).compileContract(""" - |{-# STDLIB_VERSION 4 #-} - |{-# CONTENT_TYPE DAPP #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - | - |@Callable(inv) - |func callProxy(targetDapp: ByteVector, recipient: ByteVector, amount: Int) = { - | strict result = invoke(Address(targetDapp), "leaseTo", [recipient, amount], []) - | [] - |} - |""".stripMargin)), - 0.01.waves, - ntpTime.getTimestamp() - ) - .explicitGet() - ) - ) + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(dApp.toAddress, route, Seq.empty) + checkActiveLeasesFor(recipient, route, Seq.empty) + } } "created by nested invocations" in { - val ((proxy, target, recipient), transactions) = nestedInvocation - withRoute(Seq(AddrWithBalance(proxy.toAddress), AddrWithBalance(target.toAddress))) { (d, r) => - d.appendBlock(transactions*) - val ist = Signed - .invokeScript( - TxVersion.V2, - proxy, - proxy.toAddress, - Some( - FUNCTION_CALL( - FunctionHeader.User("callProxy"), - List( - CONST_BYTESTR(ByteStr(target.toAddress.bytes)).explicitGet(), - CONST_BYTESTR(ByteStr(recipient.bytes)).explicitGet(), - CONST_LONG(10_000.waves) - ) - ) - ), - Seq.empty, - 0.005.waves, - Asset.Waves, - ntpTime.getTimestamp() - ) - - d.appendBlock(ist) - val leaseId = d.blockchain - .accountData(target.toAddress, "leaseId") - .collect { case i: BinaryDataEntry => - i.value - } - .get + val proxy = TxHelpers.signer(221) + val target = TxHelpers.signer(222) + val recipient = TxHelpers.signer(223) + + val ist = TxHelpers.invoke( + proxy.toAddress, + Some("callProxy"), + List( + CONST_BYTESTR(ByteStr(target.toAddress.bytes)).explicitGet(), + CONST_BYTESTR(ByteStr(recipient.toAddress.bytes)).explicitGet(), + CONST_LONG(10_000.waves) + ), + invoker = proxy + ) - val expectedDetails = Seq(leaseId -> LeaseDetails(target.publicKey, recipient, 10_000.waves, LeaseDetails.Status.Active, ist.id(), 1)) + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq( + proxy.toAddress -> 1.waves, + target.toAddress -> 10_001.waves + ), + fee = 0.002.waves + ), + setScriptTransaction(target), + TxHelpers.setScript( + proxy, + TestCompiler(V5) + .compileContract("""@Callable(inv) + |func callProxy(targetDapp: ByteVector, recipient: ByteVector, amount: Int) = { + | strict result = invoke(Address(targetDapp), "leaseTo", [recipient, amount], []) + | [] + |} + |""".stripMargin) + ), + ist + ) - d.liquidAndSolidAssert { () => - checkActiveLeasesFor(target.toAddress, r, expectedDetails) - checkActiveLeasesFor(recipient, r, expectedDetails) + val leaseId = domain.blockchain + .accountData(target.toAddress, "leaseId") + .collect { case i: BinaryDataEntry => + i.value } + .get + + val expectedDetails = + Seq( + leaseId -> LeaseDetails( + LeaseStaticInfo(target.publicKey, recipient.toAddress, TxPositiveAmount(10_000_00000000L), ist.id(), 1), + LeaseDetails.Status.Active + ) + ) + + domain.liquidAndSolidAssert { () => + checkActiveLeasesFor(target.toAddress, route, expectedDetails) + checkActiveLeasesFor(recipient.toAddress, route, expectedDetails) } } } "returns leases created by invoke only for lease sender or recipient" in { - val invoker = TxHelpers.signer(1) - val dApp1 = TxHelpers.signer(2) - val dApp2 = TxHelpers.signer(3) - val leaseRecipient1 = TxHelpers.signer(4) - val leaseRecipient2 = TxHelpers.signer(5) + val invoker = TxHelpers.signer(231) + val dApp1 = TxHelpers.signer(232) + val dApp2 = TxHelpers.signer(233) + val leaseRecipient1 = TxHelpers.signer(234) + val leaseRecipient2 = TxHelpers.signer(235) val leaseAmount1 = 1 val leaseAmount2 = 2 @@ -465,90 +433,71 @@ class LeaseRouteSpec |} |""".stripMargin) - def checkForInvoke(invokeTx: Transaction & Authorized): Unit = - withRoute(AddrWithBalance.enoughBalances(dApp1, dApp2) :+ AddrWithBalance(invokeTx.sender.toAddress)) { case (d, r) => - def getLeaseId(address: Address) = - d.blockchain - .accountData(address, "leaseId") - .collect { case i: BinaryDataEntry => - i.value - } - .get - - d.appendBlock( - TxHelpers.setScript(dApp1, dAppScript1), - TxHelpers.setScript(dApp2, dAppScript2) - ) + def checkForInvoke(invokeTx: Transaction & Authorized): Unit = { + def getLeaseId(address: Address) = + domain.blockchain + .accountData(address, "leaseId") + .collect { case i: BinaryDataEntry => + i.value + } + .get + + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq( + invoker.toAddress -> 1.waves, + dApp1.toAddress -> 1.waves, + dApp2.toAddress -> 1.waves, + invoker.toEthKeyPair.toWavesAddress -> 1.waves + ), + fee = 0.005.waves + ), + TxHelpers.setScript(dApp1, dAppScript1), + TxHelpers.setScript(dApp2, dAppScript2) + ) - d.appendBlock(invokeTx) + domain.appendBlock(invokeTx) - val leaseDetails1 = Seq( - getLeaseId(dApp1.toAddress) -> LeaseDetails( - dApp1.publicKey, - leaseRecipient1.toAddress, - leaseAmount1, - LeaseDetails.Status.Active, - invokeTx.id(), - 3 - ) + val lease1Id = getLeaseId(dApp1.toAddress) + val leaseDetails1 = Seq( + lease1Id -> LeaseDetails( + LeaseStaticInfo(dApp1.publicKey, leaseRecipient1.toAddress, TxPositiveAmount.unsafeFrom(leaseAmount1), invokeTx.id(), 3), + LeaseDetails.Status.Active ) - val leaseDetails2 = Seq( - getLeaseId(dApp2.toAddress) -> LeaseDetails( - dApp2.publicKey, - leaseRecipient2.toAddress, - leaseAmount2, - LeaseDetails.Status.Active, - invokeTx.id(), - 3 - ) + ) + val lease2Id = getLeaseId(dApp2.toAddress) + val leaseDetails2 = Seq( + lease2Id -> LeaseDetails( + LeaseStaticInfo(dApp2.publicKey, leaseRecipient2.toAddress, TxPositiveAmount.unsafeFrom(leaseAmount2), invokeTx.id(), 3), + LeaseDetails.Status.Active ) + ) - checkActiveLeasesFor(invokeTx.sender.toAddress, r, Seq.empty) - checkActiveLeasesFor(dApp1.toAddress, r, leaseDetails1) - checkActiveLeasesFor(dApp2.toAddress, r, leaseDetails2) - checkActiveLeasesFor(leaseRecipient1.toAddress, r, leaseDetails1) - checkActiveLeasesFor(leaseRecipient2.toAddress, r, leaseDetails2) - } + checkActiveLeasesFor(invokeTx.sender.toAddress, route, Seq.empty) + checkActiveLeasesFor(dApp1.toAddress, route, leaseDetails1) + checkActiveLeasesFor(dApp2.toAddress, route, leaseDetails2) + checkActiveLeasesFor(leaseRecipient1.toAddress, route, leaseDetails1) + checkActiveLeasesFor(leaseRecipient2.toAddress, route, leaseDetails2) + + domain.appendBlock( + TxHelpers.leaseCancel(lease1Id, dApp1), + TxHelpers.leaseCancel(lease2Id, dApp2) + ) + } checkForInvoke(TxHelpers.invoke(dApp1.toAddress, Some("foo"), invoker = invoker)) checkForInvoke(EthTxGenerator.generateEthInvoke(invoker.toEthKeyPair, dApp1.toAddress, "foo", Seq.empty, Seq.empty)) } routePath("/info") in { - val blockchain = stub[Blockchain] - val commonApi = stub[CommonAccountsApi] - - val route = LeaseApiRoute( - restAPISettings, - stub[Wallet], - blockchain, - stub[TransactionPublisher], - SystemTime, - commonApi, - new RouteTimeout(60.seconds)(sharedScheduler) - ).route val lease = TxHelpers.lease() val leaseCancel = TxHelpers.leaseCancel(lease.id()) - (blockchain.transactionInfo _).when(lease.id()).returning(Some(TxMeta(Height(1), Status.Succeeded, 0L) -> lease)) - (commonApi.leaseInfo _) - .when(lease.id()) - .returning( - Some( - LeaseInfo( - lease.id(), - lease.id(), - lease.sender.toAddress, - lease.recipient.asInstanceOf[Address], - lease.amount.value, - 1, - LeaseInfo.Status.Canceled, - Some(2), - Some(leaseCancel.id()) - ) - ) - ) - (commonApi.leaseInfo _).when(*).returning(None) + domain.appendBlock(lease) + val leaseHeight = domain.blockchain.height + domain.appendBlock(leaseCancel) + val leaseCancelHeight = domain.blockchain.height Get(routePath(s"/info/${lease.id()}")) ~> route ~> check { val response = responseAs[JsObject] @@ -558,9 +507,9 @@ class LeaseRouteSpec | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", | "recipient" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", | "amount" : 1000000000, - | "height" : 1, + | "height" : $leaseHeight, | "status" : "canceled", - | "cancelHeight" : 2, + | "cancelHeight" : $leaseCancelHeight, | "cancelTransactionId" : "${leaseCancel.id()}" |}""".stripMargin) } @@ -571,9 +520,9 @@ class LeaseRouteSpec | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", | "recipient" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", | "amount" : 1000000000, - | "height" : 1, + | "height" : $leaseHeight, | "status" : "canceled", - | "cancelHeight" : 2, + | "cancelHeight" : $leaseCancelHeight, | "cancelTransactionId" : "${leaseCancel.id()}" |}, { @@ -582,9 +531,9 @@ class LeaseRouteSpec | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", | "recipient" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", | "amount" : 1000000000, - | "height" : 1, + | "height" : $leaseHeight, | "status" : "canceled", - | "cancelHeight" : 2, + | "cancelHeight" : $leaseCancelHeight, | "cancelTransactionId" : "${leaseCancel.id()}" |}]""".stripMargin) diff --git a/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala b/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala index 667863482b..bf1c804f30 100644 --- a/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala @@ -13,8 +13,7 @@ import com.wavesplatform.lang.v1.compiler.Terms.{CONST_LONG, FUNCTION_CALL} import com.wavesplatform.protobuf.transaction.{PBSignedTransaction, PBTransactions} import com.wavesplatform.protobuf.utils.PBUtils import com.wavesplatform.settings.Constants -import com.wavesplatform.state.Blockchain -import com.wavesplatform.state.reader.SnapshotBlockchain +import com.wavesplatform.state.{Blockchain, SnapshotBlockchain} import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.assets.* @@ -158,9 +157,9 @@ class ProtoVersionTransactionsSpec (reissueTx.json() \ "chainId").asOpt[Byte].value shouldBe reissueTx.chainId (burnTx.json() \ "chainId").asOpt[Byte].value shouldBe burnTx.chainId - issueTx.isProtobufVersion shouldBe true - reissueTx.isProtobufVersion shouldBe true - burnTx.isProtobufVersion shouldBe true + PBSince.affects(issueTx) shouldBe true + PBSince.affects(reissueTx) shouldBe true + PBSince.affects(burnTx) shouldBe true } "DataTransaction" in { @@ -183,7 +182,7 @@ class ProtoVersionTransactionsSpec (dataTx.json() \ "chainId").asOpt[Byte].value shouldBe dataTx.chainId - dataTx.isProtobufVersion shouldBe true + PBSince.affects(dataTx) shouldBe true } "ExchangeTransaction" in { @@ -210,7 +209,7 @@ class ProtoVersionTransactionsSpec (exchangeTx.json() \ "chainId").asOpt[Byte].value shouldBe exchangeTx.chainId - exchangeTx.isProtobufVersion shouldBe true + PBSince.affects(exchangeTx) shouldBe true } "InvokeScriptTransaction" in { @@ -247,7 +246,7 @@ class ProtoVersionTransactionsSpec (invokeScriptTx.json() \ "chainId").asOpt[Byte].value shouldBe invokeScriptTx.chainId - invokeScriptTx.isProtobufVersion shouldBe true + PBSince.affects(invokeScriptTx) shouldBe true } "LeaseTransaction/LeaseCancelTransaction" in { @@ -287,8 +286,8 @@ class ProtoVersionTransactionsSpec (leaseTx.json() \ "chainId").asOpt[Byte].value shouldBe leaseTx.chainId (leaseCancelTx.json() \ "chainId").asOpt[Byte].value shouldBe leaseCancelTx.chainId - leaseTx.isProtobufVersion shouldBe true - leaseCancelTx.isProtobufVersion shouldBe true + PBSince.affects(leaseTx) shouldBe true + PBSince.affects(leaseCancelTx) shouldBe true } "TransferTransaction" in { @@ -316,7 +315,7 @@ class ProtoVersionTransactionsSpec (transferTx.json() \ "chainId").asOpt[Byte].value shouldBe transferTx.chainId - transferTx.isProtobufVersion shouldBe true + PBSince.affects(transferTx) shouldBe true } "MassTransferTransaction" in { @@ -344,7 +343,7 @@ class ProtoVersionTransactionsSpec (massTransferTx.json() \ "chainId").asOpt[Byte].value shouldBe massTransferTx.chainId - massTransferTx.isProtobufVersion shouldBe true + PBSince.affects(massTransferTx) shouldBe true } "SetScriptTransaction" in { @@ -389,7 +388,7 @@ class ProtoVersionTransactionsSpec decode(base64Str) shouldBe setAssetScriptTx - setAssetScriptTx.isProtobufVersion shouldBe true + PBSince.affects(setAssetScriptTx) shouldBe true } "SponsorshipTransaction" in { @@ -414,7 +413,7 @@ class ProtoVersionTransactionsSpec (sponsorshipTx.json() \ "chainId").asOpt[Byte].value shouldBe sponsorshipTx.chainId - sponsorshipTx.isProtobufVersion shouldBe true + PBSince.affects(sponsorshipTx) shouldBe true } "UpdateAssetInfoTransaction" in { @@ -452,7 +451,7 @@ class ProtoVersionTransactionsSpec (updateAssetInfoTx.json() \ "version").as[Byte] shouldBe TxVersion.V1 } - def checkProofs(response: HttpResponse, tx: VersionedTransaction): (Proofs, JsObject) = { + def checkProofs(response: HttpResponse, tx: Versioned): (Proofs, JsObject) = { response.status shouldBe StatusCodes.OK (responseAs[JsObject] \ "version").as[Byte] shouldBe tx.version diff --git a/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala b/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala index f05881ba67..d714fd1db6 100644 --- a/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala @@ -10,8 +10,7 @@ import com.wavesplatform.lang.directives.values.V5 import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 import com.wavesplatform.lang.v1.traits.domain.{Lease, Recipient} import com.wavesplatform.network.TransactionPublisher -import com.wavesplatform.state.reader.SnapshotBlockchain -import com.wavesplatform.state.{AccountScriptInfo, Blockchain} +import com.wavesplatform.state.{AccountScriptInfo, Blockchain, SnapshotBlockchain} import com.wavesplatform.test.TestTime import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.GenericError diff --git a/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala index ad6aca80e5..916fd75ac9 100644 --- a/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala @@ -2,48 +2,38 @@ package com.wavesplatform.http import akka.http.scaladsl.model.* import akka.http.scaladsl.model.headers.Accept -import akka.http.scaladsl.server.Route import com.wavesplatform.account.KeyPair -import com.wavesplatform.api.common.{CommonTransactionsApi, TransactionMeta} -import com.wavesplatform.api.http.ApiError.* +import com.wavesplatform.api.http.ApiError.{ScriptExecutionError as _, *} import com.wavesplatform.api.http.{CustomJson, RouteTimeout, TransactionsApiRoute} import com.wavesplatform.block.Block -import com.wavesplatform.block.Block.TransactionProof +import com.wavesplatform.common.merkle.Merkle import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, *} -import com.wavesplatform.db.WithDomain import com.wavesplatform.db.WithState.AddrWithBalance -import com.wavesplatform.features.BlockchainFeatures as BF -import com.wavesplatform.history.{Domain, defaultSigner, settingsWithFeatures} -import com.wavesplatform.lang.directives.values.V5 +import com.wavesplatform.history.defaultSigner +import com.wavesplatform.lang.directives.values.{V5, V7} import com.wavesplatform.lang.v1.FunctionHeader -import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BOOLEAN, CONST_LONG, FUNCTION_CALL} +import com.wavesplatform.lang.v1.compiler.Terms.{ARR, CONST_BOOLEAN, CONST_BYTESTR, CONST_LONG, CONST_STRING, FUNCTION_CALL} import com.wavesplatform.lang.v1.compiler.TestCompiler -import com.wavesplatform.lang.v1.traits.domain.LeaseCancel -import com.wavesplatform.network.TransactionPublisher -import com.wavesplatform.state.TxMeta.Status -import com.wavesplatform.state.reader.LeaseDetails -import com.wavesplatform.state.{Blockchain, Height, InvokeScriptResult, TxMeta} +import com.wavesplatform.protobuf.transaction.PBTransactions +import com.wavesplatform.settings.WavesSettings +import com.wavesplatform.state.{BinaryDataEntry, EmptyDataEntry, InvokeScriptResult, StringDataEntry} import com.wavesplatform.test.* -import com.wavesplatform.test.DomainPresets.{RideV6, TransactionStateSnapshot} -import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} +import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.TxHelpers.defaultAddress -import com.wavesplatform.transaction.TxValidationError.GenericError +import com.wavesplatform.transaction.TxValidationError.ScriptExecutionError import com.wavesplatform.transaction.assets.exchange.{Order, OrderType} -import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction} import com.wavesplatform.transaction.serialization.impl.InvokeScriptTxSerializer +import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment -import com.wavesplatform.transaction.smart.script.trace.{AccountVerifierTrace, TracedResult} -import com.wavesplatform.transaction.smart.{InvokeScriptTransaction, SetScriptTransaction} +import com.wavesplatform.transaction.smart.script.trace.AccountVerifierTrace import com.wavesplatform.transaction.transfer.TransferTransaction +import com.wavesplatform.transaction.utils.EthConverters.* import com.wavesplatform.transaction.utils.Signed -import com.wavesplatform.transaction.{Asset, AssetIdLength, CreateAliasTransaction, EthTxGenerator, TxHelpers, TxVersion} +import com.wavesplatform.transaction.{Asset, AssetIdLength, EthTxGenerator, TxHelpers, TxVersion} import com.wavesplatform.utils.{EthEncoding, EthHelpers, SharedSchedulerMixin} -import com.wavesplatform.{BlockGen, BlockchainStubHelpers, TestValues, TestWallet} -import monix.reactive.Observable +import com.wavesplatform.{BlockGen, TestValues, crypto} import org.scalacheck.Gen.* -import org.scalacheck.{Arbitrary, Gen} -import org.scalamock.scalatest.MockFactory import org.scalatest.{Assertion, OptionValues} import play.api.libs.json.* import play.api.libs.json.Json.JsValueWrapper @@ -55,29 +45,30 @@ import scala.util.Random class TransactionsRouteSpec extends RouteSpec("/transactions") with RestAPISettingsHelper - with MockFactory with BlockGen with OptionValues - with TestWallet - with WithDomain + with SharedDomain with EthHelpers - with BlockchainStubHelpers with SharedSchedulerMixin { - private val blockchain = mock[Blockchain] - private val utxPoolSynchronizer = mock[TransactionPublisher] - private val addressTransactions = mock[CommonTransactionsApi] - private val utxPoolSize = mockFunction[Int] - private val testTime = new TestTime + private val testTime = new TestTime + + private val richAccount = TxHelpers.signer(10001) + private val richAddress = richAccount.toAddress + + override def settings: WavesSettings = DomainPresets.TransactionStateSnapshot.copy( + restAPISettings = restAPISettings.copy(transactionsByAddressLimit = 5) + ) + override def genesisBalances: Seq[AddrWithBalance] = Seq(AddrWithBalance(richAddress, 1_000_000.waves)) private val transactionsApiRoute = new TransactionsApiRoute( - restAPISettings, - addressTransactions, - testWallet, - blockchain, - () => blockchain, - utxPoolSize, - utxPoolSynchronizer, + settings.restAPISettings, + domain.transactionsApi, + domain.wallet, + domain.blockchain, + () => domain.blockchain, + () => domain.utxPool.size, + (tx, _) => Future.successful(domain.utxPool.putIfNew(tx, forceValidate = true)), testTime, new RouteTimeout(60.seconds)(sharedScheduler) ) @@ -86,6 +77,20 @@ class TransactionsRouteSpec private val invalidBase58Gen = alphaNumStr.map(_ + "0") + private val failableContract = TestCompiler(V5).compileContract("""@Callable(i) + |func testCall(succeed: Boolean) = { + | let res = if (succeed || + | sigVerify(base58'', base58'', base58'') || + | sigVerify(base58'', base58'', base58'') || + | sigVerify(base58'', base58'', base58'') || + | sigVerify(base58'', base58'', base58'') || + | sigVerify(base58'', base58'', base58'') || + | sigVerify(base58'', base58'', base58'') || + | sigVerify(base58'', base58'', base58'')) then "ok" else throw("err") + | [StringEntry("foo", res)] + |} + |""".stripMargin) + routePath("/calculateFee") - { "waves" in { val transferTx = Json.obj( @@ -96,7 +101,6 @@ class TransactionsRouteSpec "senderPublicKey" -> TestValues.keyPair.publicKey, "recipient" -> TestValues.address ) - (addressTransactions.calculateFee _).expects(*).returning(Right((Asset.Waves, 100000L, 0L))).once() Post(routePath("/calculateFee"), transferTx) ~> route ~> check { status shouldEqual StatusCodes.OK @@ -106,397 +110,392 @@ class TransactionsRouteSpec } "asset" in { - val asset: IssuedAsset = TestValues.asset + val issuer = TxHelpers.signer(270) + val issue = TxHelpers.issue(issuer) + + domain.appendBlock( + TxHelpers.transfer(richAccount, issuer.toAddress, 20.waves), + issue, + TxHelpers.sponsor(issue.asset, Some(5L), issuer) + ) + val transferTx = Json.obj( "type" -> 4, "version" -> 2, "amount" -> 1000000, - "feeAssetId" -> asset.id.toString, + "feeAssetId" -> issue.asset.id.toString, "senderPublicKey" -> TestValues.keyPair.publicKey, "recipient" -> TestValues.address ) - (addressTransactions.calculateFee _).expects(*).returning(Right((asset, 5L, 0L))).once() - Post(routePath("/calculateFee"), transferTx) ~> route ~> check { status shouldEqual StatusCodes.OK - (responseAs[JsObject] \ "feeAssetId").as[String] shouldBe asset.id.toString + (responseAs[JsObject] \ "feeAssetId").as[String] shouldBe issue.asset.id.toString (responseAs[JsObject] \ "feeAmount").as[Long] shouldEqual 5 } } } - private def mkRoute(d: Domain): Route = - seal( - new TransactionsApiRoute( - restAPISettings, - d.commonApi.transactions, - testWallet, - d.blockchain, - () => d.blockchain.snapshotBlockchain, - () => 0, - (t, _) => d.commonApi.transactions.broadcastTransaction(t), - ntpTime, - new RouteTimeout(60.seconds)(sharedScheduler) - ).route - ) - "returns lease details for lease cancel transaction" in { - val sender = testWallet.generateNewAccount().get - val recipient = testWallet.generateNewAccount().get + val sender = TxHelpers.signer(20) + val recipient = TxHelpers.signer(21) - val balances = Seq( - AddrWithBalance(sender.toAddress, 10.waves), - AddrWithBalance(recipient.toAddress, 10.waves) + val lease = TxHelpers.lease(sender, recipient.toAddress, 5.waves) + val leaseCancel = TxHelpers.leaseCancel(lease.id(), sender) + + domain.appendBlock( + TxHelpers.transfer(richAccount, sender.toAddress, 6.waves), + lease ) - withDomain(settingsWithFeatures(BF.SmartAccounts), balances) { d => - val lease = LeaseTransaction.selfSigned(2.toByte, sender, recipient.toAddress, 5.waves, 0.001.waves, ntpTime.getTimestamp()).explicitGet() - val leaseCancel = LeaseCancelTransaction.selfSigned(2.toByte, sender, lease.id(), 0.001.waves, ntpTime.getTimestamp()).explicitGet() - val sealedRoute = mkRoute(d) - - d.appendBlock(lease) - - def expectedJson(status: String, cancelHeight: Option[Int] = None, cancelTransactionId: Option[ByteStr] = None): JsObject = - Json - .parse(s"""{ - | "type" : 9, - | "id" : "${leaseCancel.id()}", - | "sender" : "${sender.toAddress}", - | "senderPublicKey" : "${sender.publicKey}", - | "fee" : ${0.001.waves}, - | "feeAssetId" : null, - | "timestamp" : ${leaseCancel.timestamp}, - | "proofs" : [ "${leaseCancel.signature}" ], - | "version" : 2, - | "leaseId" : "${lease.id()}", - | "chainId" : 84, - | "spentComplexity" : 0, - | "lease" : { - | "id" : "${lease.id()}", - | "originTransactionId" : "${lease.id()}", - | "sender" : "${sender.toAddress}", - | "recipient" : "${recipient.toAddress}", - | "amount" : ${5.waves}, - | "height" : 2, - | "status" : "$status", - | "cancelHeight" : ${cancelHeight.getOrElse("null")}, - | "cancelTransactionId" : ${cancelTransactionId.fold("null")("\"" + _ + "\"")} - | } - |}""".stripMargin) - .as[JsObject] - - d.utxPool.putIfNew(leaseCancel) - - withClue(routePath("/unconfirmed")) { - Get(routePath(s"/unconfirmed")) ~> sealedRoute ~> check { - responseAs[Seq[JsObject]].head should matchJson(expectedJson("active") - "spentComplexity") - } + val leaseHeight = domain.blockchain.height + + def expectedJson(status: String, height: Int, cancelHeight: Option[Int] = None, cancelTransactionId: Option[ByteStr] = None): JsObject = + Json + .parse(s"""{ + | "applicationStatus": "succeeded", + | "type" : 9, + | "id" : "${leaseCancel.id()}", + | "sender" : "${sender.toAddress}", + | "senderPublicKey" : "${sender.publicKey}", + | "fee" : ${0.001.waves}, + | "feeAssetId" : null, + | "timestamp" : ${leaseCancel.timestamp}, + | "proofs" : [ "${leaseCancel.signature}" ], + | "version" : 2, + | "leaseId" : "${lease.id()}", + | "chainId" : 84, + | "spentComplexity" : 0, + | "lease" : { + | "id" : "${lease.id()}", + | "originTransactionId" : "${lease.id()}", + | "sender" : "${sender.toAddress}", + | "recipient" : "${recipient.toAddress}", + | "amount" : ${5.waves}, + | "height" : $height, + | "status" : "$status", + | "cancelHeight" : ${cancelHeight.getOrElse("null")}, + | "cancelTransactionId" : ${cancelTransactionId.fold("null")("\"" + _ + "\"")} + | } + |}""".stripMargin) + .as[JsObject] + + domain.utxPool.putIfNew(leaseCancel) + + withClue(routePath("/unconfirmed")) { + Get(routePath(s"/unconfirmed")) ~> route ~> check { + responseAs[Seq[JsObject]].head should matchJson(expectedJson("active", leaseHeight) - "spentComplexity" - "applicationStatus") } + } - d.appendBlock(leaseCancel) + domain.appendBlock(leaseCancel) - val cancelTransactionJson = expectedJson("canceled", Some(3), Some(leaseCancel.id())) ++ Json.obj("height" -> 3) + val cancelHeight = domain.blockchain.height + val cancelTransactionJson = + expectedJson("canceled", leaseHeight, Some(cancelHeight), Some(leaseCancel.id())) ++ Json.obj("height" -> cancelHeight) - withClue(routePath("/address/{address}/limit/{limit}")) { - Get(routePath(s"/address/${recipient.toAddress}/limit/10")) ~> sealedRoute ~> check { - val json = (responseAs[JsArray] \ 0 \ 0).as[JsObject] - json should matchJson(cancelTransactionJson) - } + withClue(routePath("/address/{address}/limit/{limit}")) { + Get(routePath(s"/address/${recipient.toAddress}/limit/5")) ~> route ~> check { + val json = (responseAs[JsArray] \ 0 \ 0).as[JsObject] + json should matchJson(cancelTransactionJson) } + } - withClue(routePath("/info/{id}")) { - Get(routePath(s"/info/${leaseCancel.id()}")) ~> sealedRoute ~> check { - responseAs[JsObject] should matchJson(cancelTransactionJson) - } + withClue(routePath("/info/{id}")) { + Get(routePath(s"/info/${leaseCancel.id()}")) ~> route ~> check { + responseAs[JsObject] should matchJson(cancelTransactionJson) } } } - routePath("/address/{address}/limit/{limit}") - { - val bytes32StrGen = bytes32gen.map(Base58.encode) - val addressGen = accountGen.map(_.toAddress.toString) + "provides state changes in both transactions by address and by id" in { + val dapp = TxHelpers.signer(230) + val invoker = TxHelpers.signer(231) + + val invoke = TxHelpers.invoke(dapp.toAddress) + + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq( + dapp.toAddress -> 1.waves, + invoker.toAddress -> 1.waves + ), + fee = 0.002.waves + ), + TxHelpers.setScript( + dapp, + TestCompiler(V7) + .compileContract(s"""@Callable(i) + |func default() = [ + | StringEntry("key3", "some string"), + | BinaryEntry("key4", base58'encoded'), + | DeleteEntry("key5"), + | ScriptTransfer(Address(base58'${invoker.toAddress}'), 100, unit) + |] + |""".stripMargin) + ), + invoke + ) + + val expectedStateChanges = Json.toJsObject( + InvokeScriptResult( + Seq(StringDataEntry("key3", "some string"), BinaryDataEntry("key4", ByteStr.decodeBase58("encoded").get), EmptyDataEntry("key5")), + Seq(InvokeScriptResult.Payment(invoker.toAddress, Asset.Waves, 100)) + ) + ) + + Get(routePath(s"/address/${invoker.toAddress}/limit/1")) ~> route ~> check { + status shouldEqual StatusCodes.OK + (responseAs[JsArray] \ 0 \ 0 \ "stateChanges").as[JsObject] shouldBe expectedStateChanges + } + + Get(routePath(s"/info/${invoke.id()}")) ~> route ~> check { + status shouldEqual StatusCodes.OK + (responseAs[JsObject] \ "stateChanges").as[JsObject] shouldBe expectedStateChanges + } + + Get(routePath(s"/info?id=${invoke.id()}")) ~> route ~> check { + status shouldEqual StatusCodes.OK + (responseAs[JsArray] \ 0 \ "stateChanges").as[JsObject] shouldBe expectedStateChanges + } + Post( + "/transactions/info", + HttpEntity(ContentTypes.`application/json`, Json.obj("ids" -> Json.arr(invoke.id())).toString().getBytes) + ) ~> route ~> check { + status shouldEqual StatusCodes.OK + (responseAs[JsArray] \ 0 \ "stateChanges").as[JsObject] shouldBe expectedStateChanges + } + } + + "provides lease and lease cancel actions stateChanges" in { + val dapp = TxHelpers.signer(235) + val caller = TxHelpers.signer(236) + val leaseRecipient = TxHelpers.address(237) + + val originalLease = TxHelpers.lease(dapp, leaseRecipient, 20_00000000L) + val invoke = + TxHelpers.invoke(dapp.toAddress, Some("testLease"), Seq(CONST_LONG(1000), CONST_BYTESTR(originalLease.id()).explicitGet()), invoker = caller) + + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq( + dapp.toAddress -> 50.waves, + caller.toAddress -> 1.waves + ), + fee = 0.002.waves + ), + originalLease, + TxHelpers.setScript( + dapp, + TestCompiler(V7) + .compileContract(s"""@Callable(i) + |func testLease(amount: Int, id: ByteVector) = { + |let lease = Lease(Address(base58'$leaseRecipient'), amount) + |let leaseId = calculateLeaseId(lease) + |[ + | LeaseCancel(id), + | lease, + | BinaryEntry("leaseId", leaseId) + |] + |} + |""".stripMargin) + ) + ) + + val originalHeight = domain.blockchain.height + + domain.appendBlock(invoke) + + val newLeaseId = domain.blockchain + .accountData(dapp.toAddress, "leaseId") + .collect { case BinaryDataEntry(_, id) => id } + .value + + val expectedJson = + s"""{ + | "data": [ { + | "type": "binary", + | "key": "leaseId", + | "value": "${newLeaseId.base64}" + | } ], + | "transfers": [], + | "issues": [], + | "reissues": [], + | "burns": [], + | "sponsorFees": [], + | "leases" : [ { + | "id" : "$newLeaseId", + | "originTransactionId" : "${invoke.id()}", + | "sender" : "${dapp.toAddress}", + | "recipient" : "$leaseRecipient", + | "amount" : 1000, + | "height" : ${originalHeight + 1}, + | "status" : "active", + | "cancelHeight" : null, + | "cancelTransactionId" : null + | } ], + | "leaseCancels" : [ { + | "id" : "${originalLease.id()}", + | "originTransactionId" : "${originalLease.id()}", + | "sender" : "${dapp.toAddress}", + | "recipient" : "$leaseRecipient", + | "amount" : ${20.waves}, + | "height" : $originalHeight, + | "status" : "canceled", + | "cancelHeight" : ${originalHeight + 1}, + | "cancelTransactionId" : "${invoke.id()}" + | } ], + | "invokes": [] + |}""".stripMargin + + Get(routePath(s"/address/${dapp.toAddress}/limit/1")) ~> route ~> check { + status shouldEqual StatusCodes.OK + (responseAs[JsArray] \ 0 \ 0 \ "stateChanges").as[JsObject] should matchJson(expectedJson) + } + + Get(routePath(s"/info/${invoke.id()}")) ~> route ~> check { + status shouldEqual StatusCodes.OK + (responseAs[JsObject] \ "stateChanges").as[JsObject] should matchJson(expectedJson) + } + + Get(routePath(s"/info?id=${invoke.id()}")) ~> route ~> check { + status shouldEqual StatusCodes.OK + (responseAs[JsArray] \ 0 \ "stateChanges").as[JsObject] should matchJson(expectedJson) + } + + Post( + routePath("/info"), + HttpEntity(ContentTypes.`application/json`, Json.obj("ids" -> Json.arr(invoke.id())).toString()) + ) ~> route ~> check { + status shouldEqual StatusCodes.OK + (responseAs[JsArray] \ 0 \ "stateChanges").as[JsObject] should matchJson(expectedJson) + } + } + + routePath("/address/{address}/limit/{limit}") - { + val txByAddressLimit = settings.restAPISettings.transactionsByAddressLimit "handles parameter errors with corresponding responses" - { - "invalid address" in { - forAll(bytes32StrGen) { badAddress => - Get(routePath(s"/address/$badAddress/limit/1")) ~> route should produce(InvalidAddress) - } + "invalid address bytes" in { + Get(routePath(s"/address/${Base58.encode(new Array[Byte](24))}/limit/1")) ~> route should produce(InvalidAddress) + } + + "invalid base58 encoding" in { + Get(routePath(s"/address/${"1" * 23 + "0"}/limit/1")) ~> route should produce( + CustomValidationError("requirement failed: Wrong char '0' in Base58 string '111111111111111111111110'") + ) } "invalid limit" - { "limit is too big" in { - forAll(addressGen, choose(MaxTransactionsPerRequest + 1, Int.MaxValue).label("limitExceeded")) { case (address, limit) => - Get(routePath(s"/address/$address/limit/$limit")) ~> route should produce(TooBigArrayAllocation) - } + Get( + routePath(s"/address/$richAddress/limit/${txByAddressLimit + 1}") + ) ~> route should produce(TooBigArrayAllocation) } } "invalid after" in { - forAll(addressGen, choose(1, MaxTransactionsPerRequest).label("limitCorrect"), invalidBase58Gen) { case (address, limit, invalidBase58) => - Get(routePath(s"/address/$address/limit/$limit?after=$invalidBase58")) ~> route ~> check { - status shouldEqual StatusCodes.BadRequest - (responseAs[JsObject] \ "message").as[String] shouldEqual s"Unable to decode transaction id $invalidBase58" - } + val invalidBase58String = "1" * 23 + "0" + Get(routePath(s"/address/$richAddress/limit/$txByAddressLimit?after=$invalidBase58String")) ~> route ~> check { + status shouldEqual StatusCodes.BadRequest + (responseAs[JsObject] \ "message").as[String] shouldEqual s"Unable to decode transaction id $invalidBase58String" } } } "returns 200 if correct params provided" - { "address and limit" in { - forAll(addressGen, choose(1, MaxTransactionsPerRequest).label("limitCorrect")) { case (address, limit) => - (addressTransactions.aliasesOfAddress _).expects(*).returning(Observable.empty).once() - (addressTransactions.transactionsByAddress _).expects(*, *, *, None).returning(Observable.empty).once() - (() => blockchain.activatedFeatures).expects().returns(Map.empty) - Get(routePath(s"/address/$address/limit/$limit")) ~> route ~> check { - status shouldEqual StatusCodes.OK - } + Get(routePath(s"/address/$richAddress/limit/$txByAddressLimit")) ~> route ~> check { + status shouldEqual StatusCodes.OK } } "address, limit and after" in { - forAll(addressGen, choose(1, MaxTransactionsPerRequest).label("limitCorrect"), bytes32StrGen) { case (address, limit, txId) => - (addressTransactions.aliasesOfAddress _).expects(*).returning(Observable.empty).once() - (addressTransactions.transactionsByAddress _).expects(*, *, *, *).returning(Observable.empty).once() - (() => blockchain.activatedFeatures).expects().returns(Map.empty) - Get(routePath(s"/address/$address/limit/$limit?after=$txId")) ~> route ~> check { - status shouldEqual StatusCodes.OK - } + Get(routePath(s"/address/$richAddress/limit/$txByAddressLimit?after=${ByteStr(new Array[Byte](32))}")) ~> route ~> check { + status shouldEqual StatusCodes.OK } } } - "provides stateChanges" in forAll(accountGen) { account => - val transaction = TxHelpers.invoke(account.toAddress) - - (() => blockchain.activatedFeatures).expects().returns(Map.empty).anyNumberOfTimes() - (addressTransactions.aliasesOfAddress _).expects(*).returning(Observable.empty).once() - (addressTransactions.transactionsByAddress _) - .expects(account.toAddress, *, *, None) - .returning(Observable(TransactionMeta.Invoke(Height(1), transaction, Status.Succeeded, 0L, Some(InvokeScriptResult())))) - .once() - - Get(routePath(s"/address/${account.toAddress}/limit/1")) ~> route ~> check { - status shouldEqual StatusCodes.OK - (responseAs[JsArray] \ 0 \ 0 \ "stateChanges").as[JsObject] shouldBe Json.toJsObject(InvokeScriptResult()) - } - } - - "provides lease and lease cancel actions stateChanges" in { - val invokeAddress = accountGen.sample.get.toAddress - val leaseId1 = ByteStr(bytes32gen.sample.get) - val leaseId2 = ByteStr(bytes32gen.sample.get) - val leaseCancelId = ByteStr(bytes32gen.sample.get) - val recipientAddress = accountGen.sample.get.toAddress - val recipientAlias = aliasGen.sample.get - val invoke = TxHelpers.invoke(invokeAddress) - val leaseActionAmount1 = 100 - val leaseActionAmount2 = 200 - val scriptResult = InvokeScriptResult( - leases = Seq( - InvokeScriptResult.Lease(recipientAddress, leaseActionAmount1, 1, leaseId1), - InvokeScriptResult.Lease(recipientAlias, leaseActionAmount2, 3, leaseId2) - ), - leaseCancels = Seq(LeaseCancel(leaseCancelId)) - ) - - (blockchain.leaseDetails _) - .expects(leaseId1) - .returning(Some(LeaseDetails(TestValues.keyPair.publicKey, TestValues.address, 123, LeaseDetails.Status.Active, leaseId1, 1))) - .anyNumberOfTimes() - (blockchain.leaseDetails _) - .expects(leaseId2) - .returning(Some(LeaseDetails(TestValues.keyPair.publicKey, TestValues.address, 123, LeaseDetails.Status.Active, leaseId2, 1))) - .anyNumberOfTimes() - (blockchain.leaseDetails _) - .expects(leaseCancelId) - .returning( - Some( - LeaseDetails( - TestValues.keyPair.publicKey, - TestValues.address, - 123, - LeaseDetails.Status.Cancelled(2, Some(leaseCancelId)), - leaseCancelId, - 1 - ) - ) - ) - .anyNumberOfTimes() - (blockchain.transactionMeta _).expects(leaseId1).returning(Some(TxMeta(Height(1), Status.Succeeded, 0L))).anyNumberOfTimes() - (blockchain.transactionMeta _).expects(leaseId2).returning(Some(TxMeta(Height(1), Status.Succeeded, 0L))).anyNumberOfTimes() - (blockchain.transactionMeta _).expects(leaseCancelId).returning(Some(TxMeta(Height(1), Status.Succeeded, 0L))).anyNumberOfTimes() - - (() => blockchain.activatedFeatures).expects().returning(Map.empty).anyNumberOfTimes() - (addressTransactions.aliasesOfAddress _).expects(*).returning(Observable.empty).once() - (addressTransactions.transactionsByAddress _) - .expects(invokeAddress, *, *, None) - .returning(Observable(TransactionMeta.Invoke(Height(1), invoke, Status.Succeeded, 0L, Some(scriptResult)))) - .once() - (blockchain.resolveAlias _).expects(recipientAlias).returning(Right(recipientAddress)) - - Get(routePath(s"/address/$invokeAddress/limit/1")) ~> route ~> check { - status shouldEqual StatusCodes.OK - val json = (responseAs[JsArray] \ 0 \ 0 \ "stateChanges").as[JsObject] - json should matchJson(s"""{ - | "data": [], - | "transfers": [], - | "issues": [], - | "reissues": [], - | "burns": [], - | "sponsorFees": [], - | "leases": [ - | { - | "id": "$leaseId1", - | "originTransactionId": "$leaseId1", - | "sender": "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient": "$recipientAddress", - | "amount": $leaseActionAmount1, - | "height": 1, - | "status":"active", - | "cancelHeight" : null, - | "cancelTransactionId" : null - | }, - | { - | "id": "$leaseId2", - | "originTransactionId": "$leaseId2", - | "sender": "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient": "$recipientAddress", - | "amount": $leaseActionAmount2, - | "height": 1, - | "status":"active", - | "cancelHeight" : null, - | "cancelTransactionId" : null - | } - | ], - | "leaseCancels": [ - | { - | "id": "$leaseCancelId", - | "originTransactionId": "$leaseCancelId", - | "sender": "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient": "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "amount": 123, - | "height": 1, - | "status":"canceled", - | "cancelHeight" : 2, - | "cancelTransactionId" : "$leaseCancelId" - | } - | ], - | "invokes": [] - |}""".stripMargin) - } - } - "large-significand-format" in { - withDomain(RideV6) { d => - val tx = TxHelpers.transfer() - d.appendBlock(tx) - val route = seal( - transactionsApiRoute - .copy(blockchain = d.blockchain, compositeBlockchain = () => d.blockchain.snapshotBlockchain, commonApi = d.transactionsApi) - .route - ) - Get(routePath(s"/address/$defaultAddress/limit/1")) ~> Accept(CustomJson.jsonWithNumbersAsStrings) ~> route ~> check { - val result = responseAs[JsArray] \ 0 \ 0 - (result \ "amount").as[String] shouldBe tx.amount.value.toString - (result \ "fee").as[String] shouldBe tx.fee.value.toString - - (result \ "height").as[Int] shouldBe 1 - (result \ "spentComplexity").as[Int] shouldBe 0 - (result \ "version").as[Int] shouldBe tx.version - (result \ "type").as[Int] shouldBe tx.tpe.id - (result \ "timestamp").as[Long] shouldBe tx.timestamp - } + val tx = TxHelpers.transfer(richAccount, TxHelpers.address(930), 10.waves) + domain.appendBlock(tx) + Get(routePath(s"/address/$richAddress/limit/1")) ~> Accept(CustomJson.jsonWithNumbersAsStrings) ~> route ~> check { + val result = responseAs[JsArray] \ 0 \ 0 + (result \ "amount").as[String] shouldBe tx.amount.value.toString + (result \ "fee").as[String] shouldBe tx.fee.value.toString + + (result \ "height").as[Int] shouldBe domain.blockchain.height + (result \ "spentComplexity").as[Int] shouldBe 0 + (result \ "version").as[Int] shouldBe tx.version + (result \ "type").as[Int] shouldBe tx.tpe.id + (result \ "timestamp").as[Long] shouldBe tx.timestamp } } } routePath("/info/{id}") - { "returns meta for eth transfer" in { - val blockchain = createBlockchainStub { blockchain => - blockchain.stub.creditBalance(TxHelpers.defaultEthAddress, Waves) - (blockchain.wavesBalances _).when(*).returns(Map(TxHelpers.defaultEthAddress -> Long.MaxValue / 3, TxHelpers.secondAddress -> 0L)) - blockchain.stub.activateAllFeatures() - } + val ethAccount = TxHelpers.signer(240).toEthKeyPair + val transferRecipient = TxHelpers.address(241) - val differ = blockchain.stub.transactionDiffer().andThen(_.resultE.explicitGet()) - val transaction = EthTxGenerator.generateEthTransfer(TxHelpers.defaultEthSigner, TxHelpers.secondAddress, 10, Waves) - val diff = differ(transaction) - val transactionsApi = stub[CommonTransactionsApi] - (transactionsApi.transactionById _) - .when(transaction.id()) - .returning( - Some( - TransactionMeta.Ethereum( - Height(1), - transaction, - Status.Succeeded, - 15L, - diff.ethereumTransactionMeta.values.headOption, - diff.scriptResults.values.headOption - ) - ) - ) + val ethTransfer = EthTxGenerator.generateEthTransfer(ethAccount, transferRecipient, 5.waves, Asset.Waves) + domain.appendBlock( + TxHelpers.transfer(richAccount, ethAccount.toWavesAddress, 20.waves), + ethTransfer + ) - val route = seal(transactionsApiRoute.copy(blockchain = blockchain, commonApi = transactionsApi).route) - Get(routePath(s"/info/${transaction.id()}")) ~> route ~> check { + Get(routePath(s"/info/${ethTransfer.id()}")) ~> route ~> check { responseAs[JsObject] should matchJson(s"""{ | "type" : 18, - | "id" : "${transaction.id()}", + | "id" : "${ethTransfer.id()}", | "fee" : 100000, | "feeAssetId" : null, - | "timestamp" : ${transaction.timestamp}, + | "timestamp" : ${ethTransfer.timestamp}, | "version" : 1, | "chainId" : 84, - | "bytes" : "${EthEncoding.toHexString(transaction.bytes())}", - | "sender" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "senderPublicKey" : "5vwTDMooR7Hp57MekN7qHz7fHNVrkn2Nx4CiWdq4cyBR4LNnZWYAr7UfBbzhmSvtNkv6e45aJ4Q4aKCSinyHVw33", - | "height" : 1, - | "spentComplexity": 15, + | "bytes" : "${EthEncoding.toHexString(ethTransfer.bytes())}", + | "sender" : "${ethAccount.toWavesAddress}", + | "senderPublicKey" : "${ethTransfer.sender}", + | "height" : ${domain.blockchain.height}, + | "spentComplexity": 0, | "applicationStatus" : "succeeded", | "payload" : { | "type" : "transfer", - | "recipient" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", + | "recipient" : "$transferRecipient", | "asset" : null, - | "amount" : 10 + | "amount" : ${5.waves} | } |}""".stripMargin) } } "returns meta and state changes for eth invoke" in { - val blockchain = createBlockchainStub { blockchain => - blockchain.stub.creditBalance(TxHelpers.defaultEthAddress, Waves) - (blockchain.wavesBalances _).when(*).returns(Map(TxHelpers.defaultEthAddress -> Long.MaxValue / 3)) - blockchain.stub.setScript( - TxHelpers.secondAddress, - TxHelpers.scriptV5("""@Callable(i) - |func test() = [] - |""".stripMargin) - ) - blockchain.stub.activateAllFeatures() - } + val dapp = TxHelpers.signer(245) + val caller = TxHelpers.signer(246).toEthKeyPair - val differ = blockchain.stub.transactionDiffer().andThen(_.resultE.explicitGet()) - val transaction = EthTxGenerator.generateEthInvoke(TxHelpers.defaultEthSigner, TxHelpers.secondAddress, "test", Nil, Nil) - val diff = differ(transaction) - val transactionsApi = stub[CommonTransactionsApi] - (transactionsApi.transactionById _) - .when(transaction.id()) - .returning( - Some( - TransactionMeta.Ethereum( - Height(1), - transaction, - Status.Succeeded, - 15L, - diff.ethereumTransactionMeta.values.headOption, - diff.scriptResults.values.headOption - ) - ) - ) + val transaction = EthTxGenerator.generateEthInvoke(caller, dapp.toAddress, "test", Seq(EthTxGenerator.Arg.Integer(255)), Seq.empty) + + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq( + dapp.toAddress -> 1.waves, + caller.toWavesAddress -> 1.waves + ), + fee = 0.002.waves + ), + TxHelpers.setScript( + dapp, + TestCompiler(V7).compileContract("""@Callable(i) + |func test(arg: Int) = [] + |""".stripMargin) + ), + transaction + ) - val route = seal(transactionsApiRoute.copy(blockchain = blockchain, commonApi = transactionsApi).route) Get(routePath(s"/info/${transaction.id()}")) ~> route ~> check { responseAs[JsObject] should matchJson(s"""{ | "type" : 18, @@ -507,17 +506,20 @@ class TransactionsRouteSpec | "version" : 1, | "chainId" : 84, | "bytes" : "${EthEncoding.toHexString(transaction.bytes())}", - | "sender" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "senderPublicKey" : "5vwTDMooR7Hp57MekN7qHz7fHNVrkn2Nx4CiWdq4cyBR4LNnZWYAr7UfBbzhmSvtNkv6e45aJ4Q4aKCSinyHVw33", - | "height" : 1, - | "spentComplexity": 15, + | "sender" : "${caller.toWavesAddress}", + | "senderPublicKey" : "${transaction.sender}", + | "height" : ${domain.blockchain.height}, + | "spentComplexity": 1, | "applicationStatus" : "succeeded", | "payload" : { | "type" : "invocation", - | "dApp" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", + | "dApp" : "${dapp.toAddress}", | "call" : { | "function" : "test", - | "args" : [ ] + | "args" : [ { + | "type" : "integer", + | "value" : 255 + | } ] | }, | "payment" : [ ], | "stateChanges" : { @@ -537,34 +539,29 @@ class TransactionsRouteSpec } "returns lease tx for lease cancel tx" in { - val lease = TxHelpers.lease() - val leaseCancel = TxHelpers.leaseCancel(lease.id()) + val lessor = TxHelpers.signer(250) + val leaseRecipient = TxHelpers.address(251) - val blockchain = createBlockchainStub { blockchain => - (blockchain.transactionInfo _).when(lease.id()).returns(Some(TxMeta(Height(1), Status.Succeeded, 0L) -> lease)) - (blockchain.transactionInfo _).when(leaseCancel.id()).returns(Some((TxMeta(Height(1), Status.Succeeded, 0L) -> leaseCancel))) - } + val lease = TxHelpers.lease(lessor, leaseRecipient, 22.waves) - val transactionsApi = stub[CommonTransactionsApi] - (transactionsApi.transactionById _).when(lease.id()).returns(Some(TransactionMeta.Default(Height(1), lease, Status.Succeeded, 0L))) - (transactionsApi.transactionById _).when(leaseCancel.id()).returns(Some(TransactionMeta.Default(Height(1), leaseCancel, Status.Succeeded, 0L))) - (blockchain.transactionMeta _).when(lease.id()).returns(Some(TxMeta(Height(1), Status.Succeeded, 0L))) - (blockchain.leaseDetails _) - .when(lease.id()) - .returns( - Some( - LeaseDetails(lease.sender, lease.recipient, lease.amount.value, LeaseDetails.Status.Cancelled(2, Some(leaseCancel.id())), lease.id(), 1) - ) - ) + domain.appendBlock( + TxHelpers.transfer(richAccount, lessor.toAddress, 25.waves), + lease + ) + + val leaseHeight = domain.blockchain.height + + val leaseCancel = TxHelpers.leaseCancel(lease.id(), lessor) + domain.appendBlock(leaseCancel) + val cancelHeight = domain.blockchain.height - val route = transactionsApiRoute.copy(blockchain = blockchain, commonApi = transactionsApi).route Get(routePath(s"/info/${leaseCancel.id()}")) ~> route ~> check { val json = responseAs[JsObject] json shouldBe Json.parse(s"""{ | "type" : 9, | "id" : "${leaseCancel.id()}", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "senderPublicKey" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ", + | "sender" : "${lessor.toAddress}", + | "senderPublicKey" : "${lessor.publicKey}", | "fee" : 100000, | "feeAssetId" : null, | "timestamp" : ${leaseCancel.timestamp}, @@ -572,18 +569,18 @@ class TransactionsRouteSpec | "version" : 2, | "leaseId" : "${lease.id()}", | "chainId" : 84, - | "height" : 1, + | "height" : $cancelHeight, | "applicationStatus" : "succeeded", | "spentComplexity": 0, | "lease" : { | "id" : "${lease.id()}", | "originTransactionId" : "${lease.id()}", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", - | "amount" : 1000000000, - | "height" : 1, + | "sender" : "${lessor.toAddress}", + | "recipient" : "$leaseRecipient", + | "amount" : ${22.waves}, + | "height" : $leaseHeight, | "status" : "canceled", - | "cancelHeight" : 2, + | "cancelHeight" : $cancelHeight, | "cancelTransactionId" : "${leaseCancel.id()}" | } |}""".stripMargin) @@ -599,256 +596,32 @@ class TransactionsRouteSpec Get(routePath(s"/info")) ~> route should produce(InvalidTransactionId("Transaction ID was not specified")) } - "working properly otherwise" in { - val txAvailability = for { - tx <- randomTransactionGen - height <- posNum[Int] - acceptFailedActivationHeight <- posNum[Int] - succeed <- if (height >= acceptFailedActivationHeight) Arbitrary.arbBool.arbitrary else Gen.const(true) - } yield (tx, succeed, height, acceptFailedActivationHeight) - - forAll(txAvailability) { case (tx, succeed, height, acceptFailedActivationHeight) => - val applicationStatus = if (succeed) Status.Succeeded else Status.Failed - (addressTransactions.transactionById _) - .expects(tx.id()) - .returning(Some(TransactionMeta.Default(Height(height), tx, applicationStatus, 0L))) - .once() - (() => blockchain.activatedFeatures) - .expects() - .returning(Map(BF.BlockV5.id -> acceptFailedActivationHeight)) - .anyNumberOfTimes() - - def validateResponse(): Unit = { - status shouldEqual StatusCodes.OK - - val extraFields = Seq( - (if (blockchain.isFeatureActivated(BF.BlockV5, height)) - Json.obj("applicationStatus" -> JsString(if (succeed) "succeeded" else "script_execution_failed")) - else Json.obj()), - Json.obj("height" -> height, "spentComplexity" -> 0) - ).reduce(_ ++ _) - - responseAs[JsValue] should matchJson(tx.json() ++ extraFields) - } - - Get(routePath(s"/info/${tx.id().toString}")) ~> route ~> check(validateResponse()) - } - } - - "provides stateChanges" in forAll(accountGen) { account => - val transaction = TxHelpers.invoke(account.toAddress) - - (() => blockchain.activatedFeatures).expects().returns(Map.empty).anyNumberOfTimes() - (addressTransactions.transactionById _) - .expects(transaction.id()) - .returning(Some(TransactionMeta.Invoke(Height(1), transaction, Status.Succeeded, 0L, Some(InvokeScriptResult())))) - .once() - - Get(routePath(s"/info/${transaction.id()}")) ~> route ~> check { - status shouldEqual StatusCodes.OK - (responseAs[JsObject] \ "stateChanges").as[JsObject] shouldBe Json.toJsObject(InvokeScriptResult()) - } - } - - "provides lease and lease cancel action stateChanges" in { - val invokeAddress = accountGen.sample.get.toAddress - val recipientAddress = accountGen.sample.get.toAddress - val recipientAlias = aliasGen.sample.get - - val leaseId1 = ByteStr(bytes32gen.sample.get) - val leaseId2 = ByteStr(bytes32gen.sample.get) - val leaseCancelId = ByteStr(bytes32gen.sample.get) - - val nestedInvokeAddress = accountGen.sample.get.toAddress - val nestedLeaseId = ByteStr(bytes32gen.sample.get) - val nestedLeaseCancelId = ByteStr(bytes32gen.sample.get) - - val leaseActionAmount1 = 100 - val leaseActionAmount2 = 200 - val innerLeaseActionAmount = 777 - val innerLeaseActionRecipient = accountGen.sample.get.toAddress - - val scriptResult = InvokeScriptResult( - leases = Seq( - InvokeScriptResult.Lease(recipientAddress, leaseActionAmount1, 1, leaseId1), - InvokeScriptResult.Lease(recipientAlias, leaseActionAmount2, 3, leaseId2) - ), - leaseCancels = Seq(LeaseCancel(leaseCancelId)), - invokes = Seq( - InvokeScriptResult.Invocation( - nestedInvokeAddress, - InvokeScriptResult.Call("nested", Nil), - Nil, - InvokeScriptResult( - leases = Seq(InvokeScriptResult.Lease(innerLeaseActionRecipient, innerLeaseActionAmount, 1, nestedLeaseId)), - leaseCancels = Seq(LeaseCancel(nestedLeaseCancelId)) - ) - ) - ) - ) - - (blockchain.leaseDetails _) - .expects(leaseId1) - .returning(Some(LeaseDetails(TestValues.keyPair.publicKey, TestValues.address, 123, LeaseDetails.Status.Active, leaseId1, 1))) - .anyNumberOfTimes() - (blockchain.leaseDetails _) - .expects(leaseId2) - .returning(Some(LeaseDetails(TestValues.keyPair.publicKey, TestValues.address, 123, LeaseDetails.Status.Active, leaseId2, 1))) - .anyNumberOfTimes() - (blockchain.leaseDetails _) - .expects(leaseCancelId) - .returning( - Some( - LeaseDetails( - TestValues.keyPair.publicKey, - TestValues.address, - 123, - LeaseDetails.Status.Cancelled(2, Some(leaseCancelId)), - leaseCancelId, - 1 - ) - ) - ) - .anyNumberOfTimes() - (blockchain.leaseDetails _) - .expects(nestedLeaseId) - .returning(Some(LeaseDetails(TestValues.keyPair.publicKey, TestValues.address, 123, LeaseDetails.Status.Active, nestedLeaseId, 1))) - .anyNumberOfTimes() - (blockchain.leaseDetails _) - .expects(nestedLeaseCancelId) - .returning( - Some( - LeaseDetails( - TestValues.keyPair.publicKey, - TestValues.address, - 123, - LeaseDetails.Status.Cancelled(2, Some(nestedLeaseCancelId)), - nestedLeaseCancelId, - 1 - ) - ) - ) - .anyNumberOfTimes() - - (blockchain.transactionMeta _).expects(leaseId1).returning(Some(TxMeta(Height(1), Status.Succeeded, 0L))).anyNumberOfTimes() - (blockchain.transactionMeta _).expects(leaseId2).returning(Some(TxMeta(Height(1), Status.Succeeded, 0L))).anyNumberOfTimes() - (blockchain.transactionMeta _).expects(leaseCancelId).returning(Some(TxMeta(Height(1), Status.Succeeded, 0L))).anyNumberOfTimes() - (blockchain.transactionMeta _).expects(nestedLeaseId).returning(Some(TxMeta(Height(1), Status.Succeeded, 0L))).anyNumberOfTimes() - (blockchain.transactionMeta _).expects(nestedLeaseCancelId).returning(Some(TxMeta(Height(1), Status.Succeeded, 0L))).anyNumberOfTimes() - - (() => blockchain.activatedFeatures).expects().returns(Map.empty).anyNumberOfTimes() - (blockchain.resolveAlias _).expects(recipientAlias).returning(Right(recipientAddress)) - - val invoke = TxHelpers.invoke(invokeAddress) - (addressTransactions.transactionById _) - .expects(invoke.id()) - .returning(Some(TransactionMeta.Invoke(Height(1), invoke, Status.Succeeded, 0L, Some(scriptResult)))) - .once() - - Get(routePath(s"/info/${invoke.id()}")) ~> route ~> check { - status shouldEqual StatusCodes.OK - val json = (responseAs[JsObject] \ "stateChanges").as[JsObject] - json should matchJson(s"""{ - | "data" : [ ], - | "transfers" : [ ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ { - | "id" : "$leaseId1", - | "originTransactionId" : "$leaseId1", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient" : "$recipientAddress", - | "amount" : $leaseActionAmount1, - | "height" : 1, - | "status":"active", - | "cancelHeight" : null, - | "cancelTransactionId" : null - | }, { - | "id" : "$leaseId2", - | "originTransactionId" : "$leaseId2", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient" : "$recipientAddress", - | "amount" : $leaseActionAmount2, - | "height" : 1, - | "status":"active", - | "cancelHeight" : null, - | "cancelTransactionId" : null - | } ], - | "leaseCancels" : [ { - | "id" : "$leaseCancelId", - | "originTransactionId" : "$leaseCancelId", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "amount" : 123, - | "height" : 1, - | "status" : "canceled", - | "cancelHeight" : 2, - | "cancelTransactionId" : "$leaseCancelId" - | } ], - | "invokes" : [ { - | "dApp" : "$nestedInvokeAddress", - | "call" : { - | "function" : "nested", - | "args" : [ ] - | }, - | "payment" : [ ], - | "stateChanges" : { - | "data" : [ ], - | "transfers" : [ ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ { - | "id" : "$nestedLeaseId", - | "originTransactionId" : "$nestedLeaseId", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient" : "$innerLeaseActionRecipient", - | "amount" : $innerLeaseActionAmount, - | "height" : 1, - | "status":"active", - | "cancelHeight" : null, - | "cancelTransactionId" : null - | } ], - | "leaseCancels" : [ { - | "id" : "$nestedLeaseCancelId", - | "originTransactionId" : "$nestedLeaseCancelId", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "amount" : 123, - | "height" : 1, - | "status" : "canceled", - | "cancelHeight" : 2, - | "cancelTransactionId" : "$nestedLeaseCancelId" - | } ], - | "invokes" : [ ] - | } - | } ] - |} - |""".stripMargin) - } - } - "handles multiple ids" in { val inputLimitErrMsg = TooBigArrayAllocation(transactionsApiRoute.settings.transactionsByAddressLimit).message val emptyInputErrMsg = "Transaction ID was not specified" - val txCount = 5 - val txs = (1 to txCount).map(_ => TxHelpers.invoke(TxHelpers.defaultSigner.toAddress)) - txs.foreach(tx => - (addressTransactions.transactionById _) - .expects(tx.id()) - .returns(Some(TransactionMeta.Invoke(Height(1), tx, Status.Succeeded, 85L, Some(InvokeScriptResult())))) - .anyNumberOfTimes() - ) + val dapp = TxHelpers.signer(1295) + val caller = TxHelpers.signer(1296) + val txs = Seq.fill(settings.restAPISettings.transactionsByAddressLimit + 5)(TxHelpers.invoke(dapp.toAddress, invoker = caller)) - (() => blockchain.activatedFeatures).expects().returns(Map(BF.BlockV5.id -> 1)).anyNumberOfTimes() + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq(dapp.toAddress -> 5.waves, caller.toAddress -> 5.waves), + fee = 0.002.waves + ), + TxHelpers.setScript(dapp, TestCompiler(V5).compileContract("""@Callable(i) func default() = []""")) + ) + domain.appendBlock(txs*) def checkResponse(txs: Seq[InvokeScriptTransaction]): Unit = txs.zip(responseAs[JsArray].value) foreach { case (tx, json) => val extraFields = - Json.obj("height" -> 1, "spentComplexity" -> 85, "applicationStatus" -> "succeeded", "stateChanges" -> InvokeScriptResult()) + Json.obj( + "height" -> domain.blockchain.height, + "spentComplexity" -> 1, + "applicationStatus" -> "succeeded", + "stateChanges" -> InvokeScriptResult() + ) json shouldBe (tx.json() ++ extraFields) } @@ -857,25 +630,17 @@ class TransactionsRouteSpec (responseAs[JsObject] \ "message").as[String] shouldBe errMsg } - val maxLimitTxs = Seq.fill(transactionsApiRoute.settings.transactionsByAddressLimit)(txs.head) - val moreThanLimitTxs = txs.head +: maxLimitTxs + val maxLimitTxs = txs.take(transactionsApiRoute.settings.transactionsByAddressLimit) + val moreThanLimitTxs = txs.take(transactionsApiRoute.settings.transactionsByAddressLimit + 1) - Get(routePath(s"/info?${txs.map("id=" + _.id()).mkString("&")}")) ~> route ~> check(checkResponse(txs)) Get(routePath(s"/info?${maxLimitTxs.map("id=" + _.id()).mkString("&")}")) ~> route ~> check(checkResponse(maxLimitTxs)) Get(routePath(s"/info?${moreThanLimitTxs.map("id=" + _.id()).mkString("&")}")) ~> route ~> check(checkErrorResponse(inputLimitErrMsg)) Get(routePath("/info")) ~> route ~> check(checkErrorResponse(emptyInputErrMsg)) - Post(routePath("/info"), FormData(txs.map("id" -> _.id().toString)*)) ~> route ~> check(checkResponse(txs)) Post(routePath("/info"), FormData(maxLimitTxs.map("id" -> _.id().toString)*)) ~> route ~> check(checkResponse(maxLimitTxs)) Post(routePath("/info"), FormData(moreThanLimitTxs.map("id" -> _.id().toString)*)) ~> route ~> check(checkErrorResponse(inputLimitErrMsg)) Post(routePath("/info"), FormData()) ~> route ~> check(checkErrorResponse(emptyInputErrMsg)) - Post( - routePath("/info"), - HttpEntity(ContentTypes.`application/json`, Json.obj("ids" -> Json.arr(txs.map(_.id().toString: JsValueWrapper)*)).toString()) - ) ~> route ~> check( - checkResponse(txs) - ) Post( routePath("/info"), HttpEntity(ContentTypes.`application/json`, Json.obj("ids" -> Json.arr(maxLimitTxs.map(_.id().toString: JsValueWrapper)*)).toString()) @@ -905,244 +670,230 @@ class TransactionsRouteSpec } "working properly otherwise" in { - val txAvailability = for { - tx <- randomTransactionGen - height <- Gen.chooseNum(1, 1000) - acceptFailedActivationHeight <- Gen.chooseNum(1, 1000) - succeed <- if (height >= acceptFailedActivationHeight) Arbitrary.arbBool.arbitrary else Gen.const(true) - } yield (tx, height, acceptFailedActivationHeight, succeed) - - forAll(txAvailability) { case (tx, height, acceptFailedActivationHeight, succeed) => - val applicationStatus = if (succeed) Status.Succeeded else Status.Failed - (blockchain.transactionInfo _).expects(tx.id()).returning(Some(TxMeta(Height(height), applicationStatus, 93L) -> tx)).anyNumberOfTimes() - (() => blockchain.height).expects().returning(1000).anyNumberOfTimes() - (() => blockchain.activatedFeatures) - .expects() - .returning(Map(BF.BlockV5.id -> acceptFailedActivationHeight)) - .anyNumberOfTimes() - - Get(routePath(s"/status?id=${tx.id().toString}&id=${tx.id().toString}")) ~> route ~> check { - status shouldEqual StatusCodes.OK - val obj = { - val common = Json.obj( - "id" -> tx.id().toString, - "status" -> "confirmed", - "height" -> JsNumber(height), - "confirmations" -> JsNumber(1000 - height), - "spentComplexity" -> 93 - ) - val applicationStatus = - if (blockchain.isFeatureActivated(BF.BlockV5, height)) - Json.obj("applicationStatus" -> JsString(if (succeed) "succeeded" else "script_execution_failed")) - else Json.obj() - common ++ applicationStatus - } - responseAs[JsValue] shouldEqual Json.arr(obj, obj) - } - Post(routePath("/status"), Json.obj("ids" -> Seq(tx.id().toString, tx.id().toString))) ~> route ~> check { - status shouldEqual StatusCodes.OK - } + val dapp = TxHelpers.signer(1195) + val caller = TxHelpers.signer(1195) + val tx1 = TxHelpers.invoke(dapp.toAddress, Some("testCall"), Seq(CONST_BOOLEAN(true)), invoker = caller) + val tx2 = TxHelpers.invoke(dapp.toAddress, Some("testCall"), Seq(CONST_BOOLEAN(false)), invoker = caller) + + domain.appendBlock( + TxHelpers.massTransfer(richAccount, Seq(dapp.toAddress -> 5.waves, caller.toAddress -> 1.waves), fee = 0.002.waves), + TxHelpers.setScript( + dapp, + failableContract + ), + tx1, + tx2 + ) + + def common(id: ByteStr, succeeded: Boolean, complexity: Int) = Json.obj( + "id" -> id, + "status" -> "confirmed", + "height" -> domain.blockchain.height, + "confirmations" -> 0, + "spentComplexity" -> complexity, + "applicationStatus" -> (if (succeeded) "succeeded" else "script_execution_failed") + ) + + val expectedResponse = Json.arr(common(tx1.id(), true, 2), common(tx2.id(), false, 1401)) + + Get(routePath(s"/status?id=${tx1.id().toString}&id=${tx2.id().toString}")) ~> route ~> check { + status shouldEqual StatusCodes.OK + + responseAs[JsValue] shouldEqual expectedResponse + } + Post(routePath("/status"), Json.obj("ids" -> Seq(tx1.id().toString, tx2.id().toString))) ~> route ~> check { + status shouldEqual StatusCodes.OK + responseAs[JsValue] shouldEqual expectedResponse } } } routePath("/unconfirmed") - { "returns the list of unconfirmed transactions" in { - val g = for { - i <- chooseNum(0, 20) - t <- listOfN(i, randomTransactionGen) - } yield t - - forAll(g) { txs => - (() => addressTransactions.unconfirmedTransactions).expects().returning(txs).once() - Get(routePath("/unconfirmed")) ~> route ~> check { - val resp = responseAs[Seq[JsValue]] - for ((r, t) <- resp.zip(txs)) { - if ((r \ "version").as[Int] == 1) { - (r \ "signature").as[String] shouldEqual t.proofs.proofs.head.toString - } else { - (r \ "proofs").as[Seq[String]] shouldEqual t.proofs.proofs.map(_.toString) - } - } - } + domain.utxPool.removeAll(domain.utxPool.all) + val txs = Seq.tabulate(20)(a => TxHelpers.transfer(richAccount, amount = (a + 1).waves)) + txs.foreach(t => domain.utxPool.putIfNew(t)) + Get(routePath("/unconfirmed")) ~> route ~> check { + val txIds = responseAs[Seq[JsValue]].map(v => (v \ "id").as[String]) + txIds should contain allElementsOf (txs.map(_.id().toString)) } + domain.utxPool.removeAll(txs) } - } - routePath("/unconfirmed/size") - { - "returns the size of unconfirmed transactions" in { - val g = for { - i <- chooseNum(0, 20) - t <- listOfN(i, randomTransactionGen) - } yield t - - forAll(g) { txs => - utxPoolSize.expects().returning(txs.size).once() + routePath("/unconfirmed/size") - { + "returns the size of unconfirmed transactions" in { + domain.utxPool.removeAll(domain.utxPool.all) + val txs = Seq.tabulate(20)(a => TxHelpers.transfer(richAccount, amount = (a + 1).waves)) + txs.foreach(t => domain.utxPool.putIfNew(t)) Get(routePath("/unconfirmed/size")) ~> route ~> check { status shouldEqual StatusCodes.OK responseAs[JsValue] shouldEqual Json.obj("size" -> JsNumber(txs.size)) } + domain.utxPool.removeAll(txs) } } - } - routePath("/unconfirmed/info/{id}") - { - "handles invalid signature" in { - forAll(invalidBase58Gen) { invalidBase58 => - Get(routePath(s"/unconfirmed/info/$invalidBase58")) ~> route should produce(InvalidTransactionId("Wrong char"), matchMsg = true) - } + routePath("/unconfirmed/info/{id}") - { + "handles invalid signature" in { + forAll(invalidBase58Gen) { invalidBase58 => + Get(routePath(s"/unconfirmed/info/$invalidBase58")) ~> route should produce(InvalidTransactionId("Wrong char"), matchMsg = true) + } - Get(routePath(s"/unconfirmed/info/")) ~> route should produce(InvalidSignature) - Get(routePath(s"/unconfirmed/info")) ~> route should produce(InvalidSignature) - } + Get(routePath(s"/unconfirmed/info/")) ~> route should produce(InvalidSignature) + Get(routePath(s"/unconfirmed/info")) ~> route should produce(InvalidSignature) + } - "working properly otherwise" in { - forAll(randomTransactionGen) { tx => - (addressTransactions.unconfirmedTransactionById _).expects(tx.id()).returns(Some(tx)).once() + "working properly otherwise" in { + val tx = TxHelpers.transfer(richAccount, defaultAddress, 20.waves) + domain.utxPool.putIfNew(tx) Get(routePath(s"/unconfirmed/info/${tx.id().toString}")) ~> route ~> check { status shouldEqual StatusCodes.OK responseAs[JsValue] shouldEqual tx.json() } + domain.utxPool.removeAll(Seq(tx)) } } - } - routePath("/sign") - { - "function call without args" in { - val acc1 = testWallet.generateNewAccount().get - val acc2 = testWallet.generateNewAccount().get - - val funcName = "func" - val funcWithoutArgs = Json.obj("function" -> funcName) - val funcWithEmptyArgs = Json.obj("function" -> funcName, "args" -> JsArray.empty) - val funcWithArgs = InvokeScriptTxSerializer.functionCallToJson( - FUNCTION_CALL( - FunctionHeader.User(funcName), - List(CONST_LONG(1), CONST_BOOLEAN(true)) + routePath("/sign") - { + "function call without args" in { + val acc1 = domain.wallet.generateNewAccount().get + val acc2 = domain.wallet.generateNewAccount().get + + val funcName = "func" + val funcWithoutArgs = Json.obj("function" -> funcName) + val funcWithEmptyArgs = Json.obj("function" -> funcName, "args" -> JsArray.empty) + val funcWithArgs = InvokeScriptTxSerializer.functionCallToJson( + FUNCTION_CALL( + FunctionHeader.User(funcName), + List(CONST_LONG(1), CONST_BOOLEAN(true)) + ) ) - ) - def invoke(func: JsObject, expectedArgsLength: Int): Unit = { - val ist = Json.obj( - "type" -> InvokeScriptTransaction.typeId, - "version" -> Gen.oneOf(InvokeScriptTransaction.supportedVersions.toSeq).sample.get, - "sender" -> acc1.toAddress, - "dApp" -> acc2.toAddress, - "call" -> func, - "payment" -> Seq[Payment](), - "fee" -> 500000, - "feeAssetId" -> JsNull - ) - Post(routePath("/sign"), ist) ~> ApiKeyHeader ~> route ~> check { - status shouldEqual StatusCodes.OK - val jsObject = responseAs[JsObject] - (jsObject \ "senderPublicKey").as[String] shouldBe acc1.publicKey.toString - (jsObject \ "call" \ "function").as[String] shouldBe funcName - (jsObject \ "call" \ "args").as[JsArray].value.length shouldBe expectedArgsLength + def invoke(func: JsObject, expectedArgsLength: Int): Unit = { + val ist = Json.obj( + "type" -> InvokeScriptTransaction.typeId, + "version" -> 3, + "sender" -> acc1.toAddress, + "dApp" -> acc2.toAddress, + "call" -> func, + "payment" -> Seq[Payment](), + "fee" -> 500000, + "feeAssetId" -> JsNull + ) + Post(routePath("/sign"), ist) ~> ApiKeyHeader ~> route ~> check { + status shouldEqual StatusCodes.OK + val jsObject = responseAs[JsObject] + (jsObject \ "senderPublicKey").as[String] shouldBe acc1.publicKey.toString + (jsObject \ "call" \ "function").as[String] shouldBe funcName + (jsObject \ "call" \ "args").as[JsArray].value.length shouldBe expectedArgsLength + } } - } - invoke(funcWithoutArgs, 0) - invoke(funcWithEmptyArgs, 0) - invoke(funcWithArgs, 2) - } - } - - routePath("/broadcast") - { - def withInvokeScriptTransaction(f: (KeyPair, InvokeScriptTransaction) => Unit): Unit = { - val seed = new Array[Byte](32) - Random.nextBytes(seed) - val sender: KeyPair = KeyPair(seed) - val ist = Signed.invokeScript( - TxVersion.V1, - sender, - sender.toAddress, - None, - Seq.empty, - 500000L, - Asset.Waves, - testTime.getTimestamp() - ) - f(sender, ist) + invoke(funcWithoutArgs, 0) + invoke(funcWithEmptyArgs, 0) + invoke(funcWithArgs, 2) + } } - "shows trace when trace is enabled" in withInvokeScriptTransaction { (sender, ist) => - val accountTrace = AccountVerifierTrace(sender.toAddress, Some(GenericError("Error in account script"))) - (utxPoolSynchronizer.validateAndBroadcast _) - .expects(*, None) - .returning( - Future.successful(TracedResult(Right(true), List(accountTrace))) + routePath("/broadcast") - { + def withInvokeScriptTransaction(f: (KeyPair, InvokeScriptTransaction) => Unit): Unit = { + val seed = new Array[Byte](32) + Random.nextBytes(seed) + val sender: KeyPair = KeyPair(seed) + val ist = Signed.invokeScript( + TxVersion.V1, + sender, + sender.toAddress, + None, + Seq.empty, + 500000L, + Asset.Waves, + testTime.getTimestamp() ) - .once() - Post(routePath("/broadcast?trace=true"), ist.json()) ~> route ~> check { - val result = responseAs[JsObject] - (result \ "trace").as[JsValue] shouldBe Json.arr(accountTrace.json) + f(sender, ist) } - } - "does not show trace when trace is disabled" in withInvokeScriptTransaction { (sender, ist) => - val accountTrace = AccountVerifierTrace(sender.toAddress, Some(GenericError("Error in account script"))) - (utxPoolSynchronizer.validateAndBroadcast _) - .expects(*, None) - .returning( - Future.successful(TracedResult(Right(true), List(accountTrace))) + "shows trace when trace is enabled" in { + val sender = TxHelpers.signer(1201) + val ist = TxHelpers.transfer(sender, defaultAddress, 1.waves) + domain.appendBlock( + TxHelpers.transfer(richAccount, sender.toAddress, 2.waves), + TxHelpers.setScript(sender, TestCompiler(V7).compileExpression("throw(\"error\")")) ) - .twice() - Post(routePath("/broadcast"), ist.json()) ~> route ~> check { - (responseAs[JsObject] \ "trace") shouldBe empty - } - Post(routePath("/broadcast?trace=false"), ist.json()) ~> route ~> check { - (responseAs[JsObject] \ "trace") shouldBe empty + Post(routePath("/broadcast?trace=true"), ist.json()) ~> route ~> check { + val result = responseAs[JsObject] + (result \ "trace").as[JsValue] shouldBe Json.arr( + AccountVerifierTrace( + sender.toAddress, + Some( + ScriptExecutionError( + "error", + List( + "throw.@args" -> Right(ARR(IndexedSeq(CONST_STRING("error").explicitGet()), false).explicitGet()), + "throw.@complexity" -> Right(CONST_LONG(1)), + "@complexityLimit" -> Right(CONST_LONG(2147483646)) + ), + None + ) + ) + ).json + ) + } } - } - - "generates valid trace with vars" in { - val sender = testWallet.generateNewAccount().get - val aliasOwner = testWallet.generateNewAccount().get - val recipient = testWallet.generateNewAccount().get - val balances = Seq( - AddrWithBalance(sender.toAddress, 1000.waves), - AddrWithBalance(aliasOwner.toAddress, 1000.waves) - ) + "does not show trace when trace is disabled" in withInvokeScriptTransaction { (_, ist) => + Post(routePath("/broadcast"), ist.json()) ~> route ~> check { + (responseAs[JsObject] \ "trace") shouldBe empty + } + Post(routePath("/broadcast?trace=false"), ist.json()) ~> route ~> check { + (responseAs[JsObject] \ "trace") shouldBe empty + } + } - withDomain(settingsWithFeatures(BF.SmartAccounts, BF.BlockV5, BF.SynchronousCalls, BF.Ride4DApps), balances) { d => - val lease = LeaseTransaction.selfSigned(2.toByte, sender, recipient.toAddress, 50.waves, 0.001.waves, ntpTime.getTimestamp()).explicitGet() - - d.appendBlock( - CreateAliasTransaction.selfSigned(2.toByte, aliasOwner, "test_alias", 0.001.waves, ntpTime.getTimestamp()).explicitGet(), - SetScriptTransaction - .selfSigned( - 2.toByte, - sender, - Some(TestCompiler(V5).compileContract(s"""{-# STDLIB_VERSION 5 #-} - |{-# CONTENT_TYPE DAPP #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - | - |@Callable(i) - |func default() = { - | let leaseToAddress = Lease(Address(base58'${recipient.toAddress}'), ${10.waves}) - | let leaseToAlias = Lease(Alias("test_alias"), ${20.waves}) - | strict leaseId = leaseToAddress.calculateLeaseId() - | - | [ - | leaseToAddress, - | leaseToAlias, - | LeaseCancel(base58'${lease.id()}') - | ] - |} - |""".stripMargin)), - 0.01.waves, - ntpTime.getTimestamp() - ) - .explicitGet(), + "generates valid trace with vars" in { + val sender = TxHelpers.signer(1030) + val aliasOwner = TxHelpers.signer(1031) + val recipient = TxHelpers.address(1032) + + val lease = TxHelpers.lease(sender, recipient, 50.waves) + + domain.appendBlock( + TxHelpers.massTransfer( + richAccount, + Seq( + sender.toAddress -> 100.waves, + aliasOwner.toAddress -> 1.waves + ), + fee = 0.002.waves + ), + TxHelpers.createAlias("test_alias", aliasOwner), + TxHelpers.setScript( + sender, + TestCompiler(V5).compileContract(s"""{-# STDLIB_VERSION 5 #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |@Callable(i) + |func default() = { + | let leaseToAddress = Lease(Address(base58'${recipient}'), ${10.waves}) + | let leaseToAlias = Lease(Alias("test_alias"), ${20.waves}) + | strict leaseId = leaseToAddress.calculateLeaseId() + | + | [ + | leaseToAddress, + | leaseToAlias, + | LeaseCancel(base58'${lease.id()}') + | ] + |} + |""".stripMargin) + ), lease ) val invoke = Signed .invokeScript(2.toByte, sender, sender.toAddress, None, Seq.empty, 0.005.waves, Asset.Waves, ntpTime.getTimestamp()) - Post(routePath("/broadcast?trace=true"), invoke.json()) ~> mkRoute(d) ~> check { + Post(routePath("/broadcast?trace=true"), invoke.json()) ~> route ~> check { val dappTrace = (responseAs[JsObject] \ "trace").as[Seq[JsObject]].find(jsObject => (jsObject \ "type").as[String] == "dApp").get (dappTrace \ "error").get shouldEqual JsNull @@ -1150,6 +901,8 @@ class TransactionsRouteSpec "i", "default.@args", "Address.@args", + "Address.@complexity", + "@complexityLimit", "Lease.@args", "Lease.@complexity", "@complexityLimit", @@ -1162,11 +915,15 @@ class TransactionsRouteSpec "==.@complexity", "@complexityLimit", "Alias.@args", + "Alias.@complexity", + "@complexityLimit", "Lease.@args", "Lease.@complexity", "@complexityLimit", "leaseToAlias", "LeaseCancel.@args", + "LeaseCancel.@complexity", + "@complexityLimit", "cons.@args", "cons.@complexity", "@complexityLimit", @@ -1179,122 +936,109 @@ class TransactionsRouteSpec ) } } - } - "checks the length of base58 attachment in symbols" in { - val attachmentSizeInSymbols = TransferTransaction.MaxAttachmentStringSize + 1 - val attachmentStr = "1" * attachmentSizeInSymbols - - val tx = TxHelpers - .transfer() - .copy(attachment = ByteStr(Base58.decode(attachmentStr))) // to bypass a validation - .signWith(defaultSigner.privateKey) - - Post(routePath("/broadcast"), tx.json()) ~> route should produce( - WrongJson( - errors = Seq( - JsPath \ "attachment" -> Seq( - JsonValidationError(s"base58-encoded string length ($attachmentSizeInSymbols) exceeds maximum length of 192") - ) - ), - msg = Some("json data validation error, see validationErrors for details") + "checks the length of base58 attachment in symbols" in { + val attachmentSizeInSymbols = TransferTransaction.MaxAttachmentStringSize + 1 + val attachmentStr = "1" * attachmentSizeInSymbols + + val tx = TxHelpers + .transfer() + .copy(attachment = ByteStr(Base58.decode(attachmentStr))) // to bypass a validation + .signWith(defaultSigner.privateKey) + + Post(routePath("/broadcast"), tx.json()) ~> route should produce( + WrongJson( + errors = Seq( + JsPath \ "attachment" -> Seq( + JsonValidationError(s"base58-encoded string length ($attachmentSizeInSymbols) exceeds maximum length of 192") + ) + ), + msg = Some("json data validation error, see validationErrors for details") + ) ) - ) - } + } - "checks the length of base58 attachment in bytes" in { - val attachmentSizeInSymbols = TransferTransaction.MaxAttachmentSize + 1 - val attachmentStr = "1" * attachmentSizeInSymbols - val attachment = ByteStr(Base58.decode(attachmentStr)) + "checks the length of base58 attachment in bytes" in { + val attachmentSizeInSymbols = TransferTransaction.MaxAttachmentSize + 1 + val attachmentStr = "1" * attachmentSizeInSymbols + val attachment = ByteStr(Base58.decode(attachmentStr)) - val tx = TxHelpers - .transfer() - .copy(attachment = attachment) - .signWith(defaultSigner.privateKey) + val tx = TxHelpers + .transfer() + .copy(attachment = attachment) + .signWith(defaultSigner.privateKey) - Post(routePath("/broadcast"), tx.json()) ~> route should produce( - TooBigInBytes( - s"Invalid attachment. Length ${attachment.size} bytes exceeds maximum of ${TransferTransaction.MaxAttachmentSize} bytes." + Post(routePath("/broadcast"), tx.json()) ~> route should produce( + TooBigInBytes( + s"Invalid attachment. Length ${attachment.size} bytes exceeds maximum of ${TransferTransaction.MaxAttachmentSize} bytes." + ) ) - ) + } } - } - routePath("/merkleProof") - { - val transactionsGen = for { - txsSize <- Gen.choose(1, 10) - txs <- Gen.listOfN(txsSize, randomTransactionGen) - } yield txs - - val invalidBlockGen = for { - txs <- transactionsGen - signer <- accountGen - version <- Gen.choose(Block.GenesisBlockVersion, Block.RewardBlockVersion) - block <- versionedBlockGen(txs, signer, version) - } yield block - - val invalidBlocksGen = - for { - blockchainHeight <- Gen.choose(1, 10) - blocks <- Gen.listOfN(blockchainHeight, invalidBlockGen) - } yield blocks - - val merkleProofs = for { - index <- Gen.choose(0, 50) - tx <- randomTransactionGen - proofsLength <- Gen.choose(1, 5) - proofBytes <- Gen.listOfN(proofsLength, bytes32gen) - } yield (tx, TransactionProof(tx.id(), index, proofBytes)) - - def validateSuccess(expectedProofs: Seq[TransactionProof], response: HttpResponse): Unit = { - response.status shouldBe StatusCodes.OK - - val proofs = responseAs[List[JsObject]] - - proofs.size shouldBe expectedProofs.size - - proofs.zip(expectedProofs).foreach { case (p, e) => - val transactionId = (p \ "id").as[String] - val transactionIndex = (p \ "transactionIndex").as[Int] - val digests = (p \ "merkleProof").as[List[String]].map(s => ByteStr.decodeBase58(s).get) - - transactionId shouldEqual e.id.toString - transactionIndex shouldEqual e.transactionIndex - digests shouldEqual e.digests.map(ByteStr(_)) + routePath("/merkleProof") - { + def validateSuccess(blockRoot: ByteStr, expected: Seq[(ByteStr, Array[Byte], Int)], response: HttpResponse): Unit = { + response.status shouldBe StatusCodes.OK + + val proofs = responseAs[List[JsObject]] + + proofs.size shouldBe expected.size + + proofs.zip(expected).foreach { case (p, (id, hash, index)) => + val transactionId = (p \ "id").as[String] + val transactionIndex = (p \ "transactionIndex").as[Int] + val digests = (p \ "merkleProof").as[List[String]].map(s => Base58.decode(s)) + + transactionId shouldEqual id.toString + transactionIndex shouldEqual index + + assert(Merkle.verify(hash, transactionIndex, digests.reverse, blockRoot.arr)) + + } } - } - def validateFailure(response: HttpResponse): Unit = { - response.status shouldEqual StatusCodes.BadRequest - (responseAs[JsObject] \ "message").as[String] shouldEqual s"transactions do not exist or block version < ${Block.ProtoBlockVersion}" - } + def validateFailure(response: HttpResponse): Unit = { + response.status shouldEqual StatusCodes.BadRequest + (responseAs[JsObject] \ "message").as[String] shouldEqual s"transactions do not exist or block version < ${Block.ProtoBlockVersion}" + } + + "returns merkle proofs" in { + val dapp = TxHelpers.signer(1390) + val caller = TxHelpers.signer(1390) + + val tx1 = TxHelpers.invoke(dapp.toAddress, Some("testCall"), Seq(CONST_BOOLEAN(true)), invoker = caller) + val tx2 = TxHelpers.invoke(dapp.toAddress, Some("testCall"), Seq(CONST_BOOLEAN(false)), invoker = caller) + + domain.appendBlock( + TxHelpers.massTransfer(richAccount, Seq(dapp.toAddress -> 10.waves, caller.toAddress -> 10.waves), fee = 0.002.waves), + TxHelpers.setScript(dapp, failableContract), + tx1, + tx2 + ) - "returns merkle proofs" in { - forAll(Gen.choose(10, 20).flatMap(n => Gen.listOfN(n, merkleProofs))) { transactionsAndProofs => - val (transactions, proofs) = transactionsAndProofs.unzip - (addressTransactions.transactionProofs _).expects(transactions.map(_.id())).returning(proofs).twice() + val transactions = Seq(tx1, tx2) + val proofs = Seq( + (tx1.id(), crypto.fastHash(PBTransactions.toByteArrayMerkle(tx1)), 2), + (tx2.id(), crypto.fastHash(PBTransactions.toByteArrayMerkle(tx2)), 3) + ) val queryParams = transactions.map(t => s"id=${t.id()}").mkString("?", "&", "") val requestBody = Json.obj("ids" -> transactions.map(_.id().toString)) Get(routePath(s"/merkleProof$queryParams")) ~> route ~> check { - validateSuccess(proofs, response) + validateSuccess(domain.blockchain.lastBlockHeader.value.header.transactionsRoot, proofs, response) } Post(routePath("/merkleProof"), requestBody) ~> route ~> check { - validateSuccess(proofs, response) + validateSuccess(domain.blockchain.lastBlockHeader.value.header.transactionsRoot, proofs, response) } } - } - - "returns error in case of all transactions are filtered" in { - forAll(invalidBlocksGen) { blocks => - val txIdsToBlock = blocks.flatMap(b => b.transactionData.map(tx => (tx.id().toString, b))).toMap - val queryParams = txIdsToBlock.keySet.map(id => s"id=$id").mkString("?", "&", "") - val requestBody = Json.obj("ids" -> txIdsToBlock.keySet) + "returns error in case of all transactions are filtered" in { + val genesisTransactions = domain.blocksApi.blockAtHeight(1).value._2.collect { case (_, tx) => tx.id() } - (addressTransactions.transactionProofs _).expects(*).returning(Nil).anyNumberOfTimes() + val queryParams = genesisTransactions.map(id => s"id=$id").mkString("?", "&", "") + val requestBody = Json.obj("ids" -> genesisTransactions) Get(routePath(s"/merkleProof$queryParams")) ~> route ~> check { validateFailure(response) @@ -1304,48 +1048,44 @@ class TransactionsRouteSpec validateFailure(response) } } - } - "handles invalid ids" in { - val invalidIds = Seq( - ByteStr.fill(AssetIdLength)(1), - ByteStr.fill(AssetIdLength)(2) - ).map(bs => s"${bs}0") + "handles invalid ids" in { + val invalidIds = Seq( + ByteStr.fill(AssetIdLength)(1), + ByteStr.fill(AssetIdLength)(2) + ).map(bs => s"${bs}0") - Get(routePath(s"/merkleProof?${invalidIds.map("id=" + _).mkString("&")}")) ~> route should produce(InvalidIds(invalidIds)) + Get(routePath(s"/merkleProof?${invalidIds.map("id=" + _).mkString("&")}")) ~> route should produce(InvalidIds(invalidIds)) - Post(routePath("/merkleProof"), FormData(invalidIds.map("id" -> _)*)) ~> route should produce(InvalidIds(invalidIds)) + Post(routePath("/merkleProof"), FormData(invalidIds.map("id" -> _)*)) ~> route should produce(InvalidIds(invalidIds)) - Post(routePath("/merkleProof"), Json.obj("ids" -> invalidIds)) ~> route should produce(InvalidIds(invalidIds)) - } + Post(routePath("/merkleProof"), Json.obj("ids" -> invalidIds)) ~> route should produce(InvalidIds(invalidIds)) + } - "handles transactions ids limit" in { - val inputLimitErrMsg = TooBigArrayAllocation(transactionsApiRoute.settings.transactionsByAddressLimit).message - val emptyInputErrMsg = "Transaction ID was not specified" + "handles transactions ids limit" in { + val inputLimitErrMsg = TooBigArrayAllocation(transactionsApiRoute.settings.transactionsByAddressLimit).message + val emptyInputErrMsg = "Transaction ID was not specified" - def checkErrorResponse(errMsg: String): Unit = { - response.status shouldBe StatusCodes.BadRequest - (responseAs[JsObject] \ "message").as[String] shouldBe errMsg - } + def checkErrorResponse(errMsg: String): Unit = { + response.status shouldBe StatusCodes.BadRequest + (responseAs[JsObject] \ "message").as[String] shouldBe errMsg + } - def checkResponse(tx: TransferTransaction, idsCount: Int): Unit = { - response.status shouldBe StatusCodes.OK + def checkResponse(tx: TransferTransaction, idsCount: Int): Unit = { + response.status shouldBe StatusCodes.OK - val result = responseAs[JsArray].value - result.size shouldBe idsCount - (1 to idsCount).zip(responseAs[JsArray].value) foreach { case (_, json) => - (json \ "id").as[String] shouldBe tx.id().toString - (json \ "transactionIndex").as[Int] shouldBe 0 + val result = responseAs[JsArray].value + result.size shouldBe idsCount + (1 to idsCount).zip(responseAs[JsArray].value) foreach { case (_, json) => + (json \ "id").as[String] shouldBe tx.id().toString + (json \ "transactionIndex").as[Int] shouldBe 1 + } } - } - val sender = TxHelpers.signer(1) + val sender = TxHelpers.signer(1090) - withDomain(DomainPresets.RideV5, balances = AddrWithBalance.enoughBalances(sender)) { d => val transferTx = TxHelpers.transfer(from = sender) - d.appendBlock(transferTx) - - val route = mkRoute(d) + domain.appendBlock(TxHelpers.transfer(richAccount, sender.toAddress, 100.waves), transferTx) val maxLimitIds = Seq.fill(transactionsApiRoute.settings.transactionsByAddressLimit)(transferTx.id().toString) val moreThanLimitIds = transferTx.id().toString +: maxLimitIds @@ -1372,17 +1112,15 @@ class TransactionsRouteSpec ) ~> route ~> check(checkErrorResponse(emptyInputErrMsg)) } } - } - "NODE-969. Transactions API should return correct data for orders with attachment" in { - def checkOrderAttachment(txInfo: JsObject, expectedAttachment: ByteStr): Assertion = { - implicit val byteStrFormat: Format[ByteStr] = com.wavesplatform.utils.byteStrFormat - (txInfo \ "order1" \ "attachment").asOpt[ByteStr] shouldBe Some(expectedAttachment) - } + "NODE-969. Transactions API should return correct data for orders with attachment" in { + def checkOrderAttachment(txInfo: JsObject, expectedAttachment: ByteStr): Assertion = { + implicit val byteStrFormat: Format[ByteStr] = com.wavesplatform.utils.byteStrFormat + (txInfo \ "order1" \ "attachment").asOpt[ByteStr] shouldBe Some(expectedAttachment) + } - val sender = TxHelpers.signer(1) - val issuer = TxHelpers.signer(2) - withDomain(TransactionStateSnapshot, balances = AddrWithBalance.enoughBalances(sender, issuer)) { d => + val sender = TxHelpers.signer(1100) + val issuer = TxHelpers.signer(1101) val attachment = ByteStr.fill(32)(1) val issue = TxHelpers.issue(issuer) val exchange = @@ -1392,22 +1130,13 @@ class TransactionsRouteSpec version = TxVersion.V3 ) - d.appendBlock(issue) - d.appendBlock(exchange) - - val route = new TransactionsApiRoute( - d.settings.restAPISettings, - d.commonApi.transactions, - d.wallet, - d.blockchain, - () => d.blockchain.snapshotBlockchain, - () => 0, - (t, _) => d.commonApi.transactions.broadcastTransaction(t), - testTime, - new RouteTimeout(60.seconds)(sharedScheduler) - ).route - - d.liquidAndSolidAssert { () => + domain.appendBlock( + TxHelpers.massTransfer(richAccount, Seq(sender.toAddress -> 10.waves, issuer.toAddress -> 10.waves), fee = 0.002.waves), + issue, + exchange + ) + + domain.liquidAndSolidAssert { () => Get(s"/transactions/info/${exchange.id()}") ~> route ~> check { checkOrderAttachment(responseAs[JsObject], attachment) } @@ -1423,7 +1152,7 @@ class TransactionsRouteSpec checkOrderAttachment(responseAs[JsArray].value.head.as[JsObject], attachment) } - Get(s"/transactions/address/${exchange.sender.toAddress}/limit/10") ~> route ~> check { + Get(s"/transactions/address/${exchange.sender.toAddress}/limit/5") ~> route ~> check { checkOrderAttachment(responseAs[JsArray].value.head.as[JsArray].value.head.as[JsObject], attachment) } } diff --git a/node/src/test/scala/com/wavesplatform/mining/BlockV5Test.scala b/node/src/test/scala/com/wavesplatform/mining/BlockV5Test.scala index 21cb7bc539..1262559035 100644 --- a/node/src/test/scala/com/wavesplatform/mining/BlockV5Test.scala +++ b/node/src/test/scala/com/wavesplatform/mining/BlockV5Test.scala @@ -454,7 +454,7 @@ class BlockV5Test extends FlatSpec with WithDomain with OptionValues with Either ): Unit = { withRocksDBWriter(settings.blockchainSettings) { blockchain => val bcu: BlockchainUpdaterImpl = - new BlockchainUpdaterImpl(blockchain, settings, time, ignoreBlockchainUpdateTriggers, (_, _) => Seq.empty) { + new BlockchainUpdaterImpl(blockchain, settings, time, ignoreBlockchainUpdateTriggers, (_, _) => Map.empty) { override def activatedFeatures: Map[Short, Int] = super.activatedFeatures -- disabledFeatures.get() } try f(bcu) diff --git a/node/src/test/scala/com/wavesplatform/mining/BlockWithMaxBaseTargetTest.scala b/node/src/test/scala/com/wavesplatform/mining/BlockWithMaxBaseTargetTest.scala index 1137945f7b..bdf68e8dce 100644 --- a/node/src/test/scala/com/wavesplatform/mining/BlockWithMaxBaseTargetTest.scala +++ b/node/src/test/scala/com/wavesplatform/mining/BlockWithMaxBaseTargetTest.scala @@ -102,7 +102,7 @@ class BlockWithMaxBaseTargetTest extends FreeSpec with WithNewDBForEachTest with case _: SecurityException => Task.unit } - Await.result(blockAppendTask.runToFuture(scheduler), Duration.Inf) + Await.result(blockAppendTask.runToFuture(scheduler), 1.minute) signal.tryAcquire(10, TimeUnit.SECONDS) @@ -130,7 +130,7 @@ class BlockWithMaxBaseTargetTest extends FreeSpec with WithNewDBForEachTest with ) val bcu = - new BlockchainUpdaterImpl(defaultWriter, settings, ntpTime, ignoreBlockchainUpdateTriggers, (_, _) => Seq.empty) + new BlockchainUpdaterImpl(defaultWriter, settings, ntpTime, ignoreBlockchainUpdateTriggers, (_, _) => Map.empty) val pos = PoSSelector(bcu, settings.synchronizationSettings.maxBaseTarget) val utxPoolStub = new UtxPoolImpl(ntpTime, bcu, settings0.utxSettings, settings.maxTxErrorLogSize, settings0.minerSettings.enable) diff --git a/node/src/test/scala/com/wavesplatform/mining/MiningFailuresSuite.scala b/node/src/test/scala/com/wavesplatform/mining/MiningFailuresSuite.scala index 9c5fb734dd..9aa7ad379a 100644 --- a/node/src/test/scala/com/wavesplatform/mining/MiningFailuresSuite.scala +++ b/node/src/test/scala/com/wavesplatform/mining/MiningFailuresSuite.scala @@ -99,7 +99,7 @@ class MiningFailuresSuite extends FlatSpec with PathMockFactory with WithNewDBFo ) var minedBlock: Block = null - (blockchainUpdater.processBlock _).when(*, *, *, *, *, *).returning(Left(BlockFromFuture(100))).repeated(10) + (blockchainUpdater.processBlock _).when(*, *, *, *, *, *).returning(Left(BlockFromFuture(100, 100))).repeated(10) (blockchainUpdater.processBlock _) .when(*, *, *, *, *, *) .onCall { (block, _, _, _, _, _) => diff --git a/node/src/test/scala/com/wavesplatform/network/MicroBlockSynchronizerSpec.scala b/node/src/test/scala/com/wavesplatform/network/MicroBlockSynchronizerSpec.scala index 724e927fdc..118fa75c68 100644 --- a/node/src/test/scala/com/wavesplatform/network/MicroBlockSynchronizerSpec.scala +++ b/node/src/test/scala/com/wavesplatform/network/MicroBlockSynchronizerSpec.scala @@ -1,6 +1,5 @@ package com.wavesplatform.network -import com.wavesplatform.block.MicroBlockSnapshot import com.wavesplatform.common.state.ByteStr import com.wavesplatform.settings.SynchronizationSettings.MicroblockSynchronizerSettings import com.wavesplatform.test.FreeSpec @@ -22,14 +21,14 @@ class MicroBlockSynchronizerSpec extends FreeSpec with RxScheduler with BlockGen PS[ByteStr], PS[(Channel, MicroBlockInv)], PS[(Channel, MicroBlockResponse)], - Observable[(Channel, MicroBlockSynchronizer.MicroblockData, Option[(Channel, MicroBlockSnapshot)])] + Observable[(Channel, MicroBlockSynchronizer.MicroblockData, Option[(Channel, MicroBlockSnapshotResponse)])] ) => Any ) = { val peers = PeerDatabase.NoOp val lastBlockIds = PS[ByteStr]() val microInvs = PS[(Channel, MicroBlockInv)]() val microResponses = PS[(Channel, MicroBlockResponse)]() - val microSnapshots = PS[(Channel, MicroBlockSnapshot)]() + val microSnapshots = PS[(Channel, MicroBlockSnapshotResponse)]() val (r, _) = MicroBlockSynchronizer(defaultSettings, false, peers, lastBlockIds, microInvs, microResponses, microSnapshots, testScheduler) try { f(lastBlockIds, microInvs, microResponses, r) diff --git a/node/src/test/scala/com/wavesplatform/network/RxExtensionLoaderSpec.scala b/node/src/test/scala/com/wavesplatform/network/RxExtensionLoaderSpec.scala index 5c27d1c005..eaec4bceb5 100644 --- a/node/src/test/scala/com/wavesplatform/network/RxExtensionLoaderSpec.scala +++ b/node/src/test/scala/com/wavesplatform/network/RxExtensionLoaderSpec.scala @@ -1,6 +1,6 @@ package com.wavesplatform.network -import com.wavesplatform.block.{Block, BlockSnapshot} +import com.wavesplatform.block.Block import com.wavesplatform.common.state.ByteStr import com.wavesplatform.lang.ValidationError import com.wavesplatform.network.RxScoreObserver.ChannelClosedAndSyncWith @@ -39,13 +39,13 @@ class RxExtensionLoaderSpec extends FreeSpec with RxScheduler with BlockGen { PS[(Channel, Block)], PS[(Channel, Signatures)], PS[ChannelClosedAndSyncWith], - Observable[(Channel, Block, Option[BlockSnapshot])] + Observable[(Channel, Block, Option[BlockSnapshotResponse])] ) => Any ) = { val blocks = PS[(Channel, Block)]() val sigs = PS[(Channel, Signatures)]() val ccsw = PS[ChannelClosedAndSyncWith]() - val snapshots = PS[(Channel, BlockSnapshot)]() + val snapshots = PS[(Channel, BlockSnapshotResponse)]() val timeout = PS[Channel]() val op = PeerDatabase.NoOp val invBlockStorage = new InMemoryInvalidBlockStorage @@ -92,7 +92,7 @@ class RxExtensionLoaderSpec extends FreeSpec with RxScheduler with BlockGen { "should blacklist GetSignatures timeout" in withExtensionLoader(Seq.tabulate(100)(byteStr), 1.millis) { (_, _, _, ccsw, _) => val ch = new EmbeddedChannel() test(for { - _ <- send(ccsw)(ChannelClosedAndSyncWith(None, Some(BestChannel(ch, 1: BigInt)))) + _ <- send(ccsw, timeout = 1000)(ChannelClosedAndSyncWith(None, Some(BestChannel(ch, 1: BigInt)))) } yield { ch.isOpen shouldBe false }) diff --git a/node/src/test/scala/com/wavesplatform/state/BlockChallengeTest.scala b/node/src/test/scala/com/wavesplatform/state/BlockChallengeTest.scala index 2755cf490a..ec22a04b94 100644 --- a/node/src/test/scala/com/wavesplatform/state/BlockChallengeTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/BlockChallengeTest.scala @@ -5,8 +5,8 @@ import akka.http.scaladsl.server.Route import akka.http.scaladsl.testkit.* import com.wavesplatform.TestValues import com.wavesplatform.account.{Address, KeyPair, SeedKeyPair} -import com.wavesplatform.api.http.TransactionsApiRoute.{ApplicationStatus, Status} import com.wavesplatform.api.http.* +import com.wavesplatform.api.http.TransactionsApiRoute.{ApplicationStatus, Status} import com.wavesplatform.block.{Block, ChallengedHeader, MicroBlock} import com.wavesplatform.common.merkle.Merkle import com.wavesplatform.common.state.ByteStr @@ -36,9 +36,8 @@ import com.wavesplatform.test.DomainPresets.WavesSettingsOps import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.TxValidationError.{BlockAppendError, GenericError, InvalidStateHash, MicroBlockAppendError} import com.wavesplatform.transaction.assets.exchange.OrderType -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer import com.wavesplatform.transaction.utils.EthConverters.* -import com.wavesplatform.transaction.{EthTxGenerator, Transaction, TxHelpers, TxNonNegativeAmount, TxVersion} +import com.wavesplatform.transaction.{EthTxGenerator, Transaction, TxHelpers, TxVersion} import com.wavesplatform.utils.{JsonMatchers, Schedulers, SharedSchedulerMixin} import io.netty.channel.Channel import io.netty.channel.embedded.EmbeddedChannel @@ -55,8 +54,15 @@ import java.util.concurrent.locks.ReentrantLock import scala.concurrent.duration.DurationInt import scala.concurrent.{Await, Promise} -class BlockChallengeTest extends PropSpec - with WithDomain with ScalatestRouteTest with ApiMarshallers with JsonMatchers with SharedSchedulerMixin with ParallelTestExecution with BeforeAndAfterAll { +class BlockChallengeTest + extends PropSpec + with WithDomain + with ScalatestRouteTest + with ApiMarshallers + with JsonMatchers + with SharedSchedulerMixin + with ParallelTestExecution + with BeforeAndAfterAll { implicit val appenderScheduler: SchedulerService = Scheduler.singleThread("appender") val settings: WavesSettings = @@ -65,7 +71,10 @@ class BlockChallengeTest extends PropSpec val invalidStateHash: ByteStr = ByteStr.fill(DigestLength)(1) - override def afterAll(): Unit = appenderScheduler.shutdown() + override def afterAll(): Unit = { + super.afterAll() + appenderScheduler.shutdown() + } property("NODE-883. Invalid challenging block should be ignored") { val sender = TxHelpers.signer(1) @@ -479,7 +488,7 @@ class BlockChallengeTest extends PropSpec lease, TxHelpers.leaseCancel(lease.id(), recipient), TxHelpers - .massTransfer(recipient, Seq(ParsedTransfer(recipientEth.toWavesAddress, TxNonNegativeAmount.unsafeFrom(1.waves))), fee = TestValues.fee), + .massTransfer(recipient, Seq(recipientEth.toWavesAddress -> 1.waves), fee = TestValues.fee), TxHelpers.reissue(issue.asset, recipient), TxHelpers.setAssetScript(recipient, issueSmart.asset, assetScript, fee = 2.waves), TxHelpers.transfer(recipient, recipientEth.toWavesAddress, 100.waves), diff --git a/node/src/test/scala/com/wavesplatform/state/Diff.scala b/node/src/test/scala/com/wavesplatform/state/Diff.scala deleted file mode 100755 index d71987f26b..0000000000 --- a/node/src/test/scala/com/wavesplatform/state/Diff.scala +++ /dev/null @@ -1,182 +0,0 @@ -package com.wavesplatform.state - -import cats.data.Ior -import cats.implicits.{catsSyntaxEitherId, catsSyntaxSemigroup, toTraverseOps} -import com.google.common.hash.{BloomFilter, Funnels} -import com.wavesplatform.account.{Address, Alias, PublicKey} -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.database.protobuf.EthereumTransactionMeta -import com.wavesplatform.state.reader.LeaseDetails -import com.wavesplatform.transaction.Asset.IssuedAsset -import com.wavesplatform.transaction.Transaction - -import scala.collection.immutable.VectorMap -import scala.util.chaining.* - -case class Diff( - transactions: Vector[NewTransactionInfo], - portfolios: Map[Address, Portfolio], - issuedAssets: VectorMap[IssuedAsset, NewAssetInfo], - updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]], - aliases: Map[Alias, Address], - orderFills: Map[ByteStr, VolumeAndFee], - leaseState: Map[ByteStr, LeaseDetails], - scripts: Map[PublicKey, Option[AccountScriptInfo]], - assetScripts: Map[IssuedAsset, Option[AssetScriptInfo]], - accountData: Map[Address, Map[String, DataEntry[?]]], - sponsorship: Map[IssuedAsset, Sponsorship], - scriptsRun: Int, - scriptsComplexity: Long, - scriptResults: Map[ByteStr, InvokeScriptResult], - ethereumTransactionMeta: Map[ByteStr, EthereumTransactionMeta], - transactionFilter: Option[BloomFilter[Array[Byte]]] -) { - def containsTransaction(txId: ByteStr): Boolean = - transactions.nonEmpty && transactionFilter.exists(_.mightContain(txId.arr)) && transactions.exists(_.transaction.id() == txId) - - def transaction(txId: ByteStr): Option[NewTransactionInfo] = - if (transactions.nonEmpty && transactionFilter.exists(_.mightContain(txId.arr))) - transactions.find(_.transaction.id() == txId) - else None - - def combineF(newer: Diff): Either[String, Diff] = - for { - portfolios <- Portfolio.combine(portfolios, newer.portfolios) - orderFills <- { - val combinedOrders = - orderFills.toSeq - .traverse { case kv @ (orderId, value) => - newer.orderFills.get(orderId).fold(kv.asRight[String])(value.combineE(_).map(orderId -> _)) - } - .map(_.toMap) - combinedOrders.map(co => co ++ newer.orderFills.filterNot { case (id, _) => co.contains(id) }) - } - newTransactions = if (transactions.isEmpty) newer.transactions else transactions ++ newer.transactions - newFilter = transactionFilter match { - case Some(bf) => - newer.transactions.foreach(nti => bf.put(nti.transaction.id().arr)) - Some(bf) - case None => - newer.transactionFilter - } - } yield Diff( - transactions = newTransactions, - portfolios = portfolios, - issuedAssets = issuedAssets ++ newer.issuedAssets, - updatedAssets = updatedAssets |+| newer.updatedAssets, - aliases = aliases ++ newer.aliases, - orderFills = orderFills, - leaseState = leaseState ++ newer.leaseState, - scripts = scripts ++ newer.scripts, - assetScripts = assetScripts ++ newer.assetScripts, - accountData = Diff.combine(accountData, newer.accountData), - sponsorship = sponsorship.combine(newer.sponsorship), - scriptsRun = scriptsRun + newer.scriptsRun, - scriptResults = scriptResults.combine(newer.scriptResults), - scriptsComplexity = scriptsComplexity + newer.scriptsComplexity, - ethereumTransactionMeta = ethereumTransactionMeta ++ newer.ethereumTransactionMeta, - transactionFilter = newFilter - ) -} - -object Diff { - def apply( - portfolios: Map[Address, Portfolio] = Map.empty, - issuedAssets: VectorMap[IssuedAsset, NewAssetInfo] = VectorMap.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[PublicKey, Option[AccountScriptInfo]] = Map.empty, - assetScripts: Map[IssuedAsset, Option[AssetScriptInfo]] = Map.empty, - accountData: Map[Address, Map[String, DataEntry[?]]] = 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: VectorMap[IssuedAsset, NewAssetInfo] = VectorMap.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[PublicKey, Option[AccountScriptInfo]] = Map.empty, - assetScripts: Map[IssuedAsset, Option[AssetScriptInfo]] = Map.empty, - accountData: Map[Address, Map[String, DataEntry[?]]] = 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() - - private def combine[K, IK, IV](first: Map[K, Map[IK, IV]], second: Map[K, Map[IK, IV]]): Map[K, Map[IK, IV]] = { - if (first.isEmpty) { - second - } else { - first ++ second.map { case (k, innerMap) => - k -> first.get(k).fold(innerMap)(_ ++ innerMap) - } - } - } - - 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) - } -} diff --git a/node/src/test/scala/com/wavesplatform/state/LightNodeTest.scala b/node/src/test/scala/com/wavesplatform/state/LightNodeTest.scala index 807d67a5ff..409c6a7a78 100644 --- a/node/src/test/scala/com/wavesplatform/state/LightNodeTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/LightNodeTest.scala @@ -7,15 +7,16 @@ import com.wavesplatform.db.WithDomain import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.history.Domain import com.wavesplatform.mining.MiningConstraint -import com.wavesplatform.network.{ExtensionBlocks, InvalidBlockStorage, PeerDatabase} +import com.wavesplatform.network.{BlockSnapshotResponse, ExtensionBlocks, InvalidBlockStorage, PeerDatabase} +import com.wavesplatform.protobuf.PBSnapshots import com.wavesplatform.settings.WavesSettings import com.wavesplatform.state.BlockchainUpdaterImpl.BlockApplyResult.Applied import com.wavesplatform.state.appender.{BlockAppender, ExtensionAppender} import com.wavesplatform.state.diffs.BlockDiffer -import com.wavesplatform.state.reader.SnapshotBlockchain import com.wavesplatform.test.* import com.wavesplatform.transaction.TxHelpers import com.wavesplatform.transaction.TxValidationError.InvalidStateHash +import io.netty.channel.embedded.EmbeddedChannel import monix.execution.Scheduler import monix.execution.Scheduler.Implicits.global @@ -134,12 +135,22 @@ class LightNodeTest extends PropSpec with WithDomain { val extensionBlocks = ExtensionBlocks( currentScore + 1, betterBlocks.map(_._1), - betterBlocks.collect { case (b, Some(snapshots)) => b.id() -> BlockSnapshot(b.id(), snapshots) }.toMap + betterBlocks.collect { case (b, Some(snapshots)) => + b.id() -> BlockSnapshotResponse(b.id(), snapshots.map { case (s, m) => PBSnapshots.toProtobuf(s, m) }) + }.toMap ) val appender = - ExtensionAppender(d.blockchain, d.utxPool, d.posSelector, TestTime(), InvalidBlockStorage.NoOp, PeerDatabase.NoOp, Scheduler.global)( - null, + ExtensionAppender( + d.blockchain, + d.utxPool, + d.posSelector, + TestTime(extensionBlocks.blocks.last.header.timestamp), + InvalidBlockStorage.NoOp, + PeerDatabase.NoOp, + Scheduler.global + )( + new EmbeddedChannel(), _ ) @@ -162,9 +173,10 @@ class LightNodeTest extends PropSpec with WithDomain { val challengingBlock = d.createChallengingBlock(challengingMiner, invalidBlock, strictTime = true) val txSnapshots = getTxSnapshots(d, challengingBlock) - val appender = BlockAppender(d.blockchainUpdater, TestTime(), d.utxPool, d.posSelector, Scheduler.global) _ + val appender = BlockAppender(d.blockchainUpdater, TestTime(challengingBlock.header.timestamp), d.utxPool, d.posSelector, Scheduler.global) _ - appender(challengingBlock, Some(BlockSnapshot(challengingBlock.id(), txSnapshots))).runSyncUnsafe() shouldBe Right( + val sr = BlockSnapshotResponse(challengingBlock.id(), txSnapshots.map { case (s, m) => PBSnapshots.toProtobuf(s, m) }) + appender(challengingBlock, Some(sr)).runSyncUnsafe() shouldBe Right( Applied(Seq.empty, d.blockchain.score) ) d.lastBlock shouldBe challengingBlock diff --git a/node/src/test/scala/com/wavesplatform/state/RollbackSpec.scala b/node/src/test/scala/com/wavesplatform/state/RollbackSpec.scala index 1a7deaec2e..db98f19aca 100644 --- a/node/src/test/scala/com/wavesplatform/state/RollbackSpec.scala +++ b/node/src/test/scala/com/wavesplatform/state/RollbackSpec.scala @@ -18,14 +18,13 @@ import com.wavesplatform.lang.v1.compiler.Terms.TRUE import com.wavesplatform.lang.v1.compiler.{Terms, TestCompiler} import com.wavesplatform.lang.v1.traits.domain.Lease import com.wavesplatform.settings.{TestFunctionalitySettings, WavesSettings} -import com.wavesplatform.state.reader.LeaseDetails import com.wavesplatform.test.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.AliasDoesNotExist import com.wavesplatform.transaction.lease.LeaseTransaction import com.wavesplatform.transaction.smart.{InvokeTransaction, SetScriptTransaction} import com.wavesplatform.transaction.transfer.* -import com.wavesplatform.transaction.{Transaction, TxHelpers, TxNonNegativeAmount, TxVersion} +import com.wavesplatform.transaction.{Transaction, TxHelpers, TxPositiveAmount, TxVersion} import org.scalatest.{Assertion, Assertions} class RollbackSpec extends FreeSpec with WithDomain { @@ -33,7 +32,6 @@ class RollbackSpec extends FreeSpec with WithDomain { private def nextTs = time.getTimestamp() private def randomOp(sender: KeyPair, recipient: Address, amount: Long, op: Int, nextTs: => Long = nextTs) = { - import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer op match { case 1 => val lease = TxHelpers.lease(sender, recipient, amount, fee = 100000L, timestamp = nextTs, version = TxVersion.V1) @@ -43,7 +41,10 @@ class RollbackSpec extends FreeSpec with WithDomain { List( TxHelpers.massTransfer( sender, - Seq(ParsedTransfer(recipient, TxNonNegativeAmount.unsafeFrom(amount)), ParsedTransfer(recipient, TxNonNegativeAmount.unsafeFrom(amount))), + Seq( + recipient -> amount, + recipient -> amount + ), fee = 10000, timestamp = nextTs, version = TxVersion.V1 @@ -181,7 +182,7 @@ class RollbackSpec extends FreeSpec with WithDomain { d.blockchainUpdater.height shouldBe 2 val blockWithLeaseId = d.lastBlockId d.blockchainUpdater.leaseDetails(lt.id()) should contain( - LeaseDetails(sender.publicKey, recipient.toAddress, leaseAmount, LeaseDetails.Status.Active, lt.id(), 2) + LeaseDetails(LeaseStaticInfo(sender.publicKey, recipient.toAddress, lt.amount, lt.id(), 2), LeaseDetails.Status.Active) ) d.blockchainUpdater.leaseBalance(sender.toAddress).out shouldEqual leaseAmount d.blockchainUpdater.leaseBalance(recipient.toAddress).in shouldEqual leaseAmount @@ -198,12 +199,8 @@ class RollbackSpec extends FreeSpec with WithDomain { ) d.blockchainUpdater.leaseDetails(lt.id()) should contain( LeaseDetails( - sender.publicKey, - recipient.toAddress, - leaseAmount, - LeaseDetails.Status.Cancelled(d.blockchain.height, Some(leaseCancel.id())), - lt.id(), - 2 + LeaseStaticInfo(sender.publicKey, recipient.toAddress, lt.amount, lt.id(), 2), + LeaseDetails.Status.Cancelled(d.blockchain.height, Some(leaseCancel.id())) ) ) d.blockchainUpdater.leaseBalance(sender.toAddress).out shouldEqual 0 @@ -211,7 +208,7 @@ class RollbackSpec extends FreeSpec with WithDomain { d.rollbackTo(blockWithLeaseId) d.blockchainUpdater.leaseDetails(lt.id()) should contain( - LeaseDetails(sender.publicKey, recipient.toAddress, leaseAmount, LeaseDetails.Status.Active, lt.id(), 2) + LeaseDetails(LeaseStaticInfo(sender.publicKey, recipient.toAddress, lt.amount, lt.id(), 2), LeaseDetails.Status.Active) ) d.blockchainUpdater.leaseBalance(sender.toAddress).out shouldEqual leaseAmount d.blockchainUpdater.leaseBalance(recipient.toAddress).in shouldEqual leaseAmount @@ -694,7 +691,12 @@ class RollbackSpec extends FreeSpec with WithDomain { val (leaseAmount, leaseFc) = leaseFunctionCall(leaseRecipientAddress.toAddress) def leaseDetails(invokeId: ByteStr) = - Some(LeaseDetails(checkPk, leaseRecipientAddress.toAddress, leaseAmount, LeaseDetails.Status.Active, invokeId, 3)) + Some( + LeaseDetails( + LeaseStaticInfo(checkPk, leaseRecipientAddress.toAddress, TxPositiveAmount.unsafeFrom(leaseAmount), invokeId, 3), + LeaseDetails.Status.Active + ) + ) // liquid block rollback val invokeId1 = append(d.lastBlockId, leaseFc) @@ -758,13 +760,9 @@ class RollbackSpec extends FreeSpec with WithDomain { def leaseDetails(leaseHeight: Int, cancelHeight: Int = 0, cancelId: ByteStr = ByteStr.empty) = Some( LeaseDetails( - checkPk, - leaseRecipientAddress, - leaseAmount, + LeaseStaticInfo(checkPk, leaseRecipientAddress, TxPositiveAmount.unsafeFrom(leaseAmount), sourceId, leaseHeight), if (cancelId.isEmpty) LeaseDetails.Status.Active - else LeaseDetails.Status.Cancelled(cancelHeight, Some(cancelId)), - sourceId, - leaseHeight + else LeaseDetails.Status.Cancelled(cancelHeight, Some(cancelId)) ) ) diff --git a/node/src/test/scala/com/wavesplatform/state/StateHashSpec.scala b/node/src/test/scala/com/wavesplatform/state/StateHashSpec.scala index d1aab7e32e..b670994705 100644 --- a/node/src/test/scala/com/wavesplatform/state/StateHashSpec.scala +++ b/node/src/test/scala/com/wavesplatform/state/StateHashSpec.scala @@ -37,7 +37,7 @@ class StateHashSpec extends FreeSpec { stateHash.addAlias(address, "test1") stateHash.addAlias(address1, "test2") stateHash.addDataEntry(address, dataEntry) - stateHash.addLeaseStatus(TransactionId @@ assetId.id, status = true) + stateHash.addLeaseStatus(TransactionId @@ assetId.id, isActive = true) stateHash.addSponsorship(assetId, 1000) stateHash.addAssetBalance(address, assetId, 2000) stateHash.addAssetBalance(address1, assetId, 2000) diff --git a/node/src/test/scala/com/wavesplatform/state/TransactionsByAddressSpec.scala b/node/src/test/scala/com/wavesplatform/state/TransactionsByAddressSpec.scala index 39ea66c786..53dcb4c89e 100644 --- a/node/src/test/scala/com/wavesplatform/state/TransactionsByAddressSpec.scala +++ b/node/src/test/scala/com/wavesplatform/state/TransactionsByAddressSpec.scala @@ -21,7 +21,6 @@ import java.util.concurrent.locks.ReentrantLock import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.{Await, Future} import scala.concurrent.duration.* -import scala.concurrent.duration.Duration.Inf class TransactionsByAddressSpec extends FreeSpec with BlockGen with WithDomain { def transfers(sender: KeyPair, rs: AddressOrAlias, amount: Long): Seq[TransferTransaction] = @@ -122,7 +121,7 @@ class TransactionsByAddressSpec extends FreeSpec with BlockGen with WithDomain { val txs = Future { d.addressTransactions(defaultAddress).map(_._2.tpe) } d.blockchain.bestLiquidSnapshot.synchronized(d.appendKeyBlock()) startRead.unlock() - Await.result(txs, Inf).map(_.tpe) shouldBe List(TransactionType.Issue) + Await.result(txs, 1.minute).map(_.tpe) shouldBe List(TransactionType.Issue) } } } 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 8786f5d65e..ef12359b94 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/AssetTransactionsDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/AssetTransactionsDiffTest.scala @@ -24,7 +24,8 @@ import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationErro import com.wavesplatform.state.diffs.smart.smartEnabledFS import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.* -import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} +import com.wavesplatform.transaction.TxHelpers.defaultAddress import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.assets.* import com.wavesplatform.transaction.transfer.* @@ -42,7 +43,7 @@ class AssetTransactionsDiffTest extends PropSpec with BlocksTransactionsHelpers val issue = TxHelpers.issue(master, 100, reissuable = isReissuable, version = TxVersion.V1) val asset = IssuedAsset(issue.id()) - val reissue = TxHelpers.reissue(asset, master, 50, version = TxVersion.V1) + val reissue = TxHelpers.reissue(asset, master, 50, version = TxVersion.V1, fee = 1.waves) val burn = TxHelpers.burn(asset, 10, master, version = TxVersion.V1) ((genesis, issue), (reissue, burn)) @@ -50,15 +51,27 @@ class AssetTransactionsDiffTest extends PropSpec with BlocksTransactionsHelpers property("Issue+Reissue+Burn do not break waves invariant and updates state") { val ((gen, issue), (reissue, burn)) = issueReissueBurnTxs(isReissuable = true) - assertDiffAndState(Seq(TestBlock.create(Seq(gen, issue))), TestBlock.create(Seq(reissue, burn))) { case (blockDiff, newState) => - val totalPortfolioDiff = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - - totalPortfolioDiff.balance shouldBe 0 - totalPortfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0 - totalPortfolioDiff.assets shouldBe Map(reissue.asset -> (reissue.quantity.value - burn.quantity.value)) - - val totalAssetVolume = issue.quantity.value + reissue.quantity.value - burn.quantity.value - newState.balance(issue.sender.toAddress, reissue.asset) shouldEqual totalAssetVolume + withDomain(RideV3) { d => + d.appendBlock(gen) + d.appendBlock(issue) + d.appendBlock(reissue, burn) + val assetQuantityDiff = reissue.quantity.value - burn.quantity.value + d.liquidSnapshot.balances.toSeq + .map { + case ((`defaultAddress`, Waves), amount) => + val carryFee = (-issue.fee.value + reissue.fee.value + burn.fee.value) / 5 * 3 + Waves -> (amount - d.rocksDBWriter.balance(defaultAddress, Waves) + carryFee) + case ((address, asset), amount) => + asset -> (amount - d.rocksDBWriter.balance(address, asset)) + } + .groupMap(_._1)(_._2) + .foreach { + case (asset, Seq(balanceDiff)) if asset == reissue.asset => balanceDiff shouldBe assetQuantityDiff + case (_, balanceDiff) => balanceDiff.sum shouldBe 0 + } + val resultQuantity = issue.quantity.value + assetQuantityDiff + d.liquidSnapshot.assetVolumes.view.mapValues(_.volume).toMap shouldBe Map(reissue.asset -> resultQuantity) + d.balance(issue.sender.toAddress, reissue.asset) shouldEqual resultQuantity } } @@ -205,7 +218,7 @@ class AssetTransactionsDiffTest extends PropSpec with BlocksTransactionsHelpers private def createScript(code: String, version: StdLibVersion) = { val Parsed.Success(expr, _) = Parser.parseExpr(code).get - ExprScript(version, ExpressionCompiler(compilerContext(version, Expression, isAssetScript = false), expr).explicitGet()._1).explicitGet() + ExprScript(version, ExpressionCompiler(compilerContext(version, Expression, isAssetScript = false), version, expr).explicitGet()._1).explicitGet() } def genesisIssueTransferReissue( @@ -249,7 +262,7 @@ class AssetTransactionsDiffTest extends PropSpec with BlocksTransactionsHelpers Height @@ 2 ) ) - blockDiff.transaction(issue.id()) shouldBe defined + blockDiff.transactions.get(issue.id()) shouldBe defined newState.transactionInfo(issue.id()).isDefined shouldBe true } } @@ -257,8 +270,8 @@ class AssetTransactionsDiffTest extends PropSpec with BlocksTransactionsHelpers property("Can transfer when script evaluates to TRUE") { val (gen, issue, transfer, _, _) = genesisIssueTransferReissue("true") assertDiffAndState(Seq(TestBlock.create(gen)), TestBlock.create(Seq(issue, transfer)), smartEnabledFS) { case (blockDiff, newState) => - val totalPortfolioDiff = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - totalPortfolioDiff.assets(IssuedAsset(issue.id())) shouldEqual issue.quantity.value + val asset = IssuedAsset(issue.id()) + blockDiff.balances.collect { case ((_, `asset`), quantity) => quantity }.sum shouldEqual issue.quantity.value newState.balance(newState.resolveAlias(transfer.recipient).explicitGet(), IssuedAsset(issue.id())) shouldEqual transfer.amount.value } } @@ -320,12 +333,7 @@ class AssetTransactionsDiffTest extends PropSpec with BlocksTransactionsHelpers ) assertDiffEi(blocks, TestBlock.create(Seq(update), Block.ProtoBlockVersion), assetInfoUpdateEnabled) { ei => - val info = ei - .explicitGet() - .updatedAssets(update.assetId) - .left - .get - + val info = ei.explicitGet().assetNamesAndDescriptions(update.assetId) info.name.toStringUtf8 shouldEqual update.name info.description.toStringUtf8 shouldEqual update.description } @@ -403,8 +411,8 @@ class AssetTransactionsDiffTest extends PropSpec with BlocksTransactionsHelpers val (genesis1, issue1, _, _, _) = genesisIssueTransferReissue(exprV4WithComplexityBetween3000And4000, V4) assertDiffAndState(Seq(TestBlock.create(genesis1)), TestBlock.create(Seq(issue1)), rideV4Activated) { case (blockDiff, _) => - val totalPortfolioDiff = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - totalPortfolioDiff.assets(IssuedAsset(issue1.id())) shouldEqual issue1.quantity.value + val asset = IssuedAsset(issue1.id()) + blockDiff.balances.collect { case ((_, `asset`), quantity) => quantity }.sum shouldEqual issue1.quantity.value } val (genesis2, issue2, _, _, _) = genesisIssueTransferReissue(exprV4WithComplexityAbove4000, V4) @@ -467,7 +475,7 @@ class AssetTransactionsDiffTest extends PropSpec with BlocksTransactionsHelpers db.appendBlock(preparingTxs*) val tx = scriptedTx() db.appendBlock(tx) - db.liquidDiff.errorMessage(tx.id()) shouldBe None + db.liquidSnapshot.errorMessage(tx.id()) shouldBe None } withDomain(domainSettingsWithFS(settings(checkNegative = true))) { db => diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/BalanceDiffValidationTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/BalanceDiffValidationTest.scala index dc64f34548..3106324e0d 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/BalanceDiffValidationTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/BalanceDiffValidationTest.scala @@ -40,8 +40,8 @@ class BalanceDiffValidationTest extends PropSpec with WithState { Seq(TestBlock.create(Seq(genesis, masterTransfersToAlice, aliceLeasesToBob, masterLeasesToAlice))), TestBlock.create(Seq(aliceTransfersMoreThanOwnsMinusLeaseOut)), settings - ) { totalDiffEi => - totalDiffEi.explicitGet() + ) { snapshotEi => + snapshotEi.explicitGet() } } @@ -59,8 +59,8 @@ class BalanceDiffValidationTest extends PropSpec with WithState { ), TestBlock.create(Seq(aliceTransfersMoreThanOwnsMinusLeaseOut)), settings - ) { totalDiffEi => - totalDiffEi should produce("trying to spend leased money") + ) { snapshotEi => + snapshotEi should produce("trying to spend leased money") } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/BlockDifferTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/BlockDifferTest.scala index d845315504..68b26d8341 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/BlockDifferTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/BlockDifferTest.scala @@ -13,8 +13,7 @@ import com.wavesplatform.lagonaki.mocks.TestBlock.BlockWithSigner import com.wavesplatform.mining.{MinerImpl, MiningConstraint} import com.wavesplatform.settings.FunctionalitySettings import com.wavesplatform.state.diffs.BlockDiffer.Result -import com.wavesplatform.state.reader.SnapshotBlockchain -import com.wavesplatform.state.{Blockchain, Diff, StateSnapshot, TxStateSnapshotHashBuilder} +import com.wavesplatform.state.{Blockchain, SnapshotBlockchain, StateSnapshot, TxStateSnapshotHashBuilder} import com.wavesplatform.test.* import com.wavesplatform.test.node.* import com.wavesplatform.transaction.TxValidationError.InvalidStateHash @@ -303,7 +302,7 @@ class BlockDifferTest extends FreeSpec with WithDomain { } } - private def assertDiff(blocks: Seq[BlockWithSigner], ngAtHeight: Int)(assertion: (Diff, Blockchain) => Unit): Unit = { + private def assertDiff(blocks: Seq[BlockWithSigner], ngAtHeight: Int)(assertion: (StateSnapshot, Blockchain) => Unit): Unit = { val fs = FunctionalitySettings( featureCheckBlocksPeriod = ngAtHeight / 2, blocksForFeatureActivation = 1, diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/CommonValidationTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/CommonValidationTest.scala index b7a05d1047..6fc1a3bade 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/CommonValidationTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/CommonValidationTest.scala @@ -14,9 +14,8 @@ import com.wavesplatform.settings.{Constants, FunctionalitySettings, TestFunctio import com.wavesplatform.test.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.assets.exchange.OrderType -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer import com.wavesplatform.transaction.transfer.* -import com.wavesplatform.transaction.{GenesisTransaction, Transaction, TxHelpers, TxNonNegativeAmount, TxVersion} +import com.wavesplatform.transaction.{GenesisTransaction, Transaction, TxHelpers, TxVersion} class CommonValidationTest extends PropSpec with WithState { @@ -198,7 +197,7 @@ class CommonValidationTest extends PropSpec with WithState { chainId = invChainId ), TxHelpers.issue(master, amount, chainId = invChainId), - TxHelpers.massTransfer(master, Seq(ParsedTransfer(invChainAddr, TxNonNegativeAmount.unsafeFrom(amount))), chainId = invChainId), + TxHelpers.massTransfer(master, Seq(invChainAddr -> amount), chainId = invChainId), TxHelpers.leaseCancel(asset.id, master, version = TxVersion.V3, chainId = invChainId), TxHelpers.setScript(master, script, version = TxVersion.V2, chainId = invChainId), TxHelpers.setAssetScript(master, asset, script, version = TxVersion.V2, chainId = invChainId), diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/CreateAliasTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/CreateAliasTransactionDiffTest.scala index 0b8a11fbc9..31483e179a 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/CreateAliasTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/CreateAliasTransactionDiffTest.scala @@ -2,22 +2,24 @@ package com.wavesplatform.state.diffs import com.wavesplatform.account.Alias import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.db.WithState +import com.wavesplatform.db.WithDomain import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.history.SnapshotOps +import com.wavesplatform.features.BlockchainFeatures.SmartAccounts import com.wavesplatform.lagonaki.mocks.TestBlock import com.wavesplatform.settings.{FunctionalitySettings, TestFunctionalitySettings} import com.wavesplatform.state.* import com.wavesplatform.state.utils.addressTransactions import com.wavesplatform.test.* -import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} +import com.wavesplatform.test.DomainPresets.{NG, RideV3, WavesSettingsOps} +import com.wavesplatform.transaction.Asset.Waves +import com.wavesplatform.transaction.TxHelpers.defaultAddress import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.transaction.lease.LeaseTransaction import com.wavesplatform.transaction.transfer.TransferTransaction import com.wavesplatform.transaction.{Asset, CreateAliasTransaction, GenesisTransaction, TransactionType, TxHelpers, TxVersion} import monix.execution.Scheduler.Implicits.global -class CreateAliasTransactionDiffTest extends PropSpec with WithState { +class CreateAliasTransactionDiffTest extends PropSpec with WithDomain { val fs: FunctionalitySettings = TestFunctionalitySettings.Enabled.copy( @@ -39,16 +41,16 @@ class CreateAliasTransactionDiffTest extends PropSpec with WithState { TxHelpers.createAlias(alias.name, master, fee = fee, version = TxVersion.V1) ) val sameAliasTxs = Seq( - TxHelpers.createAlias(alias.name, master, fee = fee + 1), - TxHelpers.createAlias(alias.name, master, fee = fee + 1, version = TxVersion.V1) + TxHelpers.createAlias(alias.name, master, fee = fee + 100), + TxHelpers.createAlias(alias.name, master, fee = fee + 100, version = TxVersion.V1) ) val sameAliasOtherSenderTxs = Seq( - TxHelpers.createAlias(alias.name, other, fee = fee + 2), - TxHelpers.createAlias(alias.name, other, fee = fee + 2, version = TxVersion.V1) + TxHelpers.createAlias(alias.name, other, fee = fee + 200), + TxHelpers.createAlias(alias.name, other, fee = fee + 200, version = TxVersion.V1) ) val anotherAliasTxs = Seq( - TxHelpers.createAlias(alias2.name, master, fee = fee + 3), - TxHelpers.createAlias(alias2.name, master, fee = fee + 3, version = TxVersion.V1) + TxHelpers.createAlias(alias2.name, master, fee = fee + 300), + TxHelpers.createAlias(alias2.name, master, fee = fee + 300, version = TxVersion.V1) ) for { @@ -61,28 +63,28 @@ class CreateAliasTransactionDiffTest extends PropSpec with WithState { property("can create and resolve aliases preserving waves invariant") { preconditionsAndAliasCreations.foreach { case (gen, aliasTx, _, _, anotherAliasTx) => - assertDiffAndState(Seq(TestBlock.create(Seq(gen, aliasTx))), TestBlock.create(Seq(anotherAliasTx)), fs) { case (blockDiff, newState) => - val totalPortfolioDiff = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - totalPortfolioDiff.balance shouldBe 0 - totalPortfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0 - + withDomain(RideV3) { d => + d.appendBlock(gen) + d.appendBlock(aliasTx) + d.appendBlock(anotherAliasTx) + d.liquidSnapshot.balances.collect { + case ((`defaultAddress`, Waves), balance) => + val carryFee = (anotherAliasTx.fee.value - aliasTx.fee.value) / 5 * 3 + balance - d.rocksDBWriter.balance(defaultAddress) + carryFee + case ((address, Waves), balance) => + balance - d.rocksDBWriter.balance(address) + }.sum shouldBe 0 val senderAcc = anotherAliasTx.sender.toAddress - blockDiff.aliases shouldBe Map(anotherAliasTx.alias -> senderAcc) - + d.liquidSnapshot.aliases shouldBe Map(anotherAliasTx.alias -> senderAcc) addressTransactions( rdb, - Some(Height(newState.height + 1) -> SnapshotOps.fromDiff(blockDiff, newState).explicitGet()), + Some(Height(d.blockchain.height + 1) -> d.liquidSnapshot), senderAcc, Set(TransactionType.CreateAlias), None - ).collect { case (_, cat: CreateAliasTransaction) => - cat.alias - }.toSet shouldBe Set( - anotherAliasTx.alias, - aliasTx.alias - ) - newState.resolveAlias(aliasTx.alias) shouldBe Right(senderAcc) - newState.resolveAlias(anotherAliasTx.alias) shouldBe Right(senderAcc) + ).collect { case (_, cat: CreateAliasTransaction) => cat.alias }.toSet shouldBe Set(anotherAliasTx.alias, aliasTx.alias) + d.blockchain.resolveAlias(aliasTx.alias) shouldBe Right(senderAcc) + d.blockchain.resolveAlias(anotherAliasTx.alias) shouldBe Right(senderAcc) } } } @@ -92,7 +94,6 @@ class CreateAliasTransactionDiffTest extends PropSpec with WithState { assertDiffEi(Seq(TestBlock.create(Seq(gen, aliasTx))), TestBlock.create(Seq(sameAliasTx)), fs) { blockDiffEi => blockDiffEi should produce("AlreadyInTheState") } - assertDiffEi(Seq(TestBlock.create(Seq(gen, aliasTx))), TestBlock.create(Seq(sameAliasOtherSenderTx)), fs) { blockDiffEi => blockDiffEi should produce("AlreadyInTheState") } @@ -129,13 +130,13 @@ class CreateAliasTransactionDiffTest extends PropSpec with WithState { property("Can transfer to alias") { preconditionsTransferLease.foreach { case (genesis, issue1, issue2, aliasTx, transfer, _) => - assertDiffAndState(Seq(TestBlock.create(genesis :+ issue1 :+ issue2 :+ aliasTx)), TestBlock.create(Seq(transfer))) { case (blockDiff, _) => + withDomain(NG.addFeatures(SmartAccounts)) { d => + d.appendBlock(genesis*) + d.appendBlock(issue1, issue2, aliasTx) + d.appendBlock(transfer) if (transfer.sender.toAddress != aliasTx.sender.toAddress) { - val recipientPortfolioDiff = blockDiff.portfolios(aliasTx.sender.toAddress) - transfer.assetId match { - case aid @ IssuedAsset(_) => recipientPortfolioDiff shouldBe Portfolio.build(aid, transfer.amount.value) - case Waves => recipientPortfolioDiff shouldBe Portfolio(transfer.amount.value) - } + d.liquidSnapshot.balances((aliasTx.sender.toAddress, transfer.assetId)) shouldBe + transfer.amount.value + d.rocksDBWriter.balance(aliasTx.sender.toAddress, transfer.assetId) } } } @@ -145,8 +146,8 @@ class CreateAliasTransactionDiffTest extends PropSpec with WithState { preconditionsTransferLease.foreach { case (genesis, issue1, issue2, aliasTx, _, lease) => assertDiffEi(Seq(TestBlock.create(genesis :+ issue1 :+ issue2 :+ aliasTx)), TestBlock.create(Seq(lease))) { blockDiffEi => if (lease.sender.toAddress != aliasTx.sender.toAddress) { - val recipientPortfolioDiff = blockDiffEi.explicitGet().portfolios(aliasTx.sender.toAddress) - recipientPortfolioDiff shouldBe Portfolio(0, LeaseBalance(lease.amount.value, 0)) + blockDiffEi.explicitGet().balances.get((aliasTx.sender.toAddress, Waves)) shouldBe None + blockDiffEi.explicitGet().leaseBalances(aliasTx.sender.toAddress) shouldBe LeaseBalance(lease.amount.value, 0) } else { blockDiffEi should produce("Cannot lease to self") } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/DataTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/DataTransactionDiffTest.scala index 4ddaaff042..3295bf319e 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/DataTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/DataTransactionDiffTest.scala @@ -2,15 +2,17 @@ package com.wavesplatform.state.diffs import com.wavesplatform.account.KeyPair import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.db.WithState +import com.wavesplatform.db.WithDomain import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.lagonaki.mocks.TestBlock.create as block import com.wavesplatform.settings.TestFunctionalitySettings import com.wavesplatform.state.{BinaryDataEntry, BooleanDataEntry, IntegerDataEntry} import com.wavesplatform.test.* +import com.wavesplatform.test.DomainPresets.RideV3 +import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.{GenesisTransaction, TxHelpers} -class DataTransactionDiffTest extends PropSpec with WithState { +class DataTransactionDiffTest extends PropSpec with WithDomain { val fs = TestFunctionalitySettings.Enabled.copy(preActivatedFeatures = Map(BlockchainFeatures.DataTransaction.id -> 0)) @@ -41,34 +43,38 @@ class DataTransactionDiffTest extends PropSpec with WithState { } val (genesisTx, items, txs) = setup - val sender = txs.head.sender - val genesis = block(Seq(genesisTx)) - val blocks = txs.map(tx => block(Seq(tx))) + val sender = txs.head.sender.toAddress val item1 = items.head - assertDiffAndState(Seq(genesis), blocks(0), fs) { - case (totalDiff, state) => - assertBalanceInvariant(totalDiff) - state.balance(sender.toAddress) shouldBe (ENOUGH_AMT - txs(0).fee.value) - state.accountData(sender.toAddress, item1.key) shouldBe Some(item1) + withDomain(RideV3) { d => + d.appendBlock(genesisTx) + d.appendBlock(txs(0)) + d.liquidSnapshot.balances((sender, Waves)) shouldBe (ENOUGH_AMT - txs(0).fee.value) + d.liquidSnapshot.accountData(sender)(item1.key) shouldBe item1 + val carryFee = -txs(0).fee.value * 3 / 5 + assertBalanceInvariant(d.liquidSnapshot, d.rocksDBWriter, carryFee) } val item2 = items(1) - assertDiffAndState(Seq(genesis, blocks(0)), blocks(1), fs) { - case (totalDiff, state) => - assertBalanceInvariant(totalDiff) - state.balance(sender.toAddress) shouldBe (ENOUGH_AMT - txs.take(2).map(_.fee.value).sum) - state.accountData(sender.toAddress, item1.key) shouldBe Some(item1) - state.accountData(sender.toAddress, item2.key) shouldBe Some(item2) + withDomain(RideV3) { d => + d.appendBlock(genesisTx) + d.appendBlock(txs(0), txs(1)) + d.liquidSnapshot.balances((sender, Waves)) shouldBe (ENOUGH_AMT - txs.take(2).map(_.fee.value).sum) + d.liquidSnapshot.accountData(sender)(item1.key) shouldBe item1 + d.liquidSnapshot.accountData(sender)(item2.key) shouldBe item2 + val carryFee = -(txs(0).fee.value + txs(1).fee.value) * 3 / 5 + assertBalanceInvariant(d.liquidSnapshot, d.rocksDBWriter, carryFee) } val item3 = items(2) - assertDiffAndState(Seq(genesis, blocks(0), blocks(1)), blocks(2), fs) { - case (totalDiff, state) => - assertBalanceInvariant(totalDiff) - state.balance(sender.toAddress) shouldBe (ENOUGH_AMT - txs.map(_.fee.value).sum) - state.accountData(sender.toAddress, item1.key) shouldBe Some(item3) - state.accountData(sender.toAddress, item2.key) shouldBe Some(item2) + withDomain(RideV3) { d => + d.appendBlock(genesisTx) + d.appendBlock(txs(0), txs(1), txs(2)) + d.liquidSnapshot.balances((sender, Waves)) shouldBe (ENOUGH_AMT - txs.map(_.fee.value).sum) + d.liquidSnapshot.accountData(sender)(item1.key) shouldBe item3 + d.liquidSnapshot.accountData(sender)(item2.key) shouldBe item2 + val carryFee = -(txs(0).fee.value + txs(1).fee.value + txs(2).fee.value) * 3 / 5 + assertBalanceInvariant(d.liquidSnapshot, d.rocksDBWriter, carryFee) } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/EthereumTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/EthereumTransactionDiffTest.scala index fb9831b246..1c403c22e2 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/EthereumTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/EthereumTransactionDiffTest.scala @@ -4,25 +4,24 @@ import com.wavesplatform.TestValues import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.crypto.EthereumKeyLength -import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.db.WithDomain +import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.lang.directives.values.V6 +import com.wavesplatform.lang.v1.ContractLimits.MaxInvokeScriptSizeInBytes import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.settings.RewardsVotingSettings -import com.wavesplatform.state.{Diff, Portfolio} +import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationError import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.* import com.wavesplatform.transaction.Asset.Waves +import com.wavesplatform.transaction.EthTxGenerator.Arg import com.wavesplatform.transaction.EthereumTransaction.Transfer import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment -import com.wavesplatform.transaction.{Asset, EthTxGenerator, EthereumTransaction, TxHelpers} import com.wavesplatform.transaction.utils.EthConverters.* -import EthTxGenerator.Arg -import com.wavesplatform.lang.v1.ContractLimits.MaxInvokeScriptSizeInBytes -import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationError +import com.wavesplatform.transaction.{Asset, EthTxGenerator, EthereumTransaction, TxHelpers} import com.wavesplatform.utils.{DiffMatchers, EthEncoding, JsonMatchers} import org.web3j.crypto.{Bip32ECKeyPair, RawTransaction} import play.api.libs.json.Json @@ -93,29 +92,28 @@ class EthereumTransactionDiffTest extends FlatSpec with WithDomain with DiffMatc val recipient = TxHelpers.address(2) val issuer = TxHelpers.signer(3) - val fee = TestValues.fee - val feeDiff = Diff(portfolios = Map(senderKp.toWavesAddress -> Portfolio.waves(fee))) - - withDomain(DomainPresets.RideV6.copy(rewardsSettings = RewardsVotingSettings(None)), Seq(AddrWithBalance(senderKp.toWavesAddress))) { d => - val wavesTransfer = EthTxGenerator.generateEthTransfer(senderKp, recipient, 1.waves, Waves, fee) - assertBalanceInvariant(d.createDiff(wavesTransfer).combineF(feeDiff).explicitGet()) + val fee = TestValues.fee + withDomain(RideV6.copy(rewardsSettings = RewardsVotingSettings(None)), Seq(AddrWithBalance(senderKp.toWavesAddress))) { d => + val wavesTransfer = EthTxGenerator.generateEthTransfer(senderKp, recipient, 1.waves, Waves, fee) val transferPayload = wavesTransfer.payload.asInstanceOf[Transfer] d.appendAndAssertSucceed(wavesTransfer) + val rewardAndFee = 6.waves - wavesTransfer.fee * 3 / 5 + assertBalanceInvariant(d.liquidSnapshot, d.rocksDBWriter, rewardAndFee) d.blockchain.balance(recipient) shouldBe transferPayload.amount d.blockchain.balance(senderKp.toWavesAddress) shouldBe ENOUGH_AMT - transferPayload.amount - fee } - withDomain(DomainPresets.RideV6, Seq(AddrWithBalance(senderKp.toWavesAddress), AddrWithBalance(issuer.toAddress))) { d => + withDomain(RideV6, Seq(AddrWithBalance(senderKp.toWavesAddress), AddrWithBalance(issuer.toAddress))) { d => val issue = TxHelpers.issue(issuer) val nativeTransfer = TxHelpers.transfer(issuer, senderKp.toWavesAddress, issue.quantity.value, issue.asset) val assetTransfer = EthTxGenerator.generateEthTransfer(senderKp, recipient, issue.quantity.value, issue.asset, fee) d.appendBlock(issue, nativeTransfer) - assertBalanceInvariant(d.createDiff(assetTransfer).combineF(feeDiff).explicitGet()) - d.appendAndAssertSucceed(assetTransfer) + val rewardAndFee = 6.waves + (issue.fee.value + nativeTransfer.fee.value - assetTransfer.fee) * 3 / 5 + assertBalanceInvariant(d.liquidSnapshot, d.rocksDBWriter, rewardAndFee) d.blockchain.balance(recipient) shouldBe 0L d.blockchain.balance(recipient, issue.asset) shouldBe issue.quantity.value d.blockchain.balance(senderKp.toWavesAddress) shouldBe ENOUGH_AMT - assetTransfer.fee @@ -306,23 +304,23 @@ class EthereumTransactionDiffTest extends FlatSpec with WithDomain with DiffMatc Seq(Payment(321, issue.asset)) ) - val diff = d.transactionDiffer(invoke).resultE.explicitGet() - diff should containAppliedTx(invoke.id()) - Json.toJson(diff.scriptResults.values.head) should matchJson("""{ - | "data" : [ ], - | "transfers" : [ { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : null, - | "amount" : 123 - | } ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - |}""".stripMargin) + val snapshot = d.transactionDiffer(invoke).resultE.explicitGet() + snapshot should containAppliedTx(invoke.id()) + Json.toJson(snapshot.scriptResults.values.head) should matchJson("""{ + | "data" : [ ], + | "transfers" : [ { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : null, + | "amount" : 123 + | } ], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + |}""".stripMargin) } } @@ -395,23 +393,23 @@ class EthereumTransactionDiffTest extends FlatSpec with WithDomain with DiffMatc Seq(), Seq(Payment(321, issue.asset)) ) - val diff = d.transactionDiffer(invoke).resultE.explicitGet() - diff should containAppliedTx(invoke.id()) - Json.toJson(diff.scriptResults.values.head) should matchJson("""{ - | "data" : [ ], - | "transfers" : [ { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : null, - | "amount" : 123 - | } ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - |}""".stripMargin) + val snapshot = d.transactionDiffer(invoke).resultE.explicitGet() + snapshot should containAppliedTx(invoke.id()) + Json.toJson(snapshot.scriptResults.values.head) should matchJson("""{ + | "data" : [ ], + | "transfers" : [ { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : null, + | "amount" : 123 + | } ], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + |}""".stripMargin) } } @@ -438,24 +436,27 @@ class EthereumTransactionDiffTest extends FlatSpec with WithDomain with DiffMatc d.appendBlock(setScript) - val invoke = EthTxGenerator.generateEthInvoke(invoker, dApp.toAddress, "deposit", Seq(), Seq()) - val diff = d.transactionDiffer(invoke).resultE.explicitGet() - diff should containAppliedTx(invoke.id()) - Json.toJson(diff.scriptResults.values.head) should matchJson("""{ - | "data" : [ ], - | "transfers" : [ { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : null, - | "amount" : 123 - | } ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - |}""".stripMargin) + val invoke = EthTxGenerator.generateEthInvoke(invoker, dApp.toAddress, "deposit", Seq(), Seq()) + val snapshot = d.transactionDiffer(invoke).resultE.explicitGet() + snapshot should containAppliedTx(invoke.id()) + Json.toJson(snapshot.scriptResults.values.head) should matchJson( + """ { + | "data" : [ ], + | "transfers" : [ { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : null, + | "amount" : 123 + | } ], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + | } + """.stripMargin + ) } } @@ -526,23 +527,23 @@ class EthereumTransactionDiffTest extends FlatSpec with WithDomain with DiffMatc Seq(), Seq(Payment(321, issue.asset)) ) - val diff = d.transactionDiffer(invoke).resultE.explicitGet() - diff should containAppliedTx(invoke.id()) - Json.toJson(diff.scriptResults.values.head) should matchJson("""{ - | "data" : [ ], - | "transfers" : [ { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : null, - | "amount" : 123 - | } ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - |}""".stripMargin) + val snapshot = d.transactionDiffer(invoke).resultE.explicitGet() + snapshot should containAppliedTx(invoke.id()) + Json.toJson(snapshot.scriptResults.values.head) should matchJson("""{ + | "data" : [ ], + | "transfers" : [ { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : null, + | "amount" : 123 + | } ], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + |}""".stripMargin) } } @@ -580,28 +581,28 @@ class EthereumTransactionDiffTest extends FlatSpec with WithDomain with DiffMatc Seq(), Nil ) - val diff = d.transactionDiffer(invoke).resultE.explicitGet() - diff should containAppliedTx(invoke.id()) - Json.toJson(diff.scriptResults.values.head) should matchJson(s"""{ - | "data" : [ ], - | "transfers" : [ { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : null, - | "amount" : 123 - | }, - | { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : "${issue.asset}", - | "amount" : 123 - | }], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - |}""".stripMargin) + val snapshot = d.transactionDiffer(invoke).resultE.explicitGet() + snapshot should containAppliedTx(invoke.id()) + Json.toJson(snapshot.scriptResults.values.head) should matchJson(s"""{ + | "data" : [ ], + | "transfers" : [ { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : null, + | "amount" : 123 + | }, + | { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : "${issue.asset}", + | "amount" : 123 + | }], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + |}""".stripMargin) } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala index 94bc8008ad..233baedc0c 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala @@ -27,12 +27,12 @@ import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.* import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} +import com.wavesplatform.transaction.TxHelpers.defaultAddress import com.wavesplatform.transaction.TxValidationError.{AccountBalanceError, GenericError} import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.transaction.assets.exchange.* import com.wavesplatform.transaction.assets.exchange.OrderPriceMode.{AssetDecimals, FixedDecimals, Default as DefaultPriceMode} import com.wavesplatform.transaction.smart.script.ScriptCompiler -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer import com.wavesplatform.transaction.transfer.{MassTransferTransaction, TransferTransaction} import com.wavesplatform.transaction.utils.EthConverters.* import com.wavesplatform.utils.{EthEncoding, EthHelpers} @@ -40,6 +40,7 @@ import com.wavesplatform.{TestValues, TestWallet, crypto} import org.scalatest.{EitherValues, Inside} import org.web3j.crypto.Bip32ECKeyPair +import scala.concurrent.duration.* import scala.util.{Random, Try} class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain with EitherValues with TestWallet with EthHelpers { @@ -328,23 +329,27 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w } preconditionsAndExchange.foreach { case (genesis, issue1, issue2, exchange) => - assertDiffAndState( - Seq(TestBlock.create(genesis :+ issue1 :+ issue2)), - TestBlock.create(Seq(exchange), Block.ProtoBlockVersion), - fsWithOrderFeature - ) { case (blockDiff, _) => - val totalPortfolioDiff: Portfolio = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - totalPortfolioDiff.balance shouldBe 0 - totalPortfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0 - totalPortfolioDiff.assets.values.toSet should (be(Set()) or be(Set(0))) - - blockDiff.portfolios(exchange.sender.toAddress).balance shouldBe exchange.buyMatcherFee + exchange.sellMatcherFee - exchange.fee.value + withDomain(ScriptsAndSponsorship) { d => + d.appendBlock(genesis*) + d.appendBlock(issue1, issue2) + d.appendBlock(exchange) + d.liquidSnapshot.balances.toSeq + .map { + case ((`defaultAddress`, Waves), amount) => + val carryFee = (issue1.fee.value + issue2.fee.value - exchange.fee.value) * 3 / 5 + Waves -> (amount - d.rocksDBWriter.balance(defaultAddress, Waves) - carryFee) + case ((address, asset), amount) => + asset -> (amount - d.rocksDBWriter.balance(address, asset)) + } + .groupMap(_._1)(_._2) + .foreach { case (_, balances) => balances.sum shouldBe 0 } + d.liquidSnapshot.balances((exchange.sender.toAddress, Waves)) shouldBe + d.rocksDBWriter.balance(exchange.sender.toAddress) + exchange.buyMatcherFee + exchange.sellMatcherFee - exchange.fee.value } } } property("Preserves assets invariant (matcher's fee in one of the assets of the pair or in Waves), stores match info, rewards matcher") { - val preconditionsAndExchange: Seq[(Seq[GenesisTransaction], IssueTransaction, IssueTransaction, ExchangeTransaction)] = { val buyer = TxHelpers.signer(1) val seller = TxHelpers.signer(2) @@ -401,30 +406,35 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w } preconditionsAndExchange.foreach { case (genesis, issue1, issue2, exchange) => - assertDiffAndState( - Seq(TestBlock.create(genesis :+ issue1 :+ issue2)), - TestBlock.create(Seq(exchange), Block.ProtoBlockVersion), - fsWithOrderFeature - ) { case (blockDiff, _) => - val totalPortfolioDiff: Portfolio = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - totalPortfolioDiff.balance shouldBe 0 - totalPortfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0 - totalPortfolioDiff.assets.values.toSet shouldBe Set(0L) - - val matcherPortfolio = - blockDiff.portfolios.view - .filterKeys(_.toString == exchange.sender.toAddress.toString) - .values - .fold(Portfolio())(_.combine(_).explicitGet()) - - val restoredMatcherPortfolio = + withDomain(ScriptsAndSponsorship) { d => + d.appendBlock(genesis*) + d.appendBlock(issue1, issue2) + d.appendBlock(exchange) + d.liquidSnapshot.balances.toSeq + .map { + case ((`defaultAddress`, Waves), amount) => + val carryFee = (issue1.fee.value + issue2.fee.value - exchange.fee.value) * 3 / 5 + Waves -> (amount - d.rocksDBWriter.balance(defaultAddress, Waves) - carryFee) + case ((address, asset), amount) => + asset -> (amount - d.rocksDBWriter.balance(address, asset)) + } + .groupMap(_._1)(_._2) + .foreach { case (_, balances) => balances.sum shouldBe 0 } + + val sender = exchange.sender.toAddress + val expectedMatcherPortfolio = Seq( ExchangeTransactionDiff.getOrderFeePortfolio(exchange.buyOrder, exchange.buyMatcherFee), ExchangeTransactionDiff.getOrderFeePortfolio(exchange.sellOrder, exchange.sellMatcherFee), wavesPortfolio(-exchange.fee.value) ).fold(Portfolio())(_.combine(_).explicitGet()) - matcherPortfolio shouldBe restoredMatcherPortfolio + d.liquidSnapshot.balances.collect { + case ((`sender`, Waves), balance) => + balance - d.rocksDBWriter.balance(sender) shouldBe expectedMatcherPortfolio.balance + case ((`sender`, asset: IssuedAsset), balance) => + balance - d.rocksDBWriter.balance(sender, asset) shouldBe expectedMatcherPortfolio.assets(asset) + } } } } @@ -475,7 +485,6 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w } property("Preserves assets invariant (matcher's fee in separately issued asset), stores match info, rewards matcher (Orders V3 are used)") { - val preconditionsAndExchange = { val buyer = TxHelpers.signer(1) val seller = TxHelpers.signer(2) @@ -509,35 +518,41 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w matcher, version = TxVersion.V2 ) - (genesis, issue1, issue2, issue3, issue4, exchange) } } preconditionsAndExchange.foreach { case (genesis, issue1, issue2, issue3, issue4, exchange) => - assertDiffAndState( - Seq(TestBlock.create(genesis :+ issue1 :+ issue2 :+ issue3 :+ issue4)), - TestBlock.create(Seq(exchange), Block.ProtoBlockVersion), - fsWithOrderFeature - ) { case (blockDiff, _) => - val totalPortfolioDiff: Portfolio = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - totalPortfolioDiff.balance shouldBe 0 - totalPortfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0 - totalPortfolioDiff.assets.values.toSet shouldBe Set(0L) - - val matcherPortfolio = - blockDiff.portfolios.view - .filterKeys(_.toString == exchange.sender.toAddress.toString) - .values - .fold(Portfolio())(_.combine(_).explicitGet()) - - val restoredMatcherPortfolio = + withDomain(ScriptsAndSponsorship) { d => + d.appendBlock(genesis*) + d.appendBlock(issue1, issue2, issue3, issue4) + d.appendAndAssertSucceed(exchange) + + val carryFee = (issue1.fee.value + issue2.fee.value + issue3.fee.value + issue4.fee.value - exchange.fee.value) * 3 / 5 + d.liquidSnapshot.balances.toSeq + .map { + case ((`defaultAddress`, Waves), amount) => + Waves -> (amount - d.rocksDBWriter.balance(defaultAddress, Waves) - carryFee) + case ((address, asset), amount) => + asset -> (amount - d.rocksDBWriter.balance(address, asset)) + } + .groupMap(_._1)(_._2) + .foreach { case (_, balanceDiff) => balanceDiff.sum shouldBe 0 } + + val sender = exchange.sender.toAddress + val expectedMatcherPortfolio = Seq( ExchangeTransactionDiff.getOrderFeePortfolio(exchange.buyOrder, exchange.buyMatcherFee), ExchangeTransactionDiff.getOrderFeePortfolio(exchange.sellOrder, exchange.sellMatcherFee), wavesPortfolio(-exchange.fee.value) ).fold(Portfolio())(_.combine(_).explicitGet()) - matcherPortfolio shouldBe restoredMatcherPortfolio + + d.liquidSnapshot.balances.collect { + case ((`sender`, Waves), balance) => + balance - d.rocksDBWriter.balance(sender) shouldBe expectedMatcherPortfolio.balance + case ((`sender`, asset: IssuedAsset), balance) => + balance - d.rocksDBWriter.balance(sender, asset) shouldBe expectedMatcherPortfolio.assets(asset) + } } } } @@ -636,26 +651,25 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w } property("Total matcher's fee (sum of matcher's fees in exchange transactions) is less than or equal to order's matcher fee") { - val preconditions = oneBuyFewSellsPreconditions( - totalBuyMatcherFeeBoundaries = - (bigBuyOrderMatcherFee: Long) => - (bigBuyOrderMatcherFee - 1000L, bigBuyOrderMatcherFee), // sum of buyMatcherFee in ex trs <= specified in bigBuyOrder + totalBuyMatcherFeeBoundaries = identity, sellersTotalAmount = identity ) val (genesises, issueTx1, issueTx2, massTransfer, exchanges, bigBuyOrder) = preconditions - assertDiffAndState( - Seq(TestBlock.create(genesises), TestBlock.create(Seq(issueTx1, issueTx2, massTransfer), Block.ProtoBlockVersion)), - TestBlock.create(exchanges, Block.ProtoBlockVersion), - fsOrderMassTransfer - ) { case (blockDiff, _) => - val totalPortfolioDiff: Portfolio = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - - totalPortfolioDiff.balance shouldBe 0 - totalPortfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0 - totalPortfolioDiff.assets.values.toSet shouldBe Set(0L) + withDomain(RideV3) { d => + d.appendBlock(genesises*) + d.appendBlock(issueTx1, issueTx2, massTransfer) + d.appendBlock(exchanges*) + val carryFee = (issueTx1.fee.value + issueTx2.fee.value + massTransfer.fee.value - exchanges.map(_.fee.value).sum) * 3 / 5 + d.liquidSnapshot.balances.toSeq + .map { + case ((`defaultAddress`, Waves), amount) => Waves -> (amount - d.rocksDBWriter.balance(defaultAddress, Waves) - carryFee) + case ((address, asset), amount) => asset -> (amount - d.rocksDBWriter.balance(address, asset)) + } + .groupMap(_._1)(_._2) + .foreach { case (_, balanceDiff) => balanceDiff.sum shouldBe 0 } val combinedPortfolio = exchanges.map(ex => getOrderFeePortfolio(bigBuyOrder, ex.buyMatcherFee)).fold(Portfolio())(_.combine(_).explicitGet()) @@ -673,8 +687,7 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w val preconditions = oneBuyFewSellsPreconditions( totalBuyMatcherFeeBoundaries = - (bigBuyOrderMatcherFee: Long) => - (bigBuyOrderMatcherFee + 1, bigBuyOrderMatcherFee + 100000L), // sum of buyMatcherFee in ex trs > specified in bigBuyOrder + (bigBuyOrderMatcherFee: Long) => bigBuyOrderMatcherFee + 100000L, // sum of buyMatcherFee in ex trs > specified in bigBuyOrder sellersTotalAmount = identity ) @@ -692,9 +705,8 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w val preconditions = oneBuyFewSellsPreconditions( - totalBuyMatcherFeeBoundaries = - (bigBuyOrderMatcherFee: Long) => (bigBuyOrderMatcherFee - 10000L, bigBuyOrderMatcherFee), // correct total buyMatcherFee in ex trs - sellersTotalAmount = (bigBuyOrderAmount: Long) => bigBuyOrderAmount + 10000L // sell orders overfill buy order + totalBuyMatcherFeeBoundaries = (bigBuyOrderMatcherFee: Long) => bigBuyOrderMatcherFee, // correct total buyMatcherFee in ex trs + sellersTotalAmount = (bigBuyOrderAmount: Long) => bigBuyOrderAmount + 10000L // sell orders overfill buy order ) val (genesises, issueTx1, issueTx2, massTransfer, exchanges, _) = preconditions @@ -762,17 +774,23 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w } preconditions.foreach { case (genesis, issue, exchange) => - assertDiffAndState( - Seq(TestBlock.create(genesis :+ issue)), - TestBlock.create(Seq(exchange), Block.ProtoBlockVersion), - fsWithOrderFeature - ) { case (blockDiff, _) => - val totalPortfolioDiff: Portfolio = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - totalPortfolioDiff.balance shouldBe 0 - totalPortfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0 - totalPortfolioDiff.assets.values.toSet shouldBe Set(0L) - - blockDiff.portfolios(exchange.sender.toAddress).balance shouldBe exchange.buyMatcherFee + exchange.sellMatcherFee - exchange.fee.value + withDomain(ScriptsAndSponsorship) { d => + d.appendBlock(genesis*) + d.appendBlock(issue) + d.appendBlock(exchange) + d.liquidSnapshot.balances.toSeq + .map { + case ((`defaultAddress`, Waves), amount) => + val carryFee = (issue.fee.value - exchange.fee.value) * 3 / 5 + Waves -> (amount - d.rocksDBWriter.balance(defaultAddress, Waves) - carryFee) + case ((address, asset), amount) => + asset -> (amount - d.rocksDBWriter.balance(address, asset)) + } + .groupMap(_._1)(_._2) + .foreach { case (_, balanceDiff) => balanceDiff.sum shouldBe 0 } + + d.liquidSnapshot.balances((exchange.sender.toAddress, Waves)) shouldBe + d.rocksDBWriter.balance(exchange.sender.toAddress, Waves) + exchange.buyMatcherFee + exchange.sellMatcherFee - exchange.fee.value } assertDiffEi( @@ -819,8 +837,8 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w TxHelpers.order(OrderType.BUY, issue.asset, Waves, amount = 1000000L, fee = MatcherFee, sender = buyer, matcher = matcher, version = Order.V1) val sell = TxHelpers.order(OrderType.SELL, issue.asset, Waves, fee = MatcherFee, sender = seller, matcher = matcher, version = Order.V1) val tx = createExTx(buy, sell, buy.price.value, matcher) - assertDiffAndState(Seq(TestBlock.create(genesis :+ issue)), TestBlock.create(Seq(tx)), fs) { case (blockDiff, state) => - blockDiff.portfolios(tx.sender.toAddress).balance shouldBe tx.buyMatcherFee + tx.sellMatcherFee - tx.fee.value + assertDiffAndState(Seq(TestBlock.create(genesis :+ issue)), TestBlock.create(Seq(tx)), fs) { case (snapshot, state) => + snapshot.balances((tx.sender.toAddress, Waves)) shouldBe tx.buyMatcherFee + tx.sellMatcherFee - tx.fee.value state.balance(tx.sender.toAddress) shouldBe 1L } } @@ -861,14 +879,14 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w version = Order.V1 ) val tx = createExTx(buy, sell, buy.price.value, matcher) - assertDiffEi(Seq(TestBlock.create(genesis :+ issue)), TestBlock.create(Seq(tx)), fsWithOrderFeature) { totalDiffEi => - inside(totalDiffEi) { case Left(TransactionValidationError(AccountBalanceError(errs), _)) => + assertDiffEi(Seq(TestBlock.create(genesis :+ issue)), TestBlock.create(Seq(tx)), fsWithOrderFeature) { snapshotEi => + inside(snapshotEi) { case Left(TransactionValidationError(AccountBalanceError(errs), _)) => errs should contain key seller.toAddress } } } - property("Diff for ExchangeTransaction works as expected and doesn't use rounding inside") { + property("StateSnapshot for ExchangeTransaction works as expected and doesn't use rounding inside") { val MatcherFee = 300000L val preconditions: (KeyPair, KeyPair, KeyPair, Seq[GenesisTransaction], IssueTransaction) = { @@ -917,14 +935,13 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w fee = buy.matcherFee.value, version = TxVersion.V1 ) - - assertDiffEi(Seq(TestBlock.create(genesis :+ issue)), TestBlock.create(Seq(tx))) { totalDiffEi => - inside(totalDiffEi) { case Right(diff) => - import diff.portfolios - portfolios(buyer.toAddress).balance shouldBe (-41L + 425532L) - portfolios(seller.toAddress).balance shouldBe (-300000L - 425532L) - portfolios(matcher.toAddress).balance shouldBe (+41L + 300000L - tx.fee.value) - } + withDomain(ScriptsAndSponsorship) { d => + d.appendBlock(genesis*) + d.appendBlock(issue) + d.appendBlock(tx) + d.liquidSnapshot.balances((buyer.toAddress, Waves)) shouldBe d.rocksDBWriter.balance(buyer.toAddress) - 41 + 425532 + d.liquidSnapshot.balances((seller.toAddress, Waves)) shouldBe d.rocksDBWriter.balance(seller.toAddress) - 300000 - 425532 + d.liquidSnapshot.balances((matcher.toAddress, Waves)) shouldBe d.rocksDBWriter.balance(seller.toAddress) + 41 + 300000 - tx.fee.value } } @@ -983,8 +1000,8 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w TestBlock.create(transfers), TestBlock.create(issueAndScripts) ) - assertDiffEi(preconBlocks, TestBlock.create(Seq(exchangeTx)), fsV2) { diff => - diff.isRight shouldBe true + assertDiffEi(preconBlocks, TestBlock.create(Seq(exchangeTx)), fsV2) { snapshot => + snapshot.isRight shouldBe true } } } @@ -1296,9 +1313,7 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w ) val test = TestBlock.create(Seq(exchangeTx)) if (o1.version == 2 && o2.version == 2) { - assertDiffEi(pretest, test, fs) { diff => - diff.explicitGet() - } + assertDiffEi(pretest, test, fs)(_.explicitGet()) } else { assertLeft(pretest, test, fs)("Can't process order with signature from scripted account") } @@ -1505,7 +1520,7 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w ) forAll(scenarios) { case (txWithV3, txWithV4AsV3, txWithV4, txWithV3V4AsV3, txWithV3V4, txWithV4AsV3V3, txWithV4V3) => - val portfolios = collection.mutable.ListBuffer[Map[Address, Portfolio]]() + val balances = collection.mutable.ListBuffer[Map[(Address, Asset), Long]]() Seq(txWithV3, txWithV4AsV3, txWithV4, txWithV3V4AsV3, txWithV3V4, txWithV4AsV3V3, txWithV4V3) .foreach { tx => @@ -1513,13 +1528,13 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w preconditions, TestBlock.create(Seq(tx), Block.ProtoBlockVersion), DomainPresets.RideV6.blockchainSettings.functionalitySettings - ) { case (blockDiff, _) => - portfolios += blockDiff.portfolios + ) { case (snapshot, _) => + balances += snapshot.balances } } // all portfolios built on the state and on the composite blockchain are equal - portfolios.distinct.size shouldBe 1 + balances.distinct.size shouldBe 1 } } @@ -1592,14 +1607,14 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w } scenario.foreach { case (preconditions, fixed, reversed) => - val portfolios = collection.mutable.ListBuffer[Map[Address, Portfolio]]() + val portfolios = collection.mutable.ListBuffer[Map[(Address, Asset), Long]]() - assertDiffAndState(preconditions, TestBlock.create(Seq(fixed)), fsWithBlockV5) { case (diff, _) => - portfolios += diff.portfolios + assertDiffAndState(preconditions, TestBlock.create(Seq(fixed)), fsWithBlockV5) { case (snapshot, _) => + portfolios += snapshot.balances } - assertDiffAndState(preconditions, TestBlock.create(Seq(reversed)), fsWithBlockV5) { case (diff, _) => - portfolios += diff.portfolios + assertDiffAndState(preconditions, TestBlock.create(Seq(reversed)), fsWithBlockV5) { case (snapshot, _) => + portfolios += snapshot.balances } portfolios.tail.forall(_ == portfolios.head) shouldBe true @@ -1669,33 +1684,35 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w assertDiffEi(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(exchange), Block.ProtoBlockVersion), fsWithOrderFeature) { ei => ei.left.value } - assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(exchange), Block.ProtoBlockVersion), fsWithBlockV5) { - case (diff, state) => - diff.scriptsComplexity shouldBe 1 // throw() - diff.portfolios(exchange.sender.toAddress).balance shouldBe -exchange.fee.value - diff.portfolios.get(exchange.buyOrder.sender.toAddress) shouldBe None - diff.portfolios.get(exchange.sellOrder.sender.toAddress) shouldBe None - - diff.scriptsComplexity shouldBe DiffsCommon - .countVerifierComplexity(Some(throwingScript), state, isAsset = true) - .explicitGet() - .get - ._2 - - buyerBalance.foreach { case (asset, balance) => - state.balance(exchange.buyOrder.sender.toAddress, asset) shouldBe balance - } - sellerBalance.foreach { case (asset, balance) => - state.balance(exchange.sellOrder.sender.toAddress, asset) shouldBe balance - } + withDomain(RideV4) { d => + d.appendBlock(genesisTxs*) + d.appendBlock(exchange) + d.liquidSnapshot.scriptsComplexity shouldBe 1 // throw() + d.liquidSnapshot + .balances((exchange.sender.toAddress, Waves)) shouldBe d.rocksDBWriter.balance(exchange.sender.toAddress, Waves) - exchange.fee.value + d.liquidSnapshot.balances.get((exchange.buyOrder.sender.toAddress, Waves)) shouldBe None + d.liquidSnapshot.balances.get((exchange.sellOrder.sender.toAddress, Waves)) shouldBe None + + d.liquidSnapshot.scriptsComplexity shouldBe DiffsCommon + .countVerifierComplexity(Some(throwingScript), d.blockchain, isAsset = true) + .explicitGet() + .get + ._2 - state.balance(exchange.sender.toAddress, Waves) shouldBe matcherBalance(Waves) - exchange.fee.value - matcherBalance.collect { case b @ (IssuedAsset(_), _) => b }.foreach { case (asset, balance) => - diff.portfolios(exchange.sender.toAddress).balanceOf(asset) shouldBe 0L - state.balance(exchange.sender.toAddress, asset) shouldBe balance - } + buyerBalance.foreach { case (asset, balance) => + d.balance(exchange.buyOrder.sender.toAddress, asset) shouldBe balance + } + sellerBalance.foreach { case (asset, balance) => + d.balance(exchange.sellOrder.sender.toAddress, asset) shouldBe balance + } + + d.balance(exchange.sender.toAddress, Waves) shouldBe matcherBalance(Waves) - exchange.fee.value + matcherBalance.collect { case b @ (IssuedAsset(_), _) => b }.foreach { case (asset, balance) => + d.liquidSnapshot.balances.get((exchange.sender.toAddress, asset)) shouldBe None + d.balance(exchange.sender.toAddress, asset) shouldBe balance + } - state.transactionInfo(exchange.id()).map(r => r._2 -> (r._1.status == Status.Succeeded)) shouldBe Some((exchange, false)) + d.blockchain.transactionInfo(exchange.id()).map(r => r._2 -> (r._1.status == Status.Succeeded)) shouldBe Some((exchange, false)) } } } @@ -1726,7 +1743,7 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w withDomain(RideV4) { d => d.appendBlock(Seq(amountAssetIssue, priceAssetIssue, order1FeeAssetIssue, order2FeeAssetIssue).distinct*) d.appendAndAssertFailed(exchange) - d.liquidDiff.scriptsComplexity shouldBe complexity + d.liquidSnapshot.scriptsComplexity shouldBe complexity } } @@ -1798,10 +1815,10 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w ) { d => d.appendBlock(Seq(tradeableAssetIssue, feeAssetIssue).distinct*) val newBlock = d.createBlock(2.toByte, Seq(exchange)) - val diff = BlockDiffer + val snapshot = BlockDiffer .fromBlock(d.blockchainUpdater, Some(d.lastBlock), newBlock, None, MiningConstraint.Unlimited, newBlock.header.generationSignature) .explicitGet() - diff.snapshot.scriptsComplexity shouldBe complexity + snapshot.snapshot.scriptsComplexity shouldBe complexity val feeUnits = FeeValidation.getMinFee(d.blockchainUpdater, exchange).explicitGet().minFeeInWaves / FeeValidation.FeeUnit if (complexity > 0) feeUnits shouldBe 7 @@ -1907,10 +1924,10 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w defaultSigner.publicKey, AssetPair(issue.asset, Waves), OrderType.BUY, - TxExchangeAmount.unsafeFrom(1), - TxOrderPrice.unsafeFrom(1), + TxExchangeAmount(1), + TxOrderPrice(1), System.currentTimeMillis(), - System.currentTimeMillis() + 10000, + System.currentTimeMillis() + 10.hours.toMillis, TxMatcherFee.unsafeFrom(0.003.waves) ) val signedBuyOrder = buyOrder.copy( @@ -1973,10 +1990,10 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w defaultSigner.publicKey, AssetPair(issue.asset, Waves), OrderType.BUY, - TxExchangeAmount.unsafeFrom(1), - TxOrderPrice.unsafeFrom(1), + TxExchangeAmount(1), + TxOrderPrice(1), System.currentTimeMillis(), - System.currentTimeMillis() + 10000, + System.currentTimeMillis() + 10.hours.toMillis, TxMatcherFee.unsafeFrom(0.003.waves) ) val signature = EthOrders.signOrder(buyOrder, signer) @@ -2184,7 +2201,7 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w totalMatcherFee: Long ): Seq[Order] = { val randomAmountsAndFees = - getSeqWithPredefinedSum(totalAmount, sellers.length) zip getSeqWithPredefinedSum(totalMatcherFee, sellers.length) + getSeqWithPredefinedSum(totalAmount, sellers.length) zip getSeqWithPredefinedSum(totalMatcherFee / 10, sellers.length).map(_ * 10) val sellers2AmountsAndFees = sellers zip randomAmountsAndFees @@ -2211,7 +2228,7 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w * function for manipulating of total sell orders amount */ def oneBuyFewSellsPreconditions( - totalBuyMatcherFeeBoundaries: Long => (Long, Long), + totalBuyMatcherFeeBoundaries: Long => Long, sellersTotalAmount: Long => Long ): (Seq[GenesisTransaction], IssueTransaction, IssueTransaction, MassTransferTransaction, Seq[ExchangeTransaction], Order) = { val matcher = TxHelpers.signer(1) @@ -2220,12 +2237,12 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w val buyer = TxHelpers.signer(sellOrdersCount + 2) val bigBuyOrderAmount = 3 * 100000L * 100000000L val price = 3 * 100000L - val bigBuyOrderMatcherFee = 100000L + val bigBuyOrderMatcherFee = 1000000L - val issue1 = TxHelpers.issue(buyer, Long.MaxValue - 1000L, name = "asset1") - val issue2 = TxHelpers.issue(buyer, Long.MaxValue - 1000L, name = "asset2") + val issue1 = TxHelpers.issue(buyer, Long.MaxValue - 1_000_000, name = "asset1") + val issue2 = TxHelpers.issue(buyer, Long.MaxValue - 1_000_000, name = "asset2") - val totalBuyMatcherFeeForExchangeTransactions = totalBuyMatcherFeeBoundaries(bigBuyOrderMatcherFee)._2 + val totalBuyMatcherFeeForExchangeTransactions = totalBuyMatcherFeeBoundaries(bigBuyOrderMatcherFee) val bigBuyOrder = TxHelpers.order( orderType = OrderType.BUY, @@ -2253,13 +2270,13 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w val massTransfer = TxHelpers.massTransfer( from = buyer, - to = sellers.map(seller => ParsedTransfer(seller.toAddress, TxNonNegativeAmount.unsafeFrom(issue2.quantity.value / sellOrdersCount))), + to = sellers.map(seller => seller.toAddress -> (issue2.quantity.value / sellOrdersCount)), asset = issue2.asset, - fee = 1000L, + fee = 1_000_000, version = TxVersion.V1 ) - val buyMatcherFees = getSeqWithPredefinedSum(totalBuyMatcherFeeForExchangeTransactions, sellOrdersCount) + val buyMatcherFees = getSeqWithPredefinedSum(totalBuyMatcherFeeForExchangeTransactions / 10, sellOrdersCount).map(_ * 10) val exchanges = (sellOrders zip buyMatcherFees).map { case (sellOrder, buyMatcherFee) => TxHelpers.exchange( diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/GenesisTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/GenesisTransactionDiffTest.scala index a8b01f33b1..081c29be12 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/GenesisTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/GenesisTransactionDiffTest.scala @@ -1,13 +1,13 @@ package com.wavesplatform.state.diffs -import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.db.WithDomain import com.wavesplatform.lagonaki.mocks.TestBlock -import com.wavesplatform.state.* import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.RideV6 +import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.TxHelpers import com.wavesplatform.transaction.TxHelpers.{defaultAddress, genesis} +import org.scalatest.exceptions.TestFailedException class GenesisTransactionDiffTest extends PropSpec with WithDomain { property("fails if height != 1") { @@ -17,17 +17,16 @@ class GenesisTransactionDiffTest extends PropSpec with WithDomain { } } - property("Diff establishes Waves invariant") { + property("StateSnapshot establishes Waves invariant") { val genesis = (1 to 10).map(idx => TxHelpers.genesis(TxHelpers.address(idx), 10000)) - assertDiffAndState(Seq.empty, TestBlock.create(genesis)) { (blockDiff, _) => - val totalPortfolio = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - totalPortfolio.balance shouldBe genesis.map(_.amount.value).sum - totalPortfolio.effectiveBalance(false).explicitGet() shouldBe genesis.map(_.amount.value).sum - totalPortfolio.assets shouldBe Map.empty - + blockDiff.balances.collect { + case ((_, Waves), amount) => amount + case ((_, asset), _) => throw new TestFailedException(s"unexpected $asset", 0) + }.sum shouldBe genesis.map(_.amount.value).sum + blockDiff.leaseBalances shouldBe empty genesis.foreach { gtx => - blockDiff.portfolios(gtx.recipient).balance shouldBe gtx.amount.value + blockDiff.balances((gtx.recipient, Waves)) shouldBe gtx.amount.value } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/LeaseTransactionsDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/LeaseTransactionsDiffTest.scala index d789f1853f..be960af737 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/LeaseTransactionsDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/LeaseTransactionsDiffTest.scala @@ -12,10 +12,13 @@ import com.wavesplatform.settings.TestFunctionalitySettings import com.wavesplatform.state.* import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.* +import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction} import com.wavesplatform.transaction.transfer.* import com.wavesplatform.transaction.{GenesisTransaction, TxHelpers, TxVersion} +import scala.collection.immutable.VectorMap + class LeaseTransactionsDiffTest extends PropSpec with WithDomain { private val allowMultipleLeaseCancelTransactionUntilTimestamp = Long.MaxValue / 2 @@ -25,33 +28,33 @@ class LeaseTransactionsDiffTest extends PropSpec with WithDomain { def total(l: LeaseBalance): Long = l.in - l.out property("can lease/cancel lease preserving waves invariant") { - - val sunnyDayLeaseLeaseCancel: Seq[(GenesisTransaction, LeaseTransaction, LeaseCancelTransaction)] = { - val master = TxHelpers.signer(1) - val recipient = TxHelpers.signer(2) - - val genesis = TxHelpers.genesis(master.toAddress) - for { - lease <- Seq(TxHelpers.lease(master, recipient.toAddress), TxHelpers.lease(master, recipient.toAddress, version = TxVersion.V1)) - leaseCancel <- Seq(TxHelpers.leaseCancel(lease.id(), master), TxHelpers.leaseCancel(lease.id(), master, version = TxVersion.V1)) - } yield (genesis, lease, leaseCancel) - } - - sunnyDayLeaseLeaseCancel.foreach { case (genesis, lease, leaseCancel) => - assertDiffAndState(Seq(TestBlock.create(Seq(genesis))), TestBlock.create(Seq(lease))) { case (totalDiff, _) => - val totalPortfolioDiff = totalDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - totalPortfolioDiff.balance shouldBe 0 - total(totalPortfolioDiff.lease) shouldBe 0 - totalPortfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0 - totalPortfolioDiff.assets.values.foreach(_ shouldBe 0) + val sender = TxHelpers.signer(1) + val recipient = TxHelpers.signer(2) + val miner = TestBlock.defaultSigner.toAddress + val genesis = TxHelpers.genesis(sender.toAddress) + for { + lease <- Seq(TxHelpers.lease(sender, recipient.toAddress), TxHelpers.lease(sender, recipient.toAddress, version = TxVersion.V1)) + leaseCancel <- Seq(TxHelpers.leaseCancel(lease.id(), sender), TxHelpers.leaseCancel(lease.id(), sender, version = TxVersion.V1)) + } { + assertDiffAndState(Seq(TestBlock.create(Seq(genesis))), TestBlock.create(Seq(lease))) { case (snapshot, _) => + snapshot.balances shouldBe VectorMap( + (sender.toAddress, Waves) -> (genesis.amount.value - lease.fee.value), + (miner, Waves) -> lease.fee.value + ) + snapshot.leaseBalances shouldBe Map( + sender.toAddress -> LeaseBalance(0, lease.amount.value), + recipient.toAddress -> LeaseBalance(lease.amount.value, 0) + ) } - - assertDiffAndState(Seq(TestBlock.create(Seq(genesis, lease))), TestBlock.create(Seq(leaseCancel))) { case (totalDiff, _) => - val totalPortfolioDiff = totalDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - totalPortfolioDiff.balance shouldBe 0 - total(totalPortfolioDiff.lease) shouldBe 0 - totalPortfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0 - totalPortfolioDiff.assets.values.foreach(_ shouldBe 0) + assertDiffAndState(Seq(TestBlock.create(Seq(genesis, lease))), TestBlock.create(Seq(leaseCancel))) { case (snapshot, _) => + snapshot.balances shouldBe VectorMap( + (miner, Waves) -> (lease.fee.value + leaseCancel.fee.value), + (sender.toAddress, Waves) -> (genesis.amount.value - lease.fee.value - leaseCancel.fee.value) + ) + snapshot.leaseBalances shouldBe Map( + sender.toAddress -> LeaseBalance.empty, + recipient.toAddress -> LeaseBalance.empty + ) } } } @@ -96,8 +99,8 @@ class LeaseTransactionsDiffTest extends PropSpec with WithDomain { property("cannot cancel lease twice after allowMultipleLeaseCancelTransactionUntilTimestamp") { disallowCancelTwice.foreach { case (preconditions, block) => - assertDiffEi(preconditions, block, settings) { totalDiffEi => - totalDiffEi should produce("Cannot cancel already cancelled lease") + assertDiffEi(preconditions, block, settings) { snapshotEi => + snapshotEi should produce("Cannot cancel already cancelled lease") } } } @@ -112,8 +115,8 @@ class LeaseTransactionsDiffTest extends PropSpec with WithDomain { property("can cancel lease twice before allowMultipleLeaseCancelTransactionUntilTimestamp") { allowCancelTwice.foreach { case (preconditions, block) => - assertDiffEi(preconditions, block, settings) { totalDiffEi => - totalDiffEi.explicitGet() + assertDiffEi(preconditions, block, settings) { snapshotEi => + snapshotEi.explicitGet() } } } @@ -133,8 +136,8 @@ class LeaseTransactionsDiffTest extends PropSpec with WithDomain { } setup.foreach { case (genesis, lease, leaseForward, ts) => - assertDiffEi(Seq(TestBlock.create(ts, Seq(genesis, lease))), TestBlock.create(ts, Seq(leaseForward)), settings) { totalDiffEi => - totalDiffEi should produce("Cannot lease more than own") + assertDiffEi(Seq(TestBlock.create(ts, Seq(genesis, lease))), TestBlock.create(ts, Seq(leaseForward)), settings) { snapshotEi => + snapshotEi should produce("Cannot lease more than own") } } } @@ -171,18 +174,23 @@ class LeaseTransactionsDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(blockTime, genesis :+ lease)), TestBlock.create(blockTime, Seq(unleaseOtherOrRecipient)), settings - ) { totalDiffEi => - totalDiffEi should produce("LeaseTransaction was leased by other sender") + ) { snapshotEi => + snapshotEi should produce("LeaseTransaction was leased by other sender") } } } property("can cancel lease of another sender and acquire leasing power before allowMultipleLeaseCancelTransactionUntilTimestamp") { cancelLeaseOfAnotherSender(unleaseByRecipient = false, repeatedCancelAllowed).foreach { case (genesis, lease, unleaseOther, blockTime) => - assertDiffAndState(Seq(TestBlock.create(genesis :+ lease)), TestBlock.create(blockTime, Seq(unleaseOther)), settings) { case (totalDiff, _) => - totalDiff.portfolios.get(lease.sender.toAddress) shouldBe None - total(totalDiff.portfolios(lease.recipient.asInstanceOf[Address]).lease) shouldBe -lease.amount.value - total(totalDiff.portfolios(unleaseOther.sender.toAddress).lease) shouldBe lease.amount.value + withDomain(ScriptsAndSponsorship.configure(_.copy(lastTimeBasedForkParameter = allowMultipleLeaseCancelTransactionUntilTimestamp))) { d => + d.appendBlock(genesis*) + d.appendBlock(lease) + d.appendBlock(TestBlock.create(blockTime, d.lastBlockId, Seq(unleaseOther)).block) + d.liquidSnapshot.balances.get((lease.sender.toAddress, Waves)) shouldBe None + val recipient = lease.recipient.asInstanceOf[Address] + val unleaser = unleaseOther.sender.toAddress + total(d.liquidSnapshot.leaseBalances(recipient)) shouldBe total(d.rocksDBWriter.leaseBalance(recipient)) - lease.amount.value + total(d.liquidSnapshot.leaseBalances(unleaser)) shouldBe total(d.rocksDBWriter.leaseBalance(unleaser)) + lease.amount.value } } } @@ -191,13 +199,13 @@ class LeaseTransactionsDiffTest extends PropSpec with WithDomain { "if recipient cancels lease, it doesn't change leasing component of mining power before allowMultipleLeaseCancelTransactionUntilTimestamp" ) { cancelLeaseOfAnotherSender(unleaseByRecipient = true, repeatedCancelAllowed).foreach { case (genesis, lease, unleaseRecipient, blockTime) => - assertDiffAndState( - Seq(TestBlock.create(blockTime, genesis :+ lease)), - TestBlock.create(blockTime, Seq(unleaseRecipient)), - settings - ) { case (totalDiff, _) => - totalDiff.portfolios.get(lease.sender.toAddress) shouldBe None - total(totalDiff.portfolios(unleaseRecipient.sender.toAddress).lease) shouldBe 0 + withDomain(ScriptsAndSponsorship.configure(_.copy(lastTimeBasedForkParameter = allowMultipleLeaseCancelTransactionUntilTimestamp))) { d => + d.appendBlock(genesis*) + d.appendBlock(lease) + d.appendBlock(TestBlock.create(blockTime, d.lastBlockId, Seq(unleaseRecipient)).block) + d.liquidSnapshot.balances.get((lease.sender.toAddress, Waves)) shouldBe None + val unleaser = unleaseRecipient.sender.toAddress + total(d.liquidSnapshot.leaseBalances(unleaser)) shouldBe total(d.rocksDBWriter.leaseBalance(unleaser)) } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/MassTransferTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/MassTransferTransactionDiffTest.scala index 5ad175a232..c94fc2bd62 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/MassTransferTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/MassTransferTransactionDiffTest.scala @@ -3,16 +3,17 @@ package com.wavesplatform.state.diffs import com.wavesplatform.account.{Address, Alias, KeyPair} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.db.WithState +import com.wavesplatform.db.WithDomain import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.lagonaki.mocks.TestBlock.create as block import com.wavesplatform.settings.{FunctionalitySettings, TestFunctionalitySettings} import com.wavesplatform.test.* +import com.wavesplatform.test.DomainPresets.ScriptsAndSponsorship import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer -import com.wavesplatform.transaction.{Asset, GenesisTransaction, TxHelpers, TxNonNegativeAmount, TxVersion} +import com.wavesplatform.transaction.{Asset, GenesisTransaction, TxHelpers, TxVersion} -class MassTransferTransactionDiffTest extends PropSpec with WithState { +class MassTransferTransactionDiffTest extends PropSpec with WithDomain { val fs: FunctionalitySettings = TestFunctionalitySettings.Enabled.copy(preActivatedFeatures = Map(BlockchainFeatures.MassTransfer.id -> 0)) @@ -30,47 +31,50 @@ class MassTransferTransactionDiffTest extends PropSpec with WithState { val setup = { val (genesis, master) = baseSetup - val transfers = (1 to transferCount).map(idx => ParsedTransfer(TxHelpers.address(idx + 1), TxNonNegativeAmount.unsafeFrom(100000L + idx))) + val transfers = (1 to transferCount).map(idx => TxHelpers.address(idx + 1) -> (100000L + idx)) val issue = TxHelpers.issue(master, ENOUGH_AMT, version = TxVersion.V1) Seq(Some(issue.id()), None).map { issueIdOpt => val maybeAsset = Asset.fromCompatId(issueIdOpt) - val transfer = TxHelpers.massTransfer(master, transfers, maybeAsset, version = TxVersion.V1) + val transfer = TxHelpers.massTransfer(master, transfers, maybeAsset, fee = 1.waves, version = TxVersion.V1) (genesis, issue, transfer) } } - setup.foreach { - case (genesis, issue, transfer) => - assertDiffAndState(Seq(block(Seq(genesis, issue))), block(Seq(transfer)), fs) { - case (totalDiff, newState) => - assertBalanceInvariant(totalDiff) - - val totalAmount = transfer.transfers.map(_.amount.value).sum - val fees = issue.fee.value + transfer.fee.value + setup.foreach { case (genesis, issue, transfer) => + withDomain(ScriptsAndSponsorship) { d => + d.appendBlock(genesis) + d.appendBlock(issue) + d.appendBlock(transfer) + + val carryFee = (issue.fee.value - transfer.fee.value) * 3 / 5 + assertBalanceInvariant(d.liquidSnapshot, d.rocksDBWriter, carryFee) + + val totalAmount = transfer.transfers.map(_.amount.value).sum + val fees = issue.fee.value + transfer.fee.value + transfer.assetId match { + case aid @ IssuedAsset(_) => + d.balance(transfer.sender.toAddress) shouldBe ENOUGH_AMT - fees + d.balance(transfer.sender.toAddress, aid) shouldBe ENOUGH_AMT - totalAmount + case Waves => + d.balance(transfer.sender.toAddress) shouldBe ENOUGH_AMT - fees - totalAmount + } + for (ParsedTransfer(recipient, amount) <- transfer.transfers) { + if (transfer.sender.toAddress != recipient) { transfer.assetId match { case aid @ IssuedAsset(_) => - newState.balance(transfer.sender.toAddress) shouldBe ENOUGH_AMT - fees - newState.balance(transfer.sender.toAddress, aid) shouldBe ENOUGH_AMT - totalAmount + d.balance(recipient.asInstanceOf[Address], aid) shouldBe amount.value case Waves => - newState.balance(transfer.sender.toAddress) shouldBe ENOUGH_AMT - fees - totalAmount - } - for (ParsedTransfer(recipient, amount) <- transfer.transfers) { - if (transfer.sender.toAddress != recipient) { - transfer.assetId match { - case aid @ IssuedAsset(_) => - newState.balance(recipient.asInstanceOf[Address], aid) shouldBe amount.value - case Waves => - newState.balance(recipient.asInstanceOf[Address]) shouldBe amount.value - } - } + d.balance(recipient.asInstanceOf[Address]) shouldBe amount.value } + } } + } } } - import com.wavesplatform.transaction.transfer.MassTransferTransaction.{MaxTransferCount => Max} + import com.wavesplatform.transaction.transfer.MassTransferTransaction.MaxTransferCount as Max Seq(0, 1, Max) foreach testDiff // test edge cases testDiff(5) } @@ -79,7 +83,7 @@ class MassTransferTransactionDiffTest extends PropSpec with WithState { val setup = { val (genesis, master) = baseSetup val recipient = Alias.create("alias").explicitGet() - val transfer = TxHelpers.massTransfer(master, Seq(ParsedTransfer(recipient, TxNonNegativeAmount.unsafeFrom(100000L))), version = TxVersion.V1) + val transfer = TxHelpers.massTransfer(master, Seq(recipient -> 100000L), version = TxVersion.V1) (genesis, transfer) } @@ -96,7 +100,7 @@ class MassTransferTransactionDiffTest extends PropSpec with WithState { val recipient = TxHelpers.address(2) val asset = IssuedAsset(ByteStr.fill(32)(1)) val transfer = - TxHelpers.massTransfer(master, Seq(ParsedTransfer(recipient, TxNonNegativeAmount.unsafeFrom(100000L))), asset, version = TxVersion.V1) + TxHelpers.massTransfer(master, Seq(recipient -> 100000L), asset, version = TxVersion.V1) (genesis, transfer) } @@ -110,7 +114,7 @@ class MassTransferTransactionDiffTest extends PropSpec with WithState { property("MassTransfer cannot overspend funds") { val setup = { val (genesis, master) = baseSetup - val recipients = Seq(2, 3).map(idx => ParsedTransfer(TxHelpers.address(idx), TxNonNegativeAmount.unsafeFrom(ENOUGH_AMT / 2 + 1))) + val recipients = Seq(2, 3).map(idx => TxHelpers.address(idx) -> (ENOUGH_AMT / 2 + 1)) val issue = TxHelpers.issue(master, ENOUGH_AMT, version = TxVersion.V1) Seq(Some(issue.id()), None).map { issueIdOpt => val maybeAsset = Asset.fromCompatId(issueIdOpt) @@ -120,11 +124,10 @@ class MassTransferTransactionDiffTest extends PropSpec with WithState { } } - setup.foreach { - case (genesis, transfer) => - assertDiffEi(Seq(block(Seq(genesis))), block(Seq(transfer)), fs) { blockDiffEi => - blockDiffEi should produce("Attempt to transfer unavailable funds") - } + setup.foreach { case (genesis, transfer) => + assertDiffEi(Seq(block(Seq(genesis))), block(Seq(transfer)), fs) { blockDiffEi => + blockDiffEi should produce("Attempt to transfer unavailable funds") + } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/OverflowTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/OverflowTest.scala index a0da315eba..3c2f74af96 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/OverflowTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/OverflowTest.scala @@ -10,8 +10,7 @@ import com.wavesplatform.test.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxHelpers.issue import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer -import com.wavesplatform.transaction.{TransactionType, TxHelpers, TxNonNegativeAmount} +import com.wavesplatform.transaction.{TransactionType, TxHelpers} class OverflowTest extends PropSpec with WithDomain { import DomainPresets.* @@ -43,7 +42,7 @@ class OverflowTest extends PropSpec with WithDomain { numPairs(massTransferFee).foreach { case (recipientBalance, transferAmount) => val balances = Seq(AddrWithBalance(sender.toAddress, Long.MaxValue), AddrWithBalance(recipient, recipientBalance)) withDomain(RideV5, balances) { d => - d.appendBlockE(TxHelpers.massTransfer(sender, Seq(ParsedTransfer(recipient, TxNonNegativeAmount.unsafeFrom(transferAmount))), fee = massTransferFee)) should produce( + d.appendBlockE(TxHelpers.massTransfer(sender, Seq(recipient -> transferAmount), fee = massTransferFee)) should produce( "Waves balance sum overflow" ) } @@ -55,8 +54,8 @@ class OverflowTest extends PropSpec with WithDomain { (the[Exception] thrownBy TxHelpers.massTransfer( sender, Seq( - ParsedTransfer(recipient, TxNonNegativeAmount.unsafeFrom(balance1)), - ParsedTransfer(recipient, TxNonNegativeAmount.unsafeFrom(balance2)) + recipient -> balance1, + recipient -> balance2 ) )).getMessage shouldBe "OverflowError" } @@ -115,35 +114,33 @@ class OverflowTest extends PropSpec with WithDomain { | ] """.stripMargin ) - numPairs(0).foreach { - case (amount1, amount2) => - withDomain(RideV5, AddrWithBalance.enoughBalances(sender, recipientKp)) { d => - d.appendBlock(TxHelpers.setScript(recipientKp, dApp(amount1, amount2))) - val invoke = TxHelpers.invoke(recipient, invoker = sender) - d.appendAndAssertFailed(invoke, "ScriptTransfer overflow") - } + numPairs(0).foreach { case (amount1, amount2) => + withDomain(RideV5, AddrWithBalance.enoughBalances(sender, recipientKp)) { d => + d.appendBlock(TxHelpers.setScript(recipientKp, dApp(amount1, amount2))) + val invoke = TxHelpers.invoke(recipient, invoker = sender) + d.appendAndAssertFailed(invoke, "ScriptTransfer overflow") + } } } property("invoke Reissue overflow") { - numPairs(0).foreach { - case (amount1, amount2) => - withDomain(RideV5, AddrWithBalance.enoughBalances(sender, recipientKp)) { d => - val issueTx = issue(recipientKp, amount = amount1) - val asset = IssuedAsset(issueTx.id()) - val dApp = TestCompiler(V5).compileContract( - s""" - | @Callable(i) - | func default() = - | [ - | Reissue(base58'$asset', $amount2, false) - | ] + numPairs(0).foreach { case (amount1, amount2) => + withDomain(RideV5, AddrWithBalance.enoughBalances(sender, recipientKp)) { d => + val issueTx = issue(recipientKp, amount = amount1) + val asset = IssuedAsset(issueTx.id()) + val dApp = TestCompiler(V5).compileContract( + s""" + | @Callable(i) + | func default() = + | [ + | Reissue(base58'$asset', $amount2, false) + | ] """.stripMargin - ) - val invoke = TxHelpers.invoke(recipient, invoker = sender) - d.appendBlock(issueTx, TxHelpers.setScript(recipientKp, dApp)) - d.appendAndAssertFailed(invoke, "Asset total value overflow") - } + ) + val invoke = TxHelpers.invoke(recipient, invoker = sender) + d.appendBlock(issueTx, TxHelpers.setScript(recipientKp, dApp)) + d.appendAndAssertFailed(invoke, "Asset total value overflow") + } } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/PaymentTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/PaymentTransactionDiffTest.scala index 5cf85cf7ea..135bc57fdd 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/PaymentTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/PaymentTransactionDiffTest.scala @@ -1,10 +1,8 @@ package com.wavesplatform.state.diffs -import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.db.WithState import com.wavesplatform.lagonaki.mocks.TestBlock import com.wavesplatform.settings.{FunctionalitySettings, TestFunctionalitySettings} -import com.wavesplatform.state.* import com.wavesplatform.test.* import com.wavesplatform.transaction.{GenesisTransaction, PaymentTransaction, TxHelpers} @@ -23,12 +21,11 @@ class PaymentTransactionDiffTest extends PropSpec with WithState { val settings: FunctionalitySettings = TestFunctionalitySettings.Enabled.copy(blockVersion3AfterHeight = 2) - property("Diff doesn't break invariant before block version 3") { - preconditionsAndPayments.foreach { case ((genesis, paymentV2, _)) => - assertDiffAndState(Seq(TestBlock.create(Seq(genesis))), TestBlock.create(Seq(paymentV2)), settings) { (blockDiff, newState) => - val totalPortfolioDiff: Portfolio = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet()) - totalPortfolioDiff.balance shouldBe 0 - totalPortfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0 + property("StateSnapshot doesn't break invariant before block version 3") { + preconditionsAndPayments.foreach { case (genesis, paymentV2, _) => + assertDiffAndState(Seq(TestBlock.create(Seq(genesis))), TestBlock.create(Seq(paymentV2)), settings) { (blockDiff, blockchain) => + blockDiff.balances.map { case ((address, asset), amount) => blockchain.balance(address, asset) - amount }.sum shouldBe 0 + blockDiff.leaseBalances shouldBe empty } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ScriptComplexityCountTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ScriptComplexityCountTest.scala index 82808fcb64..3551e6b70f 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ScriptComplexityCountTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ScriptComplexityCountTest.scala @@ -12,8 +12,7 @@ import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.RideV6 import com.wavesplatform.transaction.TxHelpers.* import com.wavesplatform.transaction.assets.exchange.* -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer -import com.wavesplatform.transaction.{GenesisTransaction, Transaction, TxNonNegativeAmount, TxVersion} +import com.wavesplatform.transaction.{GenesisTransaction, Transaction, TxVersion} import org.scalatest.Inside // noinspection NameBooleanParameters @@ -57,7 +56,7 @@ class ScriptComplexityCountTest extends PropSpec with WithDomain with Inside { val dataTx = data(master, Seq(BooleanDataEntry("q", true))) val tr1 = transfer(master, acc.toAddress, 10000000000L) val tr2 = transfer(master, acc.toAddress, additionalAmount, issueScr.asset) - val massTransfers = Seq(ParsedTransfer(acc.toAddress, TxNonNegativeAmount.unsafeFrom(1))) + val massTransfers = Seq(acc.toAddress -> 1L) val mt1 = massTransfer(master, massTransfers, version = TxVersion.V1, fee = 1.waves) val mt2 = massTransfer(master, massTransfers, issueScr.asset, fee = 1.waves) val l = lease(master, acc.toAddress, 1) diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/SetScriptTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/SetScriptTransactionDiffTest.scala index 20641ba452..bd8296a5d2 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/SetScriptTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/SetScriptTransactionDiffTest.scala @@ -435,7 +435,7 @@ class SetScriptTransactionDiffTest extends PropSpec with WithDomain { withDomain(domainSettingsWithFS(settings()), balances) { db => val tx = setScript() db.appendBlock(tx) - db.liquidDiff.errorMessage(tx.id()) shouldBe None + db.liquidSnapshot.errorMessage(tx.id()) shouldBe None } withDomain(domainSettingsWithFS(settings(checkNegative = true)), balances) { db => diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/DiffProduceError.scala b/node/src/test/scala/com/wavesplatform/state/diffs/SnapshotProduceError.scala similarity index 73% rename from node/src/test/scala/com/wavesplatform/state/diffs/DiffProduceError.scala rename to node/src/test/scala/com/wavesplatform/state/diffs/SnapshotProduceError.scala index 1412039e6e..aabc057770 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/DiffProduceError.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/SnapshotProduceError.scala @@ -1,13 +1,13 @@ package com.wavesplatform.state.diffs -import com.wavesplatform.state.Diff +import com.wavesplatform.state.StateSnapshot import org.scalatest.matchers.{MatchResult, Matcher} -class DiffProduceError(errorMessage: String, requireFailed: Boolean) extends Matcher[Either[_, _]] { +class SnapshotProduceError(errorMessage: String, requireFailed: Boolean) extends Matcher[Either[_, _]] { override def apply(ei: Either[_, _]): MatchResult = { ei match { - case r @ Right(diff: Diff) => - diff.scriptResults.values.find(_.error.exists(_.text.contains(errorMessage))) match { + case r @ Right(snapshot: StateSnapshot) => + snapshot.scriptResults.values.find(_.error.exists(_.text.contains(errorMessage))) match { case Some(_) => MatchResult(matches = true, "", "", Vector.empty) case None => MatchResult(matches = false, "expecting Left(...{0}...) but got {1}", "got expected error", IndexedSeq(errorMessage, r)) } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/SponsorshipDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/SponsorshipDiffTest.scala index 4d281bf026..85dc1cff51 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/SponsorshipDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/SponsorshipDiffTest.scala @@ -36,16 +36,16 @@ class SponsorshipDiffTest extends PropSpec with WithState { val cancel = TxHelpers.sponsor(issue.asset, None, master, fee = sponsorTxFee) val setupBlocks = Seq(block(Seq(genesis, issue))) - assertDiffAndState(setupBlocks, block(Seq(sponsor)), s) { case (diff, state) => - diff.sponsorship shouldBe Map(sponsor.asset -> SponsorshipValue(sponsor.minSponsoredAssetFee.get.value)) + assertDiffAndState(setupBlocks, block(Seq(sponsor)), s) { case (snapshot, state) => + snapshot.sponsorships shouldBe Map(sponsor.asset -> SponsorshipValue(sponsor.minSponsoredAssetFee.get.value)) state.assetDescription(sponsor.asset).map(_.sponsorship) shouldBe sponsor.minSponsoredAssetFee.map(_.value) } - assertDiffAndState(setupBlocks, block(Seq(sponsor, sponsor1)), s) { case (diff, state) => - diff.sponsorship shouldBe Map(sponsor.asset -> SponsorshipValue(sponsor1.minSponsoredAssetFee.get.value)) + assertDiffAndState(setupBlocks, block(Seq(sponsor, sponsor1)), s) { case (snapshot, state) => + snapshot.sponsorships shouldBe Map(sponsor.asset -> SponsorshipValue(sponsor1.minSponsoredAssetFee.get.value)) state.assetDescription(sponsor.asset).map(_.sponsorship) shouldBe sponsor1.minSponsoredAssetFee.map(_.value) } - assertDiffAndState(setupBlocks, block(Seq(sponsor, sponsor1, cancel)), s) { case (diff, state) => - diff.sponsorship shouldBe Map(sponsor.asset -> SponsorshipValue(0)) + assertDiffAndState(setupBlocks, block(Seq(sponsor, sponsor1, cancel)), s) { case (snapshot, state) => + snapshot.sponsorships shouldBe Map(sponsor.asset -> SponsorshipValue(0)) state.assetDescription(sponsor.asset).map(_.sponsorship) shouldBe Some(0) } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/TransactionValidationErrorPrintTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/TransactionValidationErrorPrintTest.scala index ef67f40a40..eb9982bd25 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/TransactionValidationErrorPrintTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/TransactionValidationErrorPrintTest.scala @@ -53,11 +53,11 @@ class TransactionValidationErrorPrintTest extends PropSpec with Inside with With val untypedScript = Parser.parseExpr(assetScript).get.value - val typedScript = ExprScript(V6, ExpressionCompiler(compilerContext(V6, Expression, isAssetScript = false), untypedScript).explicitGet()._1) + val typedScript = ExprScript(V6, ExpressionCompiler(compilerContext(V6, Expression, isAssetScript = false), V6, untypedScript).explicitGet()._1) .explicitGet() val preTypedScript = - ExprScript(V6, ExpressionCompiler(compilerContext(V6, Expression, isAssetScript = false), Parser.parseExpr("true").get.value).explicitGet()._1) + ExprScript(V6, ExpressionCompiler(compilerContext(V6, Expression, isAssetScript = false), V6, Parser.parseExpr("true").get.value).explicitGet()._1) .explicitGet() val seed = Address.fromString("3MydsP4UeQdGwBq7yDbMvf9MzfB2pxFoUKU").explicitGet() diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/TransferDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/TransferDiffTest.scala index 6273361c43..e8458dfb99 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/TransferDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/TransferDiffTest.scala @@ -2,22 +2,23 @@ package com.wavesplatform.state.diffs import com.wavesplatform.account.Address import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.db.WithState +import com.wavesplatform.db.WithDomain import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.lagonaki.mocks.TestBlock import com.wavesplatform.lang.script.v1.ExprScript import com.wavesplatform.lang.v1.compiler.Terms.CONST_BOOLEAN import com.wavesplatform.settings.TestFunctionalitySettings +import com.wavesplatform.state.diffs.FeeValidation.FeeUnit import com.wavesplatform.test.* +import com.wavesplatform.test.DomainPresets.ScriptsAndSponsorship import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.assets.* import com.wavesplatform.transaction.transfer.* import com.wavesplatform.transaction.{Asset, GenesisTransaction, TxHelpers, TxValidationError, TxVersion} -class TransferDiffTest extends PropSpec with WithState { - - val preconditionsAndTransfer: Seq[(GenesisTransaction, IssueTransaction, IssueTransaction, TransferTransaction)] = { +class TransferDiffTest extends PropSpec with WithDomain { + private val preconditionsAndTransfer = { val master = TxHelpers.signer(1) val recipient = TxHelpers.signer(2) @@ -30,25 +31,32 @@ class TransferDiffTest extends PropSpec with WithState { maybeAsset1 <- Seq(None, Some(issue1.id())).map(Asset.fromCompatId) maybeAsset2 <- Seq(None, Some(issue2.id())).map(Asset.fromCompatId) maybeFeeAsset <- Seq(maybeAsset1, maybeAsset2) + sponsor1 = TxHelpers.sponsor(IssuedAsset(issue1.id()), minSponsoredAssetFee = Some(FeeUnit), sender = master) + sponsor2 = TxHelpers.sponsor(IssuedAsset(issue2.id()), minSponsoredAssetFee = Some(FeeUnit), sender = master) transferV1 = TxHelpers.transfer(master, recipient.toAddress, asset = maybeAsset1, feeAsset = maybeFeeAsset, version = TxVersion.V1) transferV2 = TxHelpers.transfer(master, recipient.toAddress, asset = maybeAsset1, feeAsset = maybeFeeAsset) transfer <- Seq(transferV1, transferV2) - } yield (genesis, issue1, issue2, transfer) + } yield (genesis, sponsor1, sponsor2, issue1, issue2, transfer) } property("transfers assets to recipient preserving waves invariant") { - preconditionsAndTransfer.foreach { case (genesis, issue1, issue2, transfer) => - assertDiffAndState(Seq(TestBlock.create(Seq(genesis, issue1, issue2))), TestBlock.create(Seq(transfer))) { case (totalDiff, newState) => - assertBalanceInvariant(totalDiff) + preconditionsAndTransfer.foreach { case (genesis, sponsor1, sponsor2, issue1, issue2, transfer) => + withDomain(ScriptsAndSponsorship) { d => + d.appendBlock(genesis) + d.appendBlock(issue1, issue2, sponsor1, sponsor2) + d.appendBlock(transfer) + + val carryFee = (sponsor1.fee.value + sponsor2.fee.value + issue1.fee.value + issue2.fee.value - transfer.fee.value) * 3 / 5 + assertBalanceInvariant(d.liquidSnapshot, d.rocksDBWriter, carryFee) - val recipient: Address = transfer.recipient.asInstanceOf[Address] + val recipient = transfer.recipient.asInstanceOf[Address] if (transfer.sender.toAddress != recipient) { transfer.assetId match { case aid @ IssuedAsset(_) => - newState.balance(recipient) shouldBe 0 - newState.balance(recipient, aid) shouldBe transfer.amount.value + d.balance(recipient) shouldBe 0 + d.balance(recipient, aid) shouldBe transfer.amount.value case Waves => - newState.balance(recipient) shouldBe transfer.amount.value + d.balance(recipient) shouldBe transfer.amount.value } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/TransferTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/TransferTransactionDiffTest.scala index 7dcf114dae..1b7d9dd4b6 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/TransferTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/TransferTransactionDiffTest.scala @@ -1,12 +1,9 @@ package com.wavesplatform.state.diffs -import com.wavesplatform.TestValues -import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.db.WithDomain import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.lang.directives.values.V1 import com.wavesplatform.settings.RewardsVotingSettings -import com.wavesplatform.state.{Diff, Portfolio} import com.wavesplatform.test.{NumericExt, PropSpec} import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.{TxHelpers, TxValidationError, TxVersion} @@ -17,13 +14,12 @@ class TransferTransactionDiffTest extends PropSpec with WithDomain { val sender = TxHelpers.secondAddress val senderKp = TxHelpers.secondSigner val recipient = TxHelpers.address(2) - val feeDiff = Diff(portfolios = Map(sender -> Portfolio.waves(TestValues.fee))) withDomain(DomainPresets.mostRecent.copy(rewardsSettings = RewardsVotingSettings(None)), AddrWithBalance.enoughBalances(senderKp)) { d => val wavesTransfer = TxHelpers.transfer(senderKp, recipient) - assertBalanceInvariant(d.createDiff(wavesTransfer).combineF(feeDiff).explicitGet()) - d.appendAndAssertSucceed(wavesTransfer) + val rewardFee = 6.waves - wavesTransfer.fee.value * 3 / 5 + assertBalanceInvariant(d.liquidSnapshot, d.rocksDBWriter, rewardFee) d.blockchain.balance(recipient) shouldBe wavesTransfer.amount.value d.blockchain.balance(sender) shouldBe ENOUGH_AMT - wavesTransfer.amount.value - wavesTransfer.fee.value } @@ -31,9 +27,9 @@ class TransferTransactionDiffTest extends PropSpec with WithDomain { withDomain(DomainPresets.mostRecent, AddrWithBalance.enoughBalances(senderKp)) { d => val asset = d.helpers.issueAsset(senderKp) val assetTransfer = TxHelpers.transfer(senderKp, recipient, asset = asset, amount = 1000) - assertBalanceInvariant(d.createDiff(assetTransfer).combineF(feeDiff).explicitGet()) - d.appendAndAssertSucceed(assetTransfer) + val rewardAndFee = 6.waves + (1.waves - assetTransfer.fee.value) * 3 / 5 + assertBalanceInvariant(d.liquidSnapshot, d.rocksDBWriter, rewardAndFee) d.blockchain.balance(recipient) shouldBe 0L d.blockchain.balance(recipient, asset) shouldBe 1000L d.blockchain.balance(sender) shouldBe ENOUGH_AMT - assetTransfer.fee.value - 1.waves diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/BigIntInvokeTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/BigIntInvokeTest.scala index f0e177fe71..f30fc5b9a7 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/BigIntInvokeTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/BigIntInvokeTest.scala @@ -147,14 +147,14 @@ class BigIntInvokeTest extends PropSpec with Inside with WithState with DBCacheS d.appendBlock(preparingTxs*) d.appendBlock(invoke) - d.liquidDiff.errorMessage(invoke.id()) shouldBe None - inside(d.liquidDiff.scriptResults.toSeq) { case Seq((_, call1)) => + d.liquidSnapshot.errorMessage(invoke.id()) shouldBe None + inside(d.liquidSnapshot.scriptResults.toSeq) { case Seq((_, call1)) => inside(call1.invokes) { case Seq(call2) => call2.stateChanges.error shouldBe empty call2.stateChanges.invokes shouldBe empty } } - d.liquidDiff.accountData.head._2("key").value shouldBe 1 + d.liquidSnapshot.accountData.head._2("key").value shouldBe 1 } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/CallableV4DiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/CallableV4DiffTest.scala index 3bff19bce3..e4db887802 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/CallableV4DiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/CallableV4DiffTest.scala @@ -106,7 +106,7 @@ class CallableV4DiffTest extends PropSpec with WithDomain with EitherValues { } } - property("diff contains delete entries") { + property("snapshot contains delete entries") { val deleteEntryDApp = dApp( """ | [ @@ -131,8 +131,8 @@ class CallableV4DiffTest extends PropSpec with WithDomain with EitherValues { Seq(TestBlock.create(genesis :+ setScript)), TestBlock.create(Seq(invoke)), features - ) { case (diff, _) => - diff.accountData(master.toAddress) shouldBe + ) { case (snapshot, _) => + snapshot.accountData(master.toAddress) shouldBe Map( "key1" -> EmptyDataEntry("key1"), "key2" -> EmptyDataEntry("key2") @@ -389,9 +389,9 @@ class CallableV4DiffTest extends PropSpec with WithDomain with EitherValues { Seq(TestBlock.create(genesis :+ setScript)), TestBlock.create(Seq(invoke)), features - ) { case (diff, blockchain) => - val asset = diff.issuedAssets.head._1 - diff.sponsorship shouldBe Map(asset -> SponsorshipValue(minSponsoredAssetFee)) + ) { case (snapshot, blockchain) => + val asset = snapshot.assetStatics.head._1 + snapshot.sponsorships shouldBe Map(asset -> SponsorshipValue(minSponsoredAssetFee)) blockchain.assetDescription(asset).map(_.sponsorship) shouldBe Some(minSponsoredAssetFee) } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeActionsFeeTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeActionsFeeTest.scala index 2d4973fcba..2f9298528d 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeActionsFeeTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeActionsFeeTest.scala @@ -10,8 +10,9 @@ import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit} import com.wavesplatform.test.* import com.wavesplatform.transaction.Asset.IssuedAsset -import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment +import com.wavesplatform.transaction.TxHelpers.* import com.wavesplatform.transaction.smart.InvokeScriptTransaction +import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.{Transaction, TransactionType, TxHelpers} import org.scalatest.{EitherValues, Inside} @@ -26,10 +27,7 @@ class InvokeActionsFeeTest extends PropSpec with Inside with WithState with DBCa private val verifier: Script = TestCompiler(V4).compileExpression( - s""" {-# STDLIB_VERSION 4 #-} - | {-# SCRIPT_TYPE ASSET #-} - | {-# CONTENT_TYPE EXPRESSION #-} - | + s""" | !(sigVerify_32Kb(base58'', base58'', base58'') || | sigVerify_32Kb(base58'', base58'', base58'') || | sigVerify_32Kb(base58'', base58'', base58'')) @@ -38,13 +36,13 @@ class InvokeActionsFeeTest extends PropSpec with Inside with WithState with DBCa private def dApp(asset: IssuedAsset): Script = TestCompiler(V4).compileContract(s""" - | @Callable(i) - | func default() = - | [ - | ScriptTransfer(i.caller, 1, base58'$asset'), - | Burn(base58'$asset', 1), - | Reissue(base58'$asset', 1, false) - | ] + | @Callable(i) + | func default() = + | [ + | ScriptTransfer(i.caller, 1, base58'$asset'), + | Burn(base58'$asset', 1), + | Reissue(base58'$asset', 1, false) + | ] """.stripMargin) private val paymentPreconditions: (Seq[AddrWithBalance], List[Transaction], () => InvokeScriptTransaction, () => InvokeScriptTransaction) = { @@ -64,6 +62,33 @@ class InvokeActionsFeeTest extends PropSpec with Inside with WithState with DBCa (balances, List(issue, transfer1, transfer2, setVerifier, setDApp), invokeFromScripted, invokeFromNonScripted) } + property("insufficient action fees propagates failed transaction before RIDE V5 activation") { + withDomain(RideV4, AddrWithBalance.enoughBalances(secondSigner)) { d => + val issueTx = issue(script = Some(TestCompiler(V4).compileExpression("true"))) + val asset = IssuedAsset(issueTx.id()) + val dApp = TestCompiler(V4).compileContract( + s""" + | @Callable(i) + | func transfer() = [ScriptTransfer(i.caller, 1, base58'$asset')] + | + | @Callable(i) + | func reissue() = [Reissue(base58'$asset', 1, true)] + | + | @Callable(i) + | func burn() = [Burn(base58'$asset', 1)] + | + | @Callable(i) + | func issue() = [Issue("name", "", 1000, 4, true, unit, 0)] + """.stripMargin + ) + d.appendBlock(issueTx, setScript(secondSigner, dApp)) + d.appendAndAssertFailed(invoke(func = Some("transfer")), "with 1 total scripts invoked does not exceed minimal value of 900000 WAVES") + d.appendAndAssertFailed(invoke(func = Some("reissue")), "with 1 total scripts invoked does not exceed minimal value of 900000 WAVES") + d.appendAndAssertFailed(invoke(func = Some("burn")), "with 1 total scripts invoked does not exceed minimal value of 900000 WAVES") + d.appendAndAssertFailed(invoke(func = Some("issue")), "with 1 assets issued does not exceed minimal value of 100500000 WAVES") + } + } + property(s"fee for asset scripts is not required after activation ${BlockchainFeatures.SynchronousCalls}") { val (balances, preparingTxs, invokeFromScripted, invokeFromNonScripted) = paymentPreconditions withDomain(fsWithV5, balances) { d => 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 27df11b5e2..5faafa6d04 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.affected shouldBe Set(defaultAddress, secondAddress) + d.liquidSnapshot.transactions.head._2.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.affected shouldBe Set(defaultAddress, secondAddress) + d.liquidSnapshot.transactions.head._2.affected shouldBe Set(defaultAddress, secondAddress) } } } @@ -55,10 +55,10 @@ class InvokeAffectedAddressTest extends PropSpec with WithDomain { d.appendBlock(setScript(secondSigner, dApp(failed))) if (failed) { d.appendBlock(aliasTx, invokeTx) - d.liquidDiff.errorMessage(invokeTx.id()) shouldBe defined + d.liquidSnapshot.errorMessage(invokeTx.id()) shouldBe defined } else d.appendAndAssertSucceed(aliasTx, invokeTx) - d.liquidDiff.transaction(invokeTx.id()).get.affected shouldBe Set(defaultAddress, secondAddress) + d.liquidSnapshot.transactions(invokeTx.id()).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 c8ecf6b0bd..46774919df 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 @@ -8,11 +8,8 @@ import com.wavesplatform.db.{DBCacheSettings, WithDomain, WithState} import com.wavesplatform.lang.directives.values.{V4, V5} import com.wavesplatform.lang.script.ContractScript.ContractScriptImpl import com.wavesplatform.lang.v1.compiler.TestCompiler -import com.wavesplatform.state.InvokeScriptResult.ErrorMessage -import com.wavesplatform.state.TxMeta.Status -import com.wavesplatform.state.{Diff, InvokeScriptResult, NewTransactionInfo, Portfolio, StateSnapshot} import com.wavesplatform.test.* -import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} +import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.TxHelpers import com.wavesplatform.transaction.TxHelpers.{invoke, secondSigner, setScript} import org.scalatest.{EitherValues, Inside} @@ -26,7 +23,7 @@ class InvokeAssetChecksTest extends PropSpec with Inside with WithState with DBC private val lengthError = s"Transfer error: invalid asset ID '$invalidLengthAsset' length = 4 bytes, must be 32" private val nonExistentError = s"Transfer error: asset '$nonExistentAsset' is not found on the blockchain" - property("invoke asset checks") { + property("invoke transfer checks") { val dApp = TestCompiler(V4).compileContract( s""" |@Callable(i) @@ -49,71 +46,18 @@ class InvokeAssetChecksTest extends PropSpec with Inside with WithState with DBC activated <- Seq(true, false) func <- Seq("invalidLength", "unexisting") } { - { - val miner = TxHelpers.signer(0).toAddress - val invoker = TxHelpers.signer(1) - val master = TxHelpers.signer(2) - val balances = AddrWithBalance.enoughBalances(invoker, master) - val setScriptTx = TxHelpers.setScript(master, dApp) - val invoke = TxHelpers.invoke(master.toAddress, Some(func), invoker = invoker) - - val dAppAddress = master.toAddress - - def invokeInfo(succeeded: Boolean): Vector[NewTransactionInfo] = - Vector( - NewTransactionInfo( - invoke, - StateSnapshot.empty, - Set(invoke.senderAddress, dAppAddress), - if (succeeded) Status.Succeeded else Status.Failed, - 8 - ) - ) - - val expectedResult = - if (activated) { - val expectingMessage = - if (func == "invalidLength") - lengthError - else - nonExistentError - 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) - ), - scriptsComplexity = 8, - scriptResults = Map(invoke.id() -> InvokeScriptResult(error = Some(ErrorMessage(1, expectingMessage)))) - ) - } else { - val asset = if (func == "invalidLength") invalidLengthAsset else nonExistentAsset - Diff.withTransactions( - invokeInfo(true), - portfolios = Map( - invoke.senderAddress -> Portfolio(-invoke.fee.value), - miner -> Portfolio((setScriptTx.fee.value * 0.6 + invoke.fee.value * 0.4).toLong + 6.waves) - ), - scriptsComplexity = 8, - scriptResults = Map( - invoke.id() -> InvokeScriptResult( - transfers = Seq( - InvokeScriptResult.Payment(invoke.senderAddress, Waves, 0), - InvokeScriptResult.Payment(invoke.senderAddress, asset, 0) - ) - ) - ) - ) - } - - def noSnapshot(d: Diff) = - d.copy(transactions = d.transactions.map(_.copy(snapshot = StateSnapshot.empty))) - - withDomain(if (activated) RideV5 else RideV4, balances) { d => - d.appendBlock(setScriptTx) - d.appendBlock(invoke) - noSnapshot(d.liquidDiff) shouldBe expectedResult - } + val invoker = TxHelpers.signer(1) + val master = TxHelpers.signer(2) + val balances = AddrWithBalance.enoughBalances(invoker, master) + val setScriptTx = TxHelpers.setScript(master, dApp) + withDomain(if (activated) RideV5 else RideV4, balances) { d => + d.appendBlock(setScriptTx) + val invoke = TxHelpers.invoke(master.toAddress, Some(func), invoker = invoker) + if (activated) { + val expectingMessage = if (func == "invalidLength") lengthError else nonExistentError + d.appendAndAssertFailed(invoke, expectingMessage) + } else + d.appendAndAssertSucceed(invoke) } } } @@ -270,7 +214,7 @@ class InvokeAssetChecksTest extends PropSpec with Inside with WithState with DBC ) d.appendBlock(setScript(secondSigner, dApp)) d.appendAndAssertSucceed(invoke(fee = invokeFee(issues = 2))) - d.liquidDiff.issuedAssets.size shouldBe 2 + d.liquidSnapshot.assetStatics.size shouldBe 2 } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeFeeTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeFeeTest.scala index c593439d16..92972e2b41 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeFeeTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeFeeTest.scala @@ -5,14 +5,11 @@ import com.wavesplatform.db.WithDomain import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.v1.compiler.TestCompiler -import com.wavesplatform.state.Portfolio import com.wavesplatform.state.diffs.FeeValidation.FeeUnit import com.wavesplatform.test.* -import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxHelpers.* -import scala.collection.immutable.VectorMap - class InvokeFeeTest extends PropSpec with WithDomain { import DomainPresets.* @@ -73,9 +70,9 @@ class InvokeFeeTest extends PropSpec with WithDomain { val transferTx = transfer(issuer, invoker.toAddress, asset = asset) d.appendBlock(issueTx, sponsorTx, transferTx, setScript(dAppAcc, dApp)) d.appendAndAssertFailed(invoke(invoker = invoker, dApp = dAppAcc.toAddress, fee = invokeFee / coeff, feeAssetId = asset)) - d.liquidDiff.portfolios(invoker.toAddress) shouldBe Portfolio.build(asset, -invokeFee / coeff) - d.liquidDiff.portfolios(issuer.toAddress) shouldBe Portfolio(-invokeFee, assets = VectorMap(asset -> invokeFee / coeff)) - d.liquidDiff.portfolios.get(dAppAcc.toAddress) shouldBe None + d.liquidSnapshot.balances((invoker.toAddress, asset)) shouldBe d.rocksDBWriter.balance(invoker.toAddress, asset) - invokeFee / coeff + d.liquidSnapshot.balances((issuer.toAddress, asset)) shouldBe d.rocksDBWriter.balance(issuer.toAddress, asset) + invokeFee / coeff + d.liquidSnapshot.balances.get((dAppAcc.toAddress, Waves)) shouldBe None } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeReissueTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeReissueTest.scala index 6e9d4358bf..05af267008 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeReissueTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeReissueTest.scala @@ -61,7 +61,7 @@ class InvokeReissueTest extends PropSpec with WithDomain { ) d.appendBlock(setScript(secondSigner, dApp)) d.appendBlock(invoke(fee = invokeFee(issues = 1))) - val asset = d.liquidDiff.issuedAssets.head._1 + val asset = d.liquidSnapshot.assetStatics.head._1 d.appendBlock(reissue(asset, secondSigner, 234)) d.blockchain.assetDescription(asset).get.totalVolume shouldBe 1234 } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeScriptActionLimitsTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeScriptActionLimitsTest.scala index 6dd756e3aa..9334726c04 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeScriptActionLimitsTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeScriptActionLimitsTest.scala @@ -57,8 +57,8 @@ class InvokeScriptActionLimitsTest extends PropSpec with WithDomain with DBCache ) assertDiffAndState(Seq(TestBlock.create(preparingTxs1)), TestBlock.create(Seq(invoke1), Block.ProtoBlockVersion), features(version)) { - case (diff, bc) => - diff.scriptResults(invoke1.id()).error shouldBe None + case (snapshot, bc) => + snapshot.scriptResults(invoke1.id()).error shouldBe None bc.accountData(masterAddress, "key") shouldBe Some(IntegerDataEntry("key", 1)) bc.accountData(serviceAddress1, "bar") shouldBe Some(IntegerDataEntry("bar", 1)) } @@ -97,8 +97,8 @@ class InvokeScriptActionLimitsTest extends PropSpec with WithDomain with DBCache ) assertDiffAndState(Seq(TestBlock.create(preparingTxs1)), TestBlock.create(Seq(invoke1), Block.ProtoBlockVersion), features(version)) { - case (diff, bc) => - diff.scriptResults(invoke1.id()).error shouldBe None + case (snapshot, bc) => + snapshot.scriptResults(invoke1.id()).error shouldBe None bc.accountData(masterAddress, "key") shouldBe Some(IntegerDataEntry("key", 1)) bc.accountData(serviceAddress1, "bar") shouldBe Some(IntegerDataEntry("bar", 1)) } @@ -133,8 +133,8 @@ class InvokeScriptActionLimitsTest extends PropSpec with WithDomain with DBCache assetAndDataActionsServiceContract(V6, ContractLimits.MaxAssetScriptActionsAmountV6, ContractLimits.MaxWriteSetSize / 2) ) - assertDiffAndState(Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), features(V6)) { case (diff, bc) => - diff.scriptResults(invoke.id()).error shouldBe None + assertDiffAndState(Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), features(V6)) { case (snapshot, bc) => + snapshot.scriptResults(invoke.id()).error shouldBe None bc.accountData(masterAddress, "key") shouldBe Some(IntegerDataEntry("key", 1)) bc.accountData(serviceAddress, "bar") shouldBe Some(IntegerDataEntry("bar", 1)) } 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 c757652625..fed8004e3c 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 @@ -64,14 +64,14 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa .foreach(v => withDomain(settingsForRide(v))(assertion(v, _))) private def testDiffTraced(preconditions: Seq[BlockWithSigner], block: BlockWithSigner, from: StdLibVersion = V3, to: StdLibVersion)( - assertion: ((StdLibVersion, TracedResult[ValidationError, Diff])) => Unit + assertion: ((StdLibVersion, TracedResult[ValidationError, StateSnapshot])) => Unit ): Unit = allVersions .filter(v => v >= from && v <= to) .foreach(v => assertDiffEiTraced(preconditions, block, settingsForRide(v).blockchainSettings.functionalitySettings)(r => assertion((v, r)))) private def testDiff(preconditions: Seq[BlockWithSigner], block: BlockWithSigner, from: StdLibVersion = V3, to: StdLibVersion = lastVersion)( - assertion: Either[ValidationError, Diff] => Unit + assertion: Either[ValidationError, StateSnapshot] => Unit ): Unit = testDiffTraced(preconditions, block, from, to)(assertion.compose(_._2.resultE)) @@ -81,7 +81,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa from: StdLibVersion, to: StdLibVersion = lastVersion )( - assertion: (Diff, Blockchain) => Unit + assertion: (StateSnapshot, Blockchain) => Unit ): Unit = allVersions .filter(v => v >= from && v <= to) @@ -462,10 +462,10 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa val (_, setScript, ci) = preconditionsAndSetContract(dataContract(version)) d.appendBlock(setScript) d.appendBlock(ci) - d.liquidDiff.scriptsComplexity should be > 0L + d.liquidSnapshot.scriptsComplexity should be > 0L d.blockchain.accountData(dAppAddress, "sender").get.value shouldBe ByteStr(ci.sender.toAddress.bytes) d.blockchain.accountData(dAppAddress, "argument").get.value shouldBe ci.funcCallOpt.get.args.head.asInstanceOf[CONST_BYTESTR].bs - d.liquidDiff.transaction(ci.id()).get.affected.contains(setScript.sender.toAddress) shouldBe true + d.liquidSnapshot.transactions(ci.id()).affected.contains(setScript.sender.toAddress) shouldBe true } ) } @@ -496,10 +496,10 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa val (_, setScript, ci) = preconditionsAndSetContract(dAppWithTransfers(version = version)) d.appendBlock(setScript) d.appendBlock(ci) - d.liquidDiff.scriptsComplexity should be > 0L - d.liquidDiff.portfolios(thirdAddress).balance shouldBe amount - d.liquidDiff.portfolios(setScript.sender.toAddress).balance shouldBe -amount - d.liquidDiff.transaction(ci.id()) shouldBe defined + d.liquidSnapshot.scriptsComplexity should be > 0L + d.liquidSnapshot.balances((thirdAddress, Waves)) shouldBe amount + d.liquidSnapshot.balances((setScript.sender.toAddress, Waves)) shouldBe d.rocksDBWriter.balance(setScript.sender.toAddress, Waves) - amount + d.liquidSnapshot.transactions.get(ci.id()) shouldBe defined } ) } @@ -512,10 +512,10 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa val (_, setScript, ci) = preconditionsAndSetContract(dAppWithTransfers(version = version)) d.appendBlock(setScript) d.appendBlock(ci) - d.liquidDiff.scriptsComplexity should be > 0L - d.liquidDiff.portfolios(thirdAddress).balance shouldBe amount - d.liquidDiff.portfolios(setScript.sender.toAddress).balance shouldBe -amount - d.liquidDiff.transaction(ci.id()) shouldBe defined + d.liquidSnapshot.scriptsComplexity should be > 0L + d.liquidSnapshot.balances((thirdAddress, Waves)) shouldBe amount + d.liquidSnapshot.balances((setScript.sender.toAddress, Waves)) shouldBe d.rocksDBWriter.balance(setScript.sender.toAddress, Waves) - amount + d.liquidSnapshot.transactions.get(ci.id()) shouldBe defined } ) } @@ -531,8 +531,8 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa from = V4 ) { case (blockDiff, _) => blockDiff.scriptsComplexity should be > 0L - blockDiff.portfolios(thirdAddress) shouldBe Portfolio.waves(amount) - blockDiff.transaction(ci.id()) shouldBe defined + blockDiff.balances((thirdAddress, Waves)) shouldBe ENOUGH_AMT - createAlias.fee.value + amount + blockDiff.transactions.get(ci.id()) shouldBe defined } } @@ -590,7 +590,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa withDomain(settingsForRide(version), AddrWithBalance.enoughBalances(dApp, invoker)) { d => d.appendBlock(setScript, createAlias) d.appendAndAssertSucceed(ci) - d.liquidDiff.scriptsComplexity should be > 0L + d.liquidSnapshot.scriptsComplexity should be > 0L d.balance(thirdAddress, Waves) shouldBe amount d.appendBlockE(fakeCi) should produce("does not exist") } @@ -637,7 +637,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa d.appendBlock(genesis*) d.appendBlock(issue, setScript) d.appendBlock(ci) - inside(d.liquidDiff.scriptResults.toSeq) { case Seq((_, i: InvokeScriptResult)) => + inside(d.liquidSnapshot.scriptResults.toSeq) { case Seq((_, i: InvokeScriptResult)) => i.transfers.size shouldBe 1 } d.blockchain.balance(thirdAddress, Waves) shouldBe amount @@ -686,10 +686,10 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa ) withDomain(settingsForRide(version), AddrWithBalance.enoughBalances(dApp, invoker)) { d => d.appendBlock(asset, setScript) - val tracedDiff = d.transactionDiffer(ci) + val tracedSnapshot = d.transactionDiffer(ci) val message = if (version == V3) "TransactionNotAllowedByScript" else "Transaction is not allowed by script of the asset" - tracedDiff.resultE should produceRejectOrFailedDiff(message) - inside(tracedDiff.trace) { case List(_, AssetVerifierTrace(assetId, Some(tne: TransactionNotAllowedByScript), _)) => + tracedSnapshot.resultE should produceRejectOrFailedDiff(message) + inside(tracedSnapshot.trace) { case List(_, AssetVerifierTrace(assetId, Some(tne: TransactionNotAllowedByScript), _)) => assetId shouldBe asset.id() tne.isAssetScript shouldBe true } @@ -710,7 +710,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa ) d.appendBlock(issue, setScript) d.appendBlock(ci) - d.liquidDiff.scriptsComplexity should be > 0L + d.liquidSnapshot.scriptsComplexity should be > 0L d.balance(dAppAddress, asset) shouldBe (issue.quantity.value - amount) d.balance(thirdAddress, asset) shouldBe amount } @@ -741,9 +741,9 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa val (_, setScript, ci) = preconditionsAndSetContract(contract, fee = TestValues.invokeFee(2)) withDomain(settingsForRide(version), AddrWithBalance.enoughBalances(dApp, invoker)) { d => d.appendBlock(issue1, issue2, setScript) - val tracedDiff = d.transactionDiffer(ci) - tracedDiff.resultE should produceRejectOrFailedDiff("Transaction is not allowed by script") - inside(tracedDiff.trace) { + val tracedSnapshot = d.transactionDiffer(ci) + tracedSnapshot.resultE should produceRejectOrFailedDiff("Transaction is not allowed by script") + inside(tracedSnapshot.trace) { case List( InvokeScriptTrace(_, `dAppAddress`, functionCall, Right(scriptResult), _, _), AssetVerifierTrace(allowedAssetId, None, _), @@ -774,9 +774,9 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa ) withDomain(settingsForRide(version), AddrWithBalance.enoughBalances(dApp, invoker)) { d => d.appendBlock(transferringAsset, attachedAsset, setScript) - val tracedDiff = d.transactionDiffer(ci) - tracedDiff.resultE should produceRejectOrFailedDiff(s"Transaction is not allowed by script of the asset ${transferringAsset.id()}") - inside(tracedDiff.trace) { + val tracedSnapshot = d.transactionDiffer(ci) + tracedSnapshot.resultE should produceRejectOrFailedDiff(s"Transaction is not allowed by script of the asset ${transferringAsset.id()}") + inside(tracedSnapshot.trace) { case List( InvokeScriptTrace(_, _, _, Right(scriptResults), _, _), AssetVerifierTrace(transferringAssetId, Some(_), _) @@ -864,8 +864,8 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa val t = TxHelpers.transfer(dApp, invokerAddress, sponsorIssue.quantity.value / 2, sponsorAsset) d.appendBlock(sponsorIssue, t, sponsor, setScript) d.appendBlock(ci) - d.liquidDiff.scriptsComplexity should be > 0L - d.liquidDiff.errorMessage(ci.id()) shouldBe None + d.liquidSnapshot.scriptsComplexity should be > 0L + d.liquidSnapshot.errorMessage(ci.id()) shouldBe None d.balance(thirdAddress, Waves) shouldBe amount d.balance(ci.sender.toAddress, sponsorAsset) shouldBe (sponsorIssue.quantity.value / 2 - ci.fee.value) d.balance(dAppAddress, sponsorAsset) shouldBe (sponsorIssue.quantity.value - sponsorIssue.quantity.value / 2 + ci.fee.value) @@ -928,7 +928,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa d.appendBlockE(ci) should produceRejectOrFailedDiff(error) } else { d.appendBlock(ci) - d.liquidDiff.errorMessage(ci.id()).get.text shouldBe error + d.liquidSnapshot.errorMessage(ci.id()).get.text shouldBe error } } } @@ -943,7 +943,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa d.appendBlock(genesis*) d.appendBlock(setScript) d.appendBlock(ci) - d.liquidDiff.errorMessage(ci.id()) shouldBe None + d.liquidSnapshot.errorMessage(ci.id()) shouldBe None } } @@ -956,12 +956,12 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa d.appendBlock(genesis*) if (version == V3) { d.appendBlock(setScript, ci) - d.liquidDiff.errorMessage(ci.id()) shouldBe None + d.liquidSnapshot.errorMessage(ci.id()) shouldBe None } else if (version >= V6) { d.appendBlockE(setScript, ci) should produceRejectOrFailedDiff("Data entry key should not be empty") } else { d.appendBlock(setScript, ci) - d.liquidDiff.errorMessage(ci.id()).map(_.text) shouldBe Some("Data entry key should not be empty") + d.liquidSnapshot.errorMessage(ci.id()).map(_.text) shouldBe Some("Data entry key should not be empty") } } } @@ -1299,15 +1299,15 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa TestCompiler(V4).compileContract(script) } - property("duplicate issuing asset should produce diff error") { + property("duplicate issuing asset should produce snapshot error") { val genesis1Tx = TxHelpers.genesis(dAppAddress) val genesis2Tx = TxHelpers.genesis(invokerAddress) val setScriptTx = TxHelpers.setScript(dApp, doubleIssueContract) val invoke = TxHelpers.invoke(dAppAddress, Some("f"), fee = TestValues.invokeFee(issues = 2)) testDiff(Seq(TestBlock.create(Seq(genesis1Tx, genesis2Tx, setScriptTx))), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), from = V4) { inside(_) { - case Right(diff) => - diff.scriptResults(invoke.id()).error.get.text should include("is already issued") + case Right(snapshot) => + snapshot.scriptResults(invoke.id()).error.get.text should include("is already issued") case Left(TransactionValidationError(InvokeRejectError(error, _), _)) => error should include("is already issued") } } @@ -1326,7 +1326,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa d.appendAndAssertSucceed(ci) ci.feeAssetId shouldBe sponsorAsset ci.dApp shouldBe ci.sender.toAddress - d.liquidDiff.portfolios(ci.sender.toAddress).balanceOf(sponsorAsset) shouldBe 0L + d.liquidSnapshot.balances.get((ci.sender.toAddress, sponsorAsset)) shouldBe None } } } @@ -1384,8 +1384,7 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), from = V4, to = V5 - ) { case (diff, state) => - diff.portfolios(invoke.sender.toAddress).balanceOf(invoke.feeAssetId) + ) { case (_, state) => state.balance(invoke.sender.toAddress, invoke.feeAssetId) shouldBe invoke.feeAssetId.fold(g2Tx.amount.value)(_ => sponsorIssue.quantity.value ) - invoke.fee.value @@ -1461,11 +1460,11 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa .foreach { arg => val invoke = TxHelpers.invoke(dAppAddress, Some("sameComplexity"), args = List(CONST_STRING(arg).explicitGet())) testDiffAndState(Seq(TestBlock.create(Seq(gTx1, gTx2, ssTx, iTx))), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), from = V4) { - case (diff, _) => + case (snapshot, _) => if (arg == "ok") - diff.errorMessage(invoke.id()) shouldBe empty + snapshot.errorMessage(invoke.id()) shouldBe empty else - diff.errorMessage(invoke.id()) shouldBe defined + snapshot.errorMessage(invoke.id()) shouldBe defined } } @@ -1514,9 +1513,9 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa val genesisTxs = Seq(gTx1, gTx2) ++ invokerScriptTx ++ iTxs ++ tTxs ++ saTxs :+ ssTx testDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), from = V4, to = V5) { - case (diff, _) => - diff.errorMessage(invoke.id()) shouldBe defined - diff.scriptsComplexity should be > 0L + case (snapshot, _) => + snapshot.errorMessage(invoke.id()) shouldBe defined + snapshot.scriptsComplexity should be > 0L } testDiff(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), from = V6) { _ should produce("Transaction is not allowed by script of the asset") @@ -1551,8 +1550,8 @@ class InvokeScriptTransactionDiffTest extends PropSpec with WithDomain with DBCa val invoke = TxHelpers.invoke(dAppAddress, Some("foo"), payments = payments) testDiffAndState(Seq(TestBlock.create(Seq(gTx1, gTx2, alias, ssTx))), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), from = V5) { - case (diff, bc) => - diff.errorMessage(invoke.id()) shouldBe None + case (snapshot, bc) => + snapshot.errorMessage(invoke.id()) shouldBe None val hash = ByteStr(com.wavesplatform.lang.Global.blake2b256(script.bytes().arr)) bc.accountData(dAppAddress, "hash1").get.value shouldBe hash bc.accountData(dAppAddress, "hash2").get.value shouldBe hash diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeTransferBalanceErrorTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeTransferBalanceErrorTest.scala new file mode 100644 index 0000000000..359f5a8f37 --- /dev/null +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeTransferBalanceErrorTest.scala @@ -0,0 +1,59 @@ +package com.wavesplatform.state.diffs.ci + +import com.wavesplatform.db.WithDomain +import com.wavesplatform.db.WithState.AddrWithBalance +import com.wavesplatform.features.BlockchainFeatures.{LightNode, RideV6} +import com.wavesplatform.lang.directives.values.V5 +import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.test.DomainPresets.{RideV5, WavesSettingsOps} +import com.wavesplatform.test.{PropSpec, produce} +import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.transaction.TxHelpers.* + +class InvokeTransferBalanceErrorTest extends PropSpec with WithDomain { + private val assetFailScript = TestCompiler(V5).compileExpression( + s""" + | strict c = ${(1 to 5).map(_ => "sigVerify(base58'', base58'', base58'')").mkString(" || ")} + | if (true) then throw() else true + """.stripMargin + ) + + property("invoke is always rejected with a lack of funds without execution of ScriptTransfer script after RideV6, corrected after LightNode") { + val issueTx = issue(signer(10), script = Some(assetFailScript)) + val asset = IssuedAsset(issueTx.id()) + val dApp = TestCompiler(V5).compileContract( + s""" + | @Callable(i) + | func default() = { + | [ScriptTransfer(i.caller, 1, base58'$asset')] + | } + | + | @Callable(i) + | func complex() = { + | strict c = ${(1 to 6).map(_ => "sigVerify(base58'', base58'', base58'')").mkString(" || ")} + | [ScriptTransfer(i.caller, 1, base58'$asset')] + | } + """.stripMargin + ) + withDomain( + RideV5.setFeaturesHeight(RideV6 -> 6, LightNode -> 7), + AddrWithBalance.enoughBalances(secondSigner, signer(10)) + ) { d => + d.appendBlock(issueTx) + d.appendBlock(setScript(secondSigner, dApp)) + + // RideV5 — always failed + d.appendAndAssertFailed(invoke(), "Transaction is not allowed by script of the asset") + d.appendAndAssertFailed(invoke(func = Some("complex")), "Transaction is not allowed by script of the asset") + + // RideV6 — always rejected + d.appendBlockE(invoke()) should produce("negative asset balance") + d.appendBlockE(invoke(func = Some("complex"))) should produce("negative asset balance") + + // LightNode — rejected or failed + d.appendBlock() + d.appendBlockE(invoke()) should produce("negative asset balance") + d.appendAndAssertFailed(invoke(func = Some("complex")), "negative asset balance") + } + } +} diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeValidationTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeValidationTest.scala index 7efbb87d69..2fd04239c2 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeValidationTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeValidationTest.scala @@ -1,7 +1,6 @@ package com.wavesplatform.state.diffs.ci import com.wavesplatform.TestValues.invokeFee import com.wavesplatform.account.Alias -import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.db.WithDomain import com.wavesplatform.db.WithState.AddrWithBalance @@ -125,15 +124,4 @@ class InvokeValidationTest extends PropSpec with WithDomain { PBTransactions.toPBInvokeScriptData(txV2.dApp, txV2.funcCallOpt, txV2.payments).toByteArray.length shouldBe 5120 (the[Exception] thrownBy tooBigTxV2).getMessage shouldBe "GenericError(InvokeScriptTransaction bytes length = 5129 exceeds limit = 5120)" } - - property("unexisting payment asset") { - withDomain(RideV5) { d => - val asset = IssuedAsset(ByteStr.fromBytes(1, 2, 3)) - d.appendBlockE(invoke(defaultAddress, payments = Seq(Payment(1, asset)))) should produce( - "Attempt to transfer unavailable funds: " + - s"Transaction application leads to negative asset '$asset' balance to (at least) temporary negative state, " + - "current balance is 0, spends equals -1, result is -1" - ) - } - } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/LeaseActionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/LeaseActionDiffTest.scala index 3f952dad9a..9d5819074a 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/LeaseActionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/LeaseActionDiffTest.scala @@ -1,5 +1,6 @@ package com.wavesplatform.state.diffs.ci +import cats.Id import com.wavesplatform.account.{Address, Alias} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 @@ -14,11 +15,12 @@ import com.wavesplatform.lang.v1.ContractLimits import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.lang.v1.traits.domain.{Lease, Recipient} import com.wavesplatform.settings.{FunctionalitySettings, TestFunctionalitySettings} +import com.wavesplatform.state.LeaseBalance import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit} import com.wavesplatform.state.diffs.{ENOUGH_AMT, produceRejectOrFailedDiff} -import com.wavesplatform.state.{LeaseBalance, Portfolio} import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.* +import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.TxHelpers.{defaultAddress, invoke, lease, secondAddress, secondSigner, setScript} import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction} import com.wavesplatform.transaction.smart.InvokeScriptTransaction @@ -481,22 +483,20 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.portfolios(invoker) shouldBe Portfolio(-invoke.fee.value, LeaseBalance(leaseAmount, out = 0)) - diff.portfolios(dAppAcc) shouldBe Portfolio(0, LeaseBalance(in = 0, leaseAmount)) + ) { case (snapshot, _) => + snapshot.leaseBalances(invoker) shouldBe LeaseBalance(leaseAmount, out = 0) + snapshot.leaseBalances(dAppAcc) shouldBe LeaseBalance(in = 0, leaseAmount) } } property(s"Lease action cancelled by LeaseCancelTransaction") { val (preparingTxs, invoke, _, dAppAcc, invoker, _, leaseCancelTx) = leasePreconditions(cancelLeaseActionByTx = true) - assertDiffAndState( - Seq(TestBlock.create(preparingTxs)), - TestBlock.create(Seq(invoke, leaseCancelTx)), - v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()) shouldBe empty - diff.portfolios(invoker) shouldBe Portfolio(-invoke.fee.value) - diff.portfolios(dAppAcc) shouldBe Portfolio(-leaseCancelTx.fee.value) + withDomain(RideV5) { d => + d.appendBlock(preparingTxs*) + d.appendBlock(invoke, leaseCancelTx) + d.liquidSnapshot.errorMessage(invoke.id()) shouldBe empty + d.liquidSnapshot.balances((invoker, Waves)) shouldBe d.rocksDBWriter.balance(invoker) - invoke.fee.value + d.liquidSnapshot.balances((dAppAcc, Waves)) shouldBe d.rocksDBWriter.balance(dAppAcc) - leaseCancelTx.fee.value } } @@ -506,8 +506,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe "InvalidAddress(Wrong addressBytes length: expected: 26, actual: 0)" + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe "InvalidAddress(Wrong addressBytes length: expected: 26, actual: 0)" } } @@ -517,8 +517,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe "InvalidAddress(Wrong addressBytes length: expected: 26, actual: 10)" + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe "InvalidAddress(Wrong addressBytes length: expected: 26, actual: 10)" } } @@ -531,8 +531,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe "InvalidAddress(Bad address checksum)" + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe "InvalidAddress(Bad address checksum)" } } @@ -542,8 +542,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe "Alias 'alias:T:alias2' does not exists." + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe "Alias 'alias:T:alias2' does not exist." } } @@ -553,8 +553,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe s"Alias should contain only following characters: ${Alias.AliasAlphabet}" + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe s"Alias should contain only following characters: ${Alias.AliasAlphabet}" } } @@ -564,8 +564,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe "NonPositiveAmount(0,waves)" + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe "NonPositiveAmount(0,waves)" } } @@ -599,8 +599,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe s"Cannot lease more than own: Balance: $dAppBalance, already leased: 0" + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe s"Cannot lease more than own: Balance: $dAppBalance, already leased: 0" } } @@ -613,8 +613,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs.toList ::: leaseTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe s"Cannot lease more than own: Balance: ${dAppBalance - leaseFromDApp.fee.value}, already leased: ${leaseFromDApp.amount.value}" } } @@ -627,9 +627,9 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => + ) { case (snapshot, _) => val id = Lease.calculateId(Lease(recipient, amount, nonce = 0), invoke.id()) - diff.errorMessage(invoke.id()).get.text shouldBe s"Lease with id=$id is already in the state" + snapshot.errorMessage(invoke.id()).get.text shouldBe s"Lease with id=$id is already in the state" } } @@ -639,8 +639,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe "Cannot lease to self" + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe "Cannot lease to self" } } @@ -650,8 +650,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe "Cannot lease to self" + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe "Cannot lease to self" } } @@ -677,11 +677,11 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), features(version) - ) { case (diff, _) => - diff.errorMessage(invoke.id()) shouldBe empty - diff.portfolios(invoker) shouldBe Portfolio(-invoke.fee.value) - diff.portfolios(dAppAcc) shouldBe Portfolio(lease = LeaseBalance(in = 0, out = amount * limit)) - diff.portfolios(recipient) shouldBe Portfolio(lease = LeaseBalance(in = amount * limit, out = 0)) + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()) shouldBe empty + snapshot.balances((invoker, Waves)) shouldBe ENOUGH_AMT - invoke.fee.value + snapshot.leaseBalances(dAppAcc) shouldBe LeaseBalance(in = 0, out = amount * limit) + snapshot.leaseBalances(recipient) shouldBe LeaseBalance(in = amount * limit, out = 0) } } @@ -749,8 +749,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text should include("is already in the state") + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text should include("is already in the state") } } @@ -763,24 +763,25 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()) shouldBe empty - diff.portfolios(invoker) shouldBe Portfolio(-invoke.fee.value) - diff.portfolios(dAppAcc) shouldBe Portfolio(lease = LeaseBalance(in = 0, out = amount)) - diff.portfolios(recipient) shouldBe Portfolio(lease = LeaseBalance(in = amount, 0)) + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()) shouldBe empty + snapshot.balances((invoker, Waves)) shouldBe ENOUGH_AMT - invoke.fee.value + snapshot.leaseBalances(dAppAcc) shouldBe LeaseBalance(in = 0, out = amount) + snapshot.leaseBalances(recipient) shouldBe LeaseBalance(in = amount, 0) } } property(s"LeaseCancel action for lease performed via LeaseTransaction") { val (preparingTxs, invoke, _, dAppAcc, invoker, leaseTxs, _) = leasePreconditions(useLeaseCancelDApp = true) val leaseFromDApp = leaseTxs.head - assertDiffAndState( - Seq(TestBlock.create(preparingTxs ++ leaseTxs)), - TestBlock.create(Seq(invoke)), - v5Features - ) { case (diff, _) => - diff.portfolios(invoker) shouldBe Portfolio(-invoke.fee.value, LeaseBalance(in = -leaseFromDApp.amount.value, 0)) - diff.portfolios(dAppAcc) shouldBe Portfolio(0, LeaseBalance(0, out = -leaseFromDApp.amount.value)) + withDomain(RideV5) { d => + d.appendBlock(preparingTxs*) + d.appendBlock(leaseTxs*) + d.appendAndAssertSucceed(invoke) + d.liquidSnapshot + .leaseBalances(invoker) shouldBe d.rocksDBWriter.leaseBalance(invoker).combineF[Id](LeaseBalance(in = -leaseFromDApp.amount.value, 0)) + d.liquidSnapshot + .leaseBalances(dAppAcc) shouldBe d.rocksDBWriter.leaseBalance(dAppAcc).combineF[Id](LeaseBalance(0, out = -leaseFromDApp.amount.value)) } } @@ -791,8 +792,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe s"Lease with id=$leaseId not found" + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe s"Lease with id=$leaseId not found" } } @@ -803,8 +804,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe s"Lease id=$leaseId has invalid length = 1 byte(s) while expecting 32" + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe s"Lease id=$leaseId has invalid length = 1 byte(s) while expecting 32" } } @@ -815,8 +816,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs :+ leaseTx)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text should include("LeaseTransaction was leased by other sender") + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text should include("LeaseTransaction was leased by other sender") } } @@ -828,9 +829,9 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => + ) { case (snapshot, _) => val leaseId = Lease.calculateId(Lease(recipient, amount, nonce = 0), invoke.id()) - diff.errorMessage(invoke.id()).get.text shouldBe s"Duplicate LeaseCancel id(s): $leaseId" + snapshot.errorMessage(invoke.id()).get.text shouldBe s"Duplicate LeaseCancel id(s): $leaseId" } } @@ -840,8 +841,8 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs ++ leaseTxs ++ List(leaseCancelTx))), TestBlock.create(Seq(invoke)), v5Features - ) { case (diff, _) => - diff.errorMessage(invoke.id()).get.text shouldBe "Cannot cancel already cancelled lease" + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()).get.text shouldBe "Cannot cancel already cancelled lease" } } @@ -850,14 +851,18 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { val (preparingTxs, invoke, _, dAppAcc, invoker, ltx, _) = leasePreconditions(useLeaseCancelDApp = true, leaseCancelCount = ContractLimits.MaxCallableActionsAmountBeforeV6(version), version = version) val leaseTxs = ltx.init - assertDiffAndState( - Seq(TestBlock.create(preparingTxs ++ leaseTxs)), - TestBlock.create(Seq(invoke)), - features(version) - ) { case (diff, _) => - diff.errorMessage(invoke.id()) shouldBe empty - diff.portfolios(invoker) shouldBe Portfolio(-invoke.fee.value, LeaseBalance(in = -leaseTxs.map(_.amount.value).sum, out = 0)) - diff.portfolios(dAppAcc) shouldBe Portfolio(0, LeaseBalance(in = 0, out = -leaseTxs.map(_.amount.value).sum)) + withDomain(settingsForRide(version)) { d => + d.appendBlock(preparingTxs*) + d.appendBlock(leaseTxs*) + d.appendAndAssertSucceed(invoke) + d.liquidSnapshot.leaseBalances(invoker) shouldBe + d.rocksDBWriter + .leaseBalance(invoker) + .combineF[Id](LeaseBalance(in = -leaseTxs.map(_.amount.value).sum, out = 0)) + d.liquidSnapshot.leaseBalances(dAppAcc) shouldBe + d.rocksDBWriter + .leaseBalance(dAppAcc) + .combineF[Id](LeaseBalance(in = 0, out = -leaseTxs.map(_.amount.value).sum)) } } @@ -899,11 +904,12 @@ class LeaseActionDiffTest extends PropSpec with WithDomain { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), features(version) - ) { case (diff, _) => - diff.errorMessage(invoke.id()) shouldBe empty - diff.portfolios(invoker) shouldBe Portfolio(-invoke.fee.value) - diff.portfolios(dAppAcc) shouldBe Portfolio(-transfersCount, lease = LeaseBalance(in = 0, out = leaseAmount)) - diff.portfolios(recipient) shouldBe Portfolio(transfersCount, lease = LeaseBalance(in = leaseAmount, out = 0)) + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()) shouldBe empty + snapshot.balances((invoker, Waves)) shouldBe ENOUGH_AMT - invoke.fee.value + snapshot.leaseBalances.get(invoker) shouldBe None + snapshot.leaseBalances(dAppAcc) shouldBe LeaseBalance(in = 0, out = leaseAmount) + snapshot.leaseBalances(recipient) shouldBe LeaseBalance(in = leaseAmount, out = 0) } } 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 ff70c57978..020e5ca314 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 @@ -10,11 +10,11 @@ import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.v1.ContractLimits import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.settings.{Constants, TestFunctionalitySettings} -import com.wavesplatform.state.Diff +import com.wavesplatform.state.StateSnapshot import com.wavesplatform.state.TxMeta.Status import com.wavesplatform.state.diffs.* import com.wavesplatform.test.* -import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.smart.{InvokeScriptTransaction, SetScriptTransaction} @@ -34,19 +34,14 @@ class MultiPaymentInvokeDiffTest extends PropSpec with WithState { Seq(TestBlock.create(genesis ++ issues ++ Seq(setDApp, setVerifier))), TestBlock.create(Seq(ci)), features - ) { case (diff, blockchain) => - val assetBalance = issues - .map(_.id()) - .map(IssuedAsset(_)) - .map(asset => asset -> blockchain.balance(dAppAcc.toAddress, asset)) - .filter(_._2 != 0) - .toMap - - diff.portfolios(dAppAcc.toAddress).assets shouldBe assetBalance - diff.portfolios(dAppAcc.toAddress).balance shouldBe -wavesTransfer - diff.portfolios(invoker.toAddress).balance shouldBe wavesTransfer - fee + ) { case (snapshot, blockchain) => + issues.foreach { tx => + val asset = IssuedAsset(tx.id()) + blockchain.balance(dAppAcc.toAddress, asset) shouldBe snapshot.balances((dAppAcc.toAddress, asset)) + } + snapshot.balances((dAppAcc.toAddress, Waves)) shouldBe ENOUGH_AMT - setDApp.fee.value - wavesTransfer + snapshot.balances((invoker.toAddress, Waves)) shouldBe ENOUGH_AMT - setVerifier.fee.value - issues.map(_.fee.value).sum - fee + wavesTransfer } - } } @@ -61,15 +56,11 @@ class MultiPaymentInvokeDiffTest extends PropSpec with WithState { Seq(TestBlock.create(genesis ++ issues ++ Seq(setDApp, setVerifier))), TestBlock.create(Seq(ci)), features - ) { case (diff, blockchain) => - val assetBalance = issues - .map(_.id()) - .map(IssuedAsset(_)) - .map(asset => asset -> blockchain.balance(dAppAcc.toAddress, asset)) - .filter(_._2 != 0) - .toMap - - diff.portfolios(dAppAcc.toAddress).assets shouldBe assetBalance + ) { case (snapshot, blockchain) => + issues.foreach { tx => + val asset = IssuedAsset(tx.id()) + blockchain.balance(dAppAcc.toAddress, asset) shouldBe snapshot.balances((dAppAcc.toAddress, asset)) + } } } } @@ -91,7 +82,7 @@ class MultiPaymentInvokeDiffTest extends PropSpec with WithState { TestBlock.create(Seq(ci)), features )(_ should matchPattern { - case Right(diff: Diff) if diff.transactions.exists(_.status != Status.Succeeded) => + case Right(snapshot: StateSnapshot) if snapshot.transactions.exists(_._2.status != Status.Succeeded) => }) } } @@ -145,17 +136,13 @@ class MultiPaymentInvokeDiffTest extends PropSpec with WithState { Seq(TestBlock.create(genesis ++ issues ++ Seq(setDApp, setVerifier))), TestBlock.create(Seq(ci)), features - ) { case (diff, blockchain) => - val assetBalance = issues - .map(_.id()) - .map(IssuedAsset(_)) - .map(asset => asset -> blockchain.balance(dAppAcc.toAddress, asset)) - .filter(_._2 != 0) - .toMap - - diff.portfolios(dAppAcc.toAddress).assets shouldBe assetBalance - diff.portfolios(dAppAcc.toAddress).balance shouldBe -wavesTransfer - diff.portfolios(invoker.toAddress).balance shouldBe wavesTransfer - fee + ) { case (snapshot, blockchain) => + issues.foreach { tx => + val asset = IssuedAsset(tx.id()) + snapshot.balances((dAppAcc.toAddress, asset)) shouldBe blockchain.balance(dAppAcc.toAddress, asset) + } + snapshot.balances((dAppAcc.toAddress, Waves)) shouldBe ENOUGH_AMT - setDApp.fee.value - wavesTransfer + snapshot.balances((invoker.toAddress, Waves)) shouldBe ENOUGH_AMT - setVerifier.fee.value - issues.map(_.fee.value).sum - fee + wavesTransfer } } } @@ -186,8 +173,8 @@ class MultiPaymentInvokeDiffTest extends PropSpec with WithState { ): Seq[ (Seq[GenesisTransaction], SetScriptTransaction, SetScriptTransaction, InvokeScriptTransaction, List[IssueTransaction], KeyPair, KeyPair, Long) ] = { - val master = TxHelpers.signer(0) - val invoker = TxHelpers.signer(1) + val master = TxHelpers.signer(1) + val invoker = TxHelpers.signer(2) val fee = if (withEnoughFee) TxHelpers.ciFee(ContractLimits.MaxAttachedPaymentAmount + 1) else TxHelpers.ciFee(1) @@ -256,8 +243,8 @@ class MultiPaymentInvokeDiffTest extends PropSpec with WithState { TestBlock.create(Seq(ci)), features ) { - case Right(diff: Diff) => - val errMsg = diff.scriptResults(diff.transactions.head.transaction.id()).error.get.text + case Right(snapshot: StateSnapshot) => + val errMsg = snapshot.scriptResults(snapshot.transactions.head._2.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/RideV5FailRejectTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/RideV5FailRejectTest.scala index f204213cb0..e5e53e4259 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/RideV5FailRejectTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/RideV5FailRejectTest.scala @@ -8,18 +8,17 @@ import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.script.v1.ExprScript.ExprScriptImpl import com.wavesplatform.lang.v1.compiler.Terms.TRUE import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.state.StringDataEntry import com.wavesplatform.state.TxMeta.Status import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit} -import com.wavesplatform.state.{Portfolio, StringDataEntry} import com.wavesplatform.test.* -import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.test.DomainPresets.RideV5 +import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxHelpers.* import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.{TransactionType, TxHelpers} class RideV5FailRejectTest extends PropSpec with WithDomain { - import DomainPresets.* - private val assetFailScript = TestCompiler(V5).compileExpression( s""" | strict c = ${(1 to 5).map(_ => "sigVerify(base58'', base58'', base58'')").mkString(" || ")} @@ -107,32 +106,6 @@ class RideV5FailRejectTest extends PropSpec with WithDomain { } } - property("invoke is rejected with a lack of funds without execution of ScriptTransfer script only after RideV6") { - val issueTx = issue(signer(10), script = Some(assetFailScript)) - val asset = IssuedAsset(issueTx.id()) - val dApp = TestCompiler(V5).compileContract( - s""" - | @Callable(i) - | func default() = [ - | ScriptTransfer(i.caller, 1, base58'$asset') - | ] - """.stripMargin - ) - val invokeTx = invoke() - withDomain(RideV5, AddrWithBalance.enoughBalances(secondSigner, signer(10))) { d => - d.appendBlock(issueTx) - d.appendBlock(setScript(secondSigner, dApp)) - d.appendAndAssertFailed(invokeTx, "Transaction is not allowed by script of the asset") - } - - // TODO: move test after bug fix NODE-2520 - withDomain(RideV6, AddrWithBalance.enoughBalances(secondSigner, signer(10))) { d => - d.appendBlock(issueTx) - d.appendBlock(setScript(secondSigner, dApp)) - d.appendBlockE(invokeTx) should produce("negative asset balance") - } - } - property("failed invoke doesn't affect state") { withDomain(RideV5, AddrWithBalance.enoughBalances(secondSigner, signer(10))) { d => val failAssetIssue = issue(script = Some(assetFailScript)) @@ -166,19 +139,21 @@ class RideV5FailRejectTest extends PropSpec with WithDomain { d.appendBlock(setScript(secondSigner, dApp)) d.appendBlock(invokeTx) d.blockchain.transactionInfo(invokeTx.id()).get._1.status == Status.Succeeded shouldBe false - d.liquidDiff.sponsorship shouldBe Map() - d.liquidDiff.leaseState shouldBe Map() - d.liquidDiff.issuedAssets shouldBe Map() - d.liquidDiff.updatedAssets shouldBe Map() - d.liquidDiff.accountData shouldBe Map() + d.liquidSnapshot.sponsorships shouldBe Map() + d.liquidSnapshot.newLeases shouldBe Map() + d.liquidSnapshot.cancelledLeases shouldBe Map() + d.liquidSnapshot.assetStatics shouldBe Map() + d.liquidSnapshot.assetNamesAndDescriptions shouldBe Map() + d.liquidSnapshot.accountData shouldBe Map() d.blockchain.accountData(secondAddress, "old").get.value shouldBe "value" - d.liquidDiff.portfolios shouldBe { + d.liquidSnapshot.balances shouldBe { val reward = d.blockchain.blockReward(d.blockchain.height).get val setScriptFee = FeeConstants(TransactionType.SetScript) * FeeUnit val previousBlockReward = (0.6 * setScriptFee).toLong val currentBlockReward = (0.4 * invokeFee).toLong - val total = reward + previousBlockReward + currentBlockReward - invokeFee - Map(defaultAddress -> Portfolio.waves(total)) + val balanceDiff = reward + previousBlockReward + currentBlockReward - invokeFee + val dbBalance = d.rocksDBWriter.balance(defaultAddress) + Map((defaultAddress, Waves) -> (dbBalance + balanceDiff)) } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/ScriptTransferByAliasTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/ScriptTransferByAliasTest.scala index 04db51ad1e..f1ba5fb0ea 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/ScriptTransferByAliasTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/ScriptTransferByAliasTest.scala @@ -103,7 +103,7 @@ class ScriptTransferByAliasTest extends PropSpec with WithDomain { """.stripMargin ) d.appendBlock(setScript(secondSigner, dApp)) - d.appendBlockE(invoke()) should produce("Alias 'alias:T:alias' does not exists") + d.appendBlockE(invoke()) should produce("Alias 'alias:T:alias' does not exist") } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/ScriptTransferTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/ScriptTransferTest.scala index cb2e392016..aacbfc1338 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/ScriptTransferTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/ScriptTransferTest.scala @@ -90,7 +90,7 @@ class ScriptTransferTest extends PropSpec with WithDomain { val invokeTx = invoke() d.appendBlock(setScript(secondSigner, dApp)) d.appendBlockE(invokeTx) - d.liquidDiff.errorMessage(invokeTx.id()).get.text should include("key not found: asset") + d.liquidSnapshot.errorMessage(invokeTx.id()).get.text should include("key not found: asset") } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/TransactionAssetChecksTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/TransactionAssetChecksTest.scala new file mode 100644 index 0000000000..81317a31b7 --- /dev/null +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/TransactionAssetChecksTest.scala @@ -0,0 +1,113 @@ +package com.wavesplatform.state.diffs.ci + +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.db.WithDomain +import com.wavesplatform.db.WithState.AddrWithBalance +import com.wavesplatform.lang.directives.values.V8 +import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.test.DomainPresets.* +import com.wavesplatform.test.{PropSpec, produce} +import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.transaction.EthTxGenerator.{generateEthInvoke, generateEthTransfer} +import com.wavesplatform.transaction.TxHelpers.* +import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment +import com.wavesplatform.transaction.utils.EthConverters.EthereumKeyPairExt + +import scala.util.Try + +class TransactionAssetChecksTest extends PropSpec with WithDomain { + private val dApp = TestCompiler(V8).compileContract( + """ + | @Callable(i) + | func default() = [] + """.stripMargin + ) + private val issueTx = issue(secondSigner) + private val asset = IssuedAsset(issueTx.id()) + + property("invoke script transaction") { + withDomain(TransactionStateSnapshot, AddrWithBalance.enoughBalances(defaultSigner, secondSigner)) { d => + d.appendBlock(setScript(secondSigner, dApp), issueTx) + d.appendBlockE(invoke(secondAddress, payments = Seq(Payment(1, IssuedAsset(ByteStr.fill(31)(1)))))) should produce( + "invalid asset ID 'tVojvhToWjQ8Xvo4UPx2Xz9eRy7auyYMmZBjc2XfN' length = 31 bytes, must be 32" + ) + d.appendBlockE(invoke(secondAddress, payments = Seq(Payment(1, IssuedAsset(ByteStr.fill(33)(1)))))) should produce( + "invalid asset ID 'JJEfe6DcPM2ziB2vfUWDV6aHVerXRGkv3TcyvJUNGHZz' length = 33 bytes, must be 32" + ) + d.appendBlockE(invoke(secondAddress, payments = Seq(Payment(1, IssuedAsset(ByteStr.fill(32)(1)))))) should produce( + "asset '4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi' is not found on the blockchain" + ) + val invokeWithIssued = invoke(secondAddress, payments = Seq(Payment(1, asset))) + d.appendBlockE(invokeWithIssued) should produce(s"leads to negative asset '$asset' balance") + d.appendBlock(transfer(secondSigner, defaultAddress, asset = asset)) + d.appendAndAssertSucceed(invokeWithIssued) + } + } + + property("ethereum invoke script transaction") { + withDomain( + TransactionStateSnapshot, + AddrWithBalance.enoughBalances(defaultSigner, secondSigner) ++ Seq( + AddrWithBalance(defaultSigner.toEthWavesAddress), + AddrWithBalance(secondSigner.toEthWavesAddress) + ) + ) { d => + d.appendBlock(setScript(secondSigner, dApp), issueTx, transfer(secondSigner, defaultSigner.toEthWavesAddress, asset = asset)) + Try( + generateEthInvoke(defaultEthSigner, secondAddress, "default", Nil, Seq(Payment(1, IssuedAsset(ByteStr.fill(31)(1))))) + ).toEither should produce("InvocationTargetException") + Try( + generateEthInvoke(defaultEthSigner, secondAddress, "default", Nil, Seq(Payment(1, IssuedAsset(ByteStr.fill(33)(1))))) + ).toEither should produce("InvocationTargetException") + d.appendBlockE( + generateEthInvoke(defaultEthSigner, secondAddress, "default", Nil, Seq(Payment(1, IssuedAsset(ByteStr.fill(32)(1))))) + ) should produce( + "asset '4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi' is not found on the blockchain" + ) + val invokeWithIssued = generateEthInvoke(secondSigner.toEthKeyPair, secondAddress, "default", Nil, payments = Seq(Payment(1, asset))) + d.appendBlockE(invokeWithIssued) should produce("negative asset balance") + d.appendBlock(transfer(secondSigner, secondSigner.toEthWavesAddress, asset = asset)) + d.appendAndAssertSucceed(invokeWithIssued) + } + } + + property("transfer transaction") { + withDomain(TransactionStateSnapshot, AddrWithBalance.enoughBalances(defaultSigner, secondSigner)) { d => + d.appendBlock(setScript(secondSigner, dApp), issueTx) + d.appendBlockE(transfer(asset = IssuedAsset(ByteStr.fill(31)(1)))) should produce( + "invalid asset ID 'tVojvhToWjQ8Xvo4UPx2Xz9eRy7auyYMmZBjc2XfN' length = 31 bytes, must be 32" + ) + d.appendBlockE(transfer(asset = IssuedAsset(ByteStr.fill(33)(1)))) should produce( + "invalid asset ID 'JJEfe6DcPM2ziB2vfUWDV6aHVerXRGkv3TcyvJUNGHZz' length = 33 bytes, must be 32" + ) + d.appendBlockE(transfer(asset = IssuedAsset(ByteStr.fill(32)(1)))) should produce( + "asset '4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi' is not found on the blockchain" + ) + val transferIssued = transfer(asset = asset) + d.appendBlockE(transferIssued) should produce(s"leads to negative asset '$asset' balance") + d.appendBlock(transfer(secondSigner, defaultAddress, asset = asset)) + d.appendAndAssertSucceed(transferIssued) + } + } + + property("ethereum transfer transaction") { + withDomain( + TransactionStateSnapshot, + AddrWithBalance.enoughBalances(defaultSigner, secondSigner) ++ Seq( + AddrWithBalance(defaultSigner.toEthWavesAddress), + AddrWithBalance(secondSigner.toEthWavesAddress) + ) + ) { d => + d.appendBlock(setScript(secondSigner, dApp), issueTx, transfer(secondSigner, defaultSigner.toEthWavesAddress, asset = asset)) + (31 to 33).foreach(i => + d.appendBlockE(generateEthTransfer(defaultEthSigner, secondAddress, 1, IssuedAsset(ByteStr.fill(i)(1)))) should produce( + "Can't resolve ERC20 address" + ) + ) + val transferIssued = generateEthTransfer(secondSigner.toEthKeyPair, secondAddress, 1, asset) + d.appendBlockE(transferIssued) should produce(s"negative asset balance") + d.appendBlock(transfer(secondSigner, secondSigner.toEthWavesAddress, asset = asset)) + d.appendAndAssertSucceed(transferIssued) + } + } +} diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/InvokeScriptTransactionCrosscontractInvokeDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/InvokeScriptTransactionCrosscontractInvokeDiffTest.scala index ed20ca45b2..1bee5f15f6 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/InvokeScriptTransactionCrosscontractInvokeDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/InvokeScriptTransactionCrosscontractInvokeDiffTest.scala @@ -130,8 +130,8 @@ class InvokeScriptTransactionCrosscontractInvokeDiffTest forAll(scenario) { case (genesisTxs, invokeTx, secondDApp) => assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invokeTx), Block.ProtoBlockVersion), fsWithV5) { - case (diff, bc) => - diff.errorMessage(invokeTx.id()) shouldBe None + case (snapshot, bc) => + snapshot.errorMessage(invokeTx.id()) shouldBe None bc.accountData(secondDApp, invokeEntry1Key) shouldBe Some(IntegerDataEntry(invokeEntry1Key, invokeEntry1Val)) bc.accountData(secondDApp, invokeEntry2Key) shouldBe Some(IntegerDataEntry(invokeEntry2Key, invokeEntry2NewVal)) @@ -215,8 +215,8 @@ class InvokeScriptTransactionCrosscontractInvokeDiffTest forAll(scenario) { case (genesisTxs, invokeTx, mainDApp) => assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invokeTx), Block.ProtoBlockVersion), fsWithV5) { - case (diff, bc) => - diff.errorMessage(invokeTx.id()) shouldBe None + case (snapshot, bc) => + snapshot.errorMessage(invokeTx.id()) shouldBe None bc.accountData(mainDApp, invokeEntry1Key) shouldBe Some(IntegerDataEntry(invokeEntry1Key, invokeEntry1Val)) bc.accountData(mainDApp, invokeEntry2Key) shouldBe Some(IntegerDataEntry(invokeEntry2Key, invokeEntry2NewVal)) @@ -396,8 +396,8 @@ class InvokeScriptTransactionCrosscontractInvokeDiffTest forAll(scenario) { case (genesisTxs, invokeTx, thirdAcc, transferAsset, paymentAsset) => assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invokeTx), Block.ProtoBlockVersion), fsWithV5) { - case (diff, bc) => - diff.errorMessage(invokeTx.id()) shouldBe None + case (snapshot, bc) => + snapshot.errorMessage(invokeTx.id()) shouldBe None bc.balance(thirdAcc, IssuedAsset(transferAsset)) shouldBe transferAssetAmount bc.balance(thirdAcc, IssuedAsset(paymentAsset)) shouldBe paymentAssetAmount diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppComplexityCountTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppComplexityCountTest.scala index f53b4dc645..4802aa5ec1 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppComplexityCountTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppComplexityCountTest.scala @@ -16,7 +16,7 @@ import com.wavesplatform.state.Portfolio import com.wavesplatform.state.diffs.BlockDiffer.CurrentBlockFeePart import com.wavesplatform.state.diffs.{ENOUGH_AMT, ci} import com.wavesplatform.test.* -import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.smart.script.ScriptCompiler import com.wavesplatform.transaction.smart.{InvokeTransaction, SetScriptTransaction} @@ -168,52 +168,56 @@ class SyncDAppComplexityCountTest extends PropSpec with WithDomain { ): Unit = { val (preparingTxs, invokeTx, asset, lastCallingDApp) = scenario(dAppCount, withPayment, withThroughPayment, withThroughTransfer, withVerifier, raiseError, sequentialCalls, invokeExpression) - assertDiffEi( - Seq(TestBlock.create(preparingTxs), TestBlock.create(Seq.empty)), - TestBlock.create(Seq(invokeTx), Block.ProtoBlockVersion), - features(invokeExpression) - ) { diffE => - if (reject) { - diffE shouldBe Symbol("left") - diffE should produce("Error raised") - } else { - val diff = diffE.explicitGet() - diff.scriptsComplexity shouldBe complexity - if (exceeding) - diff.errorMessage(invokeTx.id()).get.text should include("Invoke complexity limit = 26000 is exceeded") - else if (raiseError) - diff.errorMessage(invokeTx.id()).get.text should include("Error raised") - else - diff.errorMessage(invokeTx.id()) shouldBe None - - val dAppAddress = invokeTx.dApp.asInstanceOf[Address] - val basePortfolios = - Map(TestBlock.defaultSigner.toAddress -> Portfolio(CurrentBlockFeePart(invokeTx.fee.value))) |+| - Map(invokeTx.sender.toAddress -> Portfolio(-invokeTx.fee.value)) - val paymentsPortfolios = - Map(invokeTx.sender.toAddress -> Portfolio.build(asset, -1)) |+| - Map(dAppAddress -> Portfolio.build(asset, 1)) - val throughTransfersPortfolios = - Map(invokeTx.sender.toAddress -> Portfolio.build(asset, 1)) |+| - Map(lastCallingDApp -> Portfolio.build(asset, -1)) - val throughPaymentsPortfolios = - Map(lastCallingDApp -> Portfolio.build(asset, 1)) |+| - Map(dAppAddress -> Portfolio.build(asset, -1)) - - val overlappedPortfolio = Portfolio.build(asset, 0) - val emptyPortfolios = Map.empty[Address, Portfolio] - - val additionalPortfolios = - (if (withPayment) paymentsPortfolios else emptyPortfolios) |+| - (if (withThroughPayment) throughPaymentsPortfolios else emptyPortfolios) |+| - (if (withThroughTransfer) throughTransfersPortfolios else emptyPortfolios) - - val totalPortfolios = if (exceeding || raiseError) basePortfolios else basePortfolios |+| additionalPortfolios - - diff.portfolios.filter(_._2 != overlappedPortfolio) shouldBe - totalPortfolios - .filter(_._2 != overlappedPortfolio) - .map(p => p.copy(_2 = p._2.copy(assets = p._2.assets.filterNot(_._2 == 0)))) + withTestState(features(invokeExpression)) { (bu, db) => + assertDiffEi( + Seq(TestBlock.create(preparingTxs), TestBlock.create(Seq.empty)), + TestBlock.create(Seq(invokeTx), Block.ProtoBlockVersion), + bu, + db, + enableExecutionLog = false + ) { diffE => + if (reject) { + diffE shouldBe Symbol("left") + diffE should produce("Error raised") + } else { + val snapshot = diffE.explicitGet() + snapshot.scriptsComplexity shouldBe complexity + if (exceeding) + snapshot.errorMessage(invokeTx.id()).get.text should include("Invoke complexity limit = 26000 is exceeded") + else if (raiseError) + snapshot.errorMessage(invokeTx.id()).get.text should include("Error raised") + else + snapshot.errorMessage(invokeTx.id()) shouldBe None + + val dAppAddress = invokeTx.dApp.asInstanceOf[Address] + val basePortfolios = + Map(TestBlock.defaultSigner.toAddress -> Portfolio(CurrentBlockFeePart(invokeTx.fee.value))) |+| + Map(invokeTx.sender.toAddress -> Portfolio(-invokeTx.fee.value)) + val paymentsPortfolios = + Map(invokeTx.sender.toAddress -> Portfolio.build(asset, -1)) |+| + Map(dAppAddress -> Portfolio.build(asset, 1)) + val throughTransfersPortfolios = + Map(invokeTx.sender.toAddress -> Portfolio.build(asset, 1)) |+| + Map(lastCallingDApp -> Portfolio.build(asset, -1)) + val throughPaymentsPortfolios = + Map(lastCallingDApp -> Portfolio.build(asset, 1)) |+| + Map(dAppAddress -> Portfolio.build(asset, -1)) + + val emptyPortfolios = Map.empty[Address, Portfolio] + val additionalPortfolios = + (if (withPayment) paymentsPortfolios else emptyPortfolios) |+| + (if (withThroughPayment) throughPaymentsPortfolios else emptyPortfolios) |+| + (if (withThroughTransfer) throughTransfersPortfolios else emptyPortfolios) + + val expectedPortfolios = if (exceeding || raiseError) basePortfolios else basePortfolios |+| additionalPortfolios + expectedPortfolios + .foreach { case (address, expectedPortfolio) => + expectedPortfolio.balance shouldBe snapshot.balances.get((address, Waves)).map(_ - db.balance(address)).getOrElse(0) + expectedPortfolio.assets.foreach { case (asset, balance) => + balance shouldBe snapshot.balances.get((address, asset)).map(_ - db.balance(address, asset)).getOrElse(0) + } + } + } } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppGeneratingBalanceTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppGeneratingBalanceTest.scala index 07aebadeb4..492982797a 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppGeneratingBalanceTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppGeneratingBalanceTest.scala @@ -35,10 +35,10 @@ class SyncDAppGeneratingBalanceTest extends PropSpec with WithDomain { d.appendBlock(setScript(defaultSigner, dApp), setScript(secondSigner, dApp)) d.appendAndAssertSucceed(invoke(secondAddress, invoker = secondSigner)) - d.liquidDiff.accountData.head._2.head._2.value shouldBe 0 + d.liquidSnapshot.accountData.head._2.head._2.value shouldBe 0 d.appendAndAssertSucceed(invoke(secondAddress, invoker = secondSigner)) - d.liquidDiff.accountData.head._2.head._2.value shouldBe amount + d.liquidSnapshot.accountData.head._2.head._2.value shouldBe amount } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppLeaseBalanceCheckTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppLeaseBalanceCheckTest.scala index eab3c89081..fdbba4048e 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppLeaseBalanceCheckTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppLeaseBalanceCheckTest.scala @@ -70,7 +70,7 @@ class SyncDAppLeaseBalanceCheckTest extends PropSpec with WithDomain { if (bigComplexityDApp1 || bigComplexityDApp2) { d.appendBlock(invoke) - d.liquidDiff.errorMessage(invoke.txId).get.text should include("Cannot lease more than own: Balance: 0") + d.liquidSnapshot.errorMessage(invoke.txId).get.text should include("Cannot lease more than own: Balance: 0") } else { d.appendBlockE(invoke) should produce("Cannot lease more than own: Balance: 0") } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppLimits.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppLimits.scala index 3777f2dccb..27b4ee28c4 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppLimits.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppLimits.scala @@ -64,8 +64,8 @@ class SyncDAppLimits extends PropSpec with WithDomain with OptionValues with Eit val calls = complexityLimit / 2000 + 1 val invokeTx = TxHelpers.invoke(aliceAddr, Some("foo"), Seq(CONST_LONG(calls)), invoker = alice) - val diff = d.createDiffE(invokeTx).value - val (_, scriptResult) = diff.scriptResults.headOption.value + val snapshot = d.createDiffE(invokeTx).value + val (_, scriptResult) = snapshot.scriptResults.headOption.value scriptResult.error.value.text should include(s"Invoke complexity limit = $complexityLimit is exceeded") d.appendBlock(invokeTx) @@ -99,8 +99,8 @@ class SyncDAppLimits extends PropSpec with WithDomain with OptionValues with Eit val invokeTx = TxHelpers.invoke(aliceAddr, Some("foo"), Seq(CONST_LONG(101)), invoker = alice) - val diff = d.createDiffE(invokeTx).value - val (_, scriptResult) = diff.scriptResults.headOption.value + val snapshot = d.createDiffE(invokeTx).value + val (_, scriptResult) = snapshot.scriptResults.headOption.value scriptResult.error.value.text should include("DApp calls limit = 100 is exceeded") d.appendBlock(invokeTx) diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppNegativeIssueTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppNegativeIssueTest.scala index bce5c82476..4377f2733c 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppNegativeIssueTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppNegativeIssueTest.scala @@ -67,7 +67,7 @@ class SyncDAppNegativeIssueTest extends PropSpec with WithDomain { d.appendBlock(preparingTxs*) if (bigComplexityDApp1 || bigComplexityDApp2) { d.appendBlock(invoke) - d.liquidDiff.errorMessage(invoke.txId).get.text should include("Invalid decimals") + d.liquidSnapshot.errorMessage(invoke.txId).get.text should include("Invalid decimals") } else { d.appendBlockE(invoke) should produce("Invalid decimals") d.appendBlock() diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppPaymentTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppPaymentTest.scala index ce4064f7c0..d2be554298 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppPaymentTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppPaymentTest.scala @@ -10,7 +10,6 @@ import com.wavesplatform.lang.directives.values.V5 import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.lang.v1.evaluator.ctx.impl.GlobalValNames -import com.wavesplatform.state.Portfolio import com.wavesplatform.state.diffs.{ENOUGH_AMT, produceRejectOrFailedDiff} import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.* @@ -34,8 +33,8 @@ class SyncDAppPaymentTest extends PropSpec with WithDomain { d.appendBlock(invoke1) d.blockchain.transactionSucceeded(invoke1.id.value()) shouldBe true - d.liquidDiff.portfolios(dApp1) shouldBe Portfolio.build(asset, 1) - d.liquidDiff.portfolios(dApp2) shouldBe Portfolio.build(asset, -1) + d.liquidSnapshot.balances((dApp1, asset)) shouldBe d.rocksDBWriter.balance(dApp1, asset) + 1 + d.liquidSnapshot.balances((dApp2, asset)) shouldBe d.rocksDBWriter.balance(dApp2, asset) - 1 val invoke2 = invoke() d.appendBlockE(invoke2) should produce { diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppRecursionTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppRecursionTest.scala index 32a135f3f4..eea40a4817 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppRecursionTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppRecursionTest.scala @@ -109,9 +109,9 @@ class SyncDAppRecursionTest extends PropSpec with WithDomain with Inside { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), features(invokeExpression) - ) { case (diff, _) => - diff.errorMessage(invoke.id()) shouldBe None - inside(diff.scriptResults.toSeq) { case Seq((_, call1)) => + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id()) shouldBe None + inside(snapshot.scriptResults.toSeq) { case Seq((_, call1)) => inside(call1.invokes) { case Seq(call2) => inside(call2.stateChanges.invokes) { case Seq(call3) => call3.stateChanges.error shouldBe empty @@ -308,9 +308,9 @@ class SyncDAppRecursionTest extends PropSpec with WithDomain with Inside { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), features() - ) { case (diff, _) => - diff.errorMessage(invoke.id.value()) shouldBe None - inside(diff.scriptResults.toSeq) { case Seq((_, call1)) => + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id.value()) shouldBe None + inside(snapshot.scriptResults.toSeq) { case Seq((_, call1)) => inside(call1.invokes) { case Seq(call2) => inside(call2.stateChanges.invokes) { case Seq(call3) => inside(call3.stateChanges.invokes) { case Seq(call4) => @@ -352,9 +352,9 @@ class SyncDAppRecursionTest extends PropSpec with WithDomain with Inside { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), features() - ) { case (diff, _) => - diff.errorMessage(invoke.id.value()) shouldBe None - inside(diff.scriptResults.toSeq) { case Seq((_, call1)) => + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id.value()) shouldBe None + inside(snapshot.scriptResults.toSeq) { case Seq((_, call1)) => inside(call1.invokes) { case Seq(call2) => inside(call2.stateChanges.invokes) { case Seq(call3) => inside(call3.stateChanges.invokes) { case Seq(call4) => @@ -394,9 +394,9 @@ class SyncDAppRecursionTest extends PropSpec with WithDomain with Inside { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), features() - ) { case (diff, _) => - diff.errorMessage(invoke.id.value()) shouldBe None - inside(diff.scriptResults.toSeq) { case Seq((_, call1)) => + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id.value()) shouldBe None + inside(snapshot.scriptResults.toSeq) { case Seq((_, call1)) => inside(call1.invokes) { case Seq(call2) => inside(call2.stateChanges.invokes) { case Seq(call3) => inside(call3.stateChanges.invokes) { case Seq(call4) => @@ -476,9 +476,9 @@ class SyncDAppRecursionTest extends PropSpec with WithDomain with Inside { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), features() - ) { case (diff, _) => - diff.errorMessage(invoke.id.value()) shouldBe None - inside(diff.scriptResults.toSeq) { case Seq((_, call1)) => + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id.value()) shouldBe None + inside(snapshot.scriptResults.toSeq) { case Seq((_, call1)) => inside(call1.invokes) { case Seq(call2) => inside(call2.stateChanges.invokes) { case Seq(call3) => inside(call3.stateChanges.invokes) { case Seq(call4) => @@ -565,9 +565,9 @@ class SyncDAppRecursionTest extends PropSpec with WithDomain with Inside { Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invoke)), features() - ) { case (diff, _) => - diff.errorMessage(invoke.id.value()) shouldBe None - inside(diff.scriptResults.toSeq) { case Seq((_, call1)) => + ) { case (snapshot, _) => + snapshot.errorMessage(invoke.id.value()) shouldBe None + inside(snapshot.scriptResults.toSeq) { case Seq((_, call1)) => inside(call1.invokes) { case Seq(call21, call22) => inside(call21.stateChanges.invokes) { case Seq(call31) => call31.stateChanges.error shouldBe empty diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppReissueTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppReissueTest.scala index 665ceabed8..ba46aeb05b 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppReissueTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppReissueTest.scala @@ -102,7 +102,7 @@ class SyncDAppReissueTest extends PropSpec with WithDomain { if (bigComplexityDApp1 || bigComplexityDApp2) { d.appendBlock(invoke) - d.liquidDiff.errorMessage(invoke.txId).get.text should include("Asset was issued by other address") + d.liquidSnapshot.errorMessage(invoke.txId).get.text should include("Asset was issued by other address") } else { d.appendBlockE(invoke) should produce("Asset was issued by other address") } 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 9aa6f375b8..c393500a03 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 @@ -18,7 +18,7 @@ import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.lang.v1.evaluator.FunctionIds.CREATE_LIST import com.wavesplatform.lang.v1.evaluator.ctx.impl.GlobalValNames import com.wavesplatform.protobuf.dapp.DAppMeta -import com.wavesplatform.state.diffs.produceRejectOrFailedDiff +import com.wavesplatform.state.diffs.{ENOUGH_AMT, produceRejectOrFailedDiff} import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} @@ -108,10 +108,10 @@ class SyncDAppTransferTest extends PropSpec with WithDomain with Inside { } property("invoking ScriptTransfer in sync call results in accounts state") { - val invoker = TxHelpers.signer(0) - val invokerDApp = TxHelpers.signer(1) - val senderDApp = TxHelpers.signer(2) - val recipient = TxHelpers.signer(3) + val invoker = TxHelpers.signer(1) + val invokerDApp = TxHelpers.signer(2) + val senderDApp = TxHelpers.signer(3) + val recipient = TxHelpers.signer(4) val transferAmount = 10.waves @@ -131,17 +131,17 @@ class SyncDAppTransferTest extends PropSpec with WithDomain with Inside { call2.stateChanges.invokes shouldBe empty } } - blockDiff.portfolios(recipient.toAddress).balance shouldBe transferAmount - blockDiff.portfolios(senderDApp.toAddress).balance shouldBe -transferAmount - blockDiff.transaction(invoke.id()) shouldBe defined + blockDiff.balances((recipient.toAddress, Waves)) shouldBe transferAmount + blockDiff.balances((senderDApp.toAddress, Waves)) shouldBe ENOUGH_AMT - setSenderScript.fee.value - transferAmount + blockDiff.transactions.get(invoke.id()) shouldBe defined } } property("invoking default func ScriptTransfer in sync call results in accounts state") { - val invoker = TxHelpers.signer(0) - val invokerDApp = TxHelpers.signer(1) - val senderDApp = TxHelpers.signer(2) - val recipient = TxHelpers.signer(3) + val invoker = TxHelpers.signer(1) + val invokerDApp = TxHelpers.signer(2) + val senderDApp = TxHelpers.signer(3) + val recipient = TxHelpers.signer(4) val transferAmount = 10.waves @@ -161,9 +161,9 @@ class SyncDAppTransferTest extends PropSpec with WithDomain with Inside { call2.stateChanges.invokes shouldBe empty } } - blockDiff.portfolios(recipient.toAddress).balance shouldBe transferAmount - blockDiff.portfolios(senderDApp.toAddress).balance shouldBe -transferAmount - blockDiff.transaction(invoke.id()) shouldBe defined + blockDiff.balances((recipient.toAddress, Waves)) shouldBe transferAmount + blockDiff.balances((senderDApp.toAddress, Waves)) shouldBe ENOUGH_AMT - setSenderScript.fee.value - transferAmount + blockDiff.transactions.get(invoke.id()) shouldBe defined } } @@ -184,10 +184,10 @@ class SyncDAppTransferTest extends PropSpec with WithDomain with Inside { } property("ScriptTransfer in sync call is allowed if funds were received from attached payment") { - val invoker = TxHelpers.signer(0) - val invokerDApp = TxHelpers.signer(1) - val senderDApp = TxHelpers.signer(2) - val recipient = TxHelpers.signer(3) + val invoker = TxHelpers.signer(1) + val invokerDApp = TxHelpers.signer(2) + val senderDApp = TxHelpers.signer(3) + val recipient = TxHelpers.signer(4) val transferAmount = 10.waves @@ -210,9 +210,9 @@ class SyncDAppTransferTest extends PropSpec with WithDomain with Inside { call2.stateChanges.invokes shouldBe empty } } - blockDiff.portfolios(recipient.toAddress).balance shouldBe transferAmount - blockDiff.portfolios(invokerDApp.toAddress).balance shouldBe -transferAmount - blockDiff.transaction(invoke.id()) shouldBe defined + blockDiff.balances((recipient.toAddress, Waves)) shouldBe transferAmount + blockDiff.balances((invokerDApp.toAddress, Waves)) shouldBe ENOUGH_AMT - setSenderScript.fee.value - transferAmount + blockDiff.transactions.get(invoke.id()) shouldBe defined } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeActionsTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeActionsTest.scala index 40eb6e8108..e573d9caf0 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeActionsTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeActionsTest.scala @@ -3,13 +3,14 @@ package com.wavesplatform.state.diffs.ci.sync import com.wavesplatform.TestValues.invokeFee import com.wavesplatform.db.WithDomain import com.wavesplatform.db.WithState.AddrWithBalance -import com.wavesplatform.lang.directives.values._ +import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.test.{PropSpec, produce} -import com.wavesplatform.transaction.TxHelpers._ +import com.wavesplatform.transaction.Asset.Waves +import com.wavesplatform.transaction.TxHelpers.* class SyncInvokeActionsTest extends PropSpec with WithDomain { - import DomainPresets._ + import DomainPresets.* private val dApp1Signer = secondSigner private val dApp1Address = secondAddress @@ -68,9 +69,9 @@ class SyncInvokeActionsTest extends PropSpec with WithDomain { ) d.appendBlock(setScript(dApp1Signer, dApp1), setScript(dApp2Signer, dApp2)) d.appendAndAssertSucceed(invoke(dApp1Address, fee = invokeFee(issues = 1))) - d.liquidDiff.portfolios.get(dApp1Address) shouldBe empty - d.liquidDiff.portfolios.get(dApp2Address) shouldBe empty - d.liquidDiff.portfolios(defaultAddress).assets.head._2 shouldBe 1000 + d.liquidSnapshot.balances.get((dApp1Address, Waves)) shouldBe empty + d.liquidSnapshot.balances.get((dApp2Address, Waves)) shouldBe empty + d.liquidSnapshot.balances.collect { case ((address, asset), amount) if address == defaultAddress && asset != Waves => amount}.head shouldBe 1000 } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeDiffTest.scala index 6fdfbfc872..040ce70167 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeDiffTest.scala @@ -68,8 +68,8 @@ class SyncInvokeDiffTest extends PropSpec with WithDomain with DBCacheSettings w val invoke = TxHelpers.invoke(dAppAddress, Some("foo"), Nil, payments) assertDiffAndState(Seq(TestBlock.create(Seq(gTx1, gTx2, ssTx))), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { - case (diff, bc) => - diff.errorMessage(invoke.id()) shouldBe None + case (snapshot, bc) => + snapshot.errorMessage(invoke.id()) shouldBe None bc.accountData(dAppAddress, "key") shouldBe Some(IntegerDataEntry("key", 1)) bc.accountData(dAppAddress, "bar") shouldBe Some(IntegerDataEntry("bar", 1)) } @@ -109,10 +109,10 @@ class SyncInvokeDiffTest extends PropSpec with WithDomain with DBCacheSettings w val invoke = TxHelpers.invoke(dAppAddress, Some("foo"), Nil, payments) assertDiffAndState(Seq(TestBlock.create(Seq(gTx1, gTx2, ssTx))), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { - case (diff, _) => - diff.errorMessage(invoke.id()) shouldBe None - diff.scriptsComplexity shouldBe 108 - inside(diff.scriptResults.toSeq) { case Seq((_, call1)) => + case (snapshot, _) => + snapshot.errorMessage(invoke.id()) shouldBe None + snapshot.scriptsComplexity shouldBe 108 + inside(snapshot.scriptResults.toSeq) { case Seq((_, call1)) => inside(call1.invokes) { case Seq(call2) => call2.stateChanges.error shouldBe empty call2.stateChanges.invokes shouldBe empty @@ -187,11 +187,11 @@ class SyncInvokeDiffTest extends PropSpec with WithDomain with DBCacheSettings w val invoke = TxHelpers.invoke(dAppAddress, Some("foo"), Nil, payments, fee = TestValues.invokeFee(issues = 1)) val genesisTxs = Seq(gTx1, gTx2, gTx3, aliasTx, ssTx1, ssTx) - assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (diff, bc) => - diff.scriptResults(invoke.id()).error shouldBe None - val l = diff.scriptResults(invoke.id()).leases(0) - val l1 = diff.scriptResults(invoke.id()).leases(1) - val l2 = diff.scriptResults(invoke.id()).leaseCancels(0) + assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (snapshot, bc) => + snapshot.scriptResults(invoke.id()).error shouldBe None + val l = snapshot.scriptResults(invoke.id()).leases(0) + val l1 = snapshot.scriptResults(invoke.id()).leases(1) + val l2 = snapshot.scriptResults(invoke.id()).leaseCancels(0) l.amount shouldBe 13 l.recipient shouldBe thirdAcc.toAddress l1.amount shouldBe 23 @@ -266,11 +266,11 @@ class SyncInvokeDiffTest extends PropSpec with WithDomain with DBCacheSettings w val invoke = TxHelpers.invoke(dAppAddress, Some("foo"), payments = payments, fee = TestValues.invokeFee(issues = 1)) val genesisTxs = Seq(gTx1, gTx2, gTx3, aliasTx, ssTx1, ssTx) - assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (diff, bc) => - diff.scriptResults(invoke.id()).error shouldBe None - val l = diff.scriptResults(invoke.id()).leases(0) - val l1 = diff.scriptResults(invoke.id()).leases(1) - val l2 = diff.scriptResults(invoke.id()).leaseCancels(0) + assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (snapshot, bc) => + snapshot.scriptResults(invoke.id()).error shouldBe None + val l = snapshot.scriptResults(invoke.id()).leases(0) + val l1 = snapshot.scriptResults(invoke.id()).leases(1) + val l2 = snapshot.scriptResults(invoke.id()).leaseCancels(0) l.amount shouldBe 13 l.recipient shouldBe thirdAcc.toAddress l1.amount shouldBe 23 @@ -340,8 +340,8 @@ class SyncInvokeDiffTest extends PropSpec with WithDomain with DBCacheSettings w val payments = List(Payment(10L, Waves)) val invoke = TxHelpers.invoke(dAppAddress, Some("foo"), payments = payments, fee = TestValues.invokeFee(2)) val genesisTxs = Seq(gTx1, gTx2, gTx3, aliasTx, ssTx1, ssTx) - assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (diff, bc) => - val err = diff.scriptResults(invoke.id()).error.get + assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (snapshot, bc) => + val err = snapshot.scriptResults(invoke.id()).error.get err.code shouldBe FailedTransactionError.Cause.FeeForActions.code bc.accountData(dAppAddress, "key") shouldBe None bc.accountData(thirdAcc.toAddress, "bar") shouldBe None @@ -409,7 +409,7 @@ class SyncInvokeDiffTest extends PropSpec with WithDomain with DBCacheSettings w val invoke = TxHelpers.invoke(dAppAddress, Some("foo"), payments = payments, fee = TestValues.invokeFee(issues = 1)) val genesisTxs = Seq(gTx1, gTx2, gTx3, aliasTx, ssTx1, ssTx) - assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (diff, bc) => + assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (snapshot, bc) => bc.accountData(dAppAddress, "key") shouldBe Some(IntegerDataEntry("key", 1)) bc.accountData(thirdAcc.toAddress, "bar") shouldBe Some(IntegerDataEntry("bar", 1)) } @@ -481,7 +481,7 @@ class SyncInvokeDiffTest extends PropSpec with WithDomain with DBCacheSettings w val genesisTxs = Seq(gTx1, gTx2, gTx3, ssTx1, ssTx) - assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (diff, bc) => + assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (snapshot, bc) => bc.accountData(dAppAddress, "key") shouldBe Some(IntegerDataEntry("key", 1)) bc.accountData(thirdAcc.toAddress, "bar") shouldBe Some(IntegerDataEntry("bar", 1)) } @@ -1064,8 +1064,8 @@ class SyncInvokeDiffTest extends PropSpec with WithDomain with DBCacheSettings w val invoke = TxHelpers.invoke(dAppAddress, Some("foo"), payments = payments, fee = fee) val genesisTxs = Seq(gTx1, gTx2, gTx3, setServiceDApp, setClientDApp, paymentIssue, transferIssue) - assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (diff, bc) => - diff.errorMessage(invoke.id()) shouldBe None + assertDiffAndState(Seq(TestBlock.create(genesisTxs)), TestBlock.create(Seq(invoke), Block.ProtoBlockVersion), fsWithV5) { case (snapshot, bc) => + snapshot.errorMessage(invoke.id()) shouldBe None bc.accountData(dAppAddress, "key") shouldBe Some(IntegerDataEntry("key", 1)) bc.accountData(thirdAcc.toAddress, "bar") shouldBe Some(IntegerDataEntry("bar", 1)) @@ -1137,8 +1137,8 @@ class SyncInvokeDiffTest extends PropSpec with WithDomain with DBCacheSettings w val invokeTx = TxHelpers.invoke(dAppAddress, Some("foo"), payments = payments) val preparingTxs = Seq(gTx1, gTx2, gTx3, ssTxMain, ssTxSecond, dataTxSecond, dataTxSecond2) - assertDiffAndState(Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invokeTx), Block.ProtoBlockVersion), fsWithV5) { case (diff, bc) => - diff.errorMessage(invokeTx.id()) shouldBe None + assertDiffAndState(Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invokeTx), Block.ProtoBlockVersion), fsWithV5) { case (snapshot, bc) => + snapshot.errorMessage(invokeTx.id()) shouldBe None bc.accountData(thirdAddress, invokeEntry1Key) shouldBe Some(IntegerDataEntry(invokeEntry1Key, invokeEntry1Val)) bc.accountData(thirdAddress, invokeEntry2Key) shouldBe Some(IntegerDataEntry(invokeEntry2Key, invokeEntry2NewVal)) } @@ -1196,8 +1196,8 @@ class SyncInvokeDiffTest extends PropSpec with WithDomain with DBCacheSettings w val invokeTx = TxHelpers.invoke(dAppAddress, Some("foo"), payments = payments) val preparingTxs = Seq(gTx1, gTx2, ssTxMain, dataTxMain, dataTxMain2) - assertDiffAndState(Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invokeTx), Block.ProtoBlockVersion), fsWithV5) { case (diff, bc) => - diff.errorMessage(invokeTx.id()) shouldBe None + assertDiffAndState(Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invokeTx), Block.ProtoBlockVersion), fsWithV5) { case (snapshot, bc) => + snapshot.errorMessage(invokeTx.id()) shouldBe None bc.accountData(dAppAddress, invokeEntry1Key) shouldBe Some(IntegerDataEntry(invokeEntry1Key, invokeEntry1Val)) bc.accountData(dAppAddress, invokeEntry2Key) shouldBe Some(IntegerDataEntry(invokeEntry2Key, invokeEntry2NewVal)) } @@ -1300,8 +1300,8 @@ class SyncInvokeDiffTest extends PropSpec with WithDomain with DBCacheSettings w val invokeTx = TxHelpers.invoke(dAppAddress, Some("foo"), payments = payments) val preparingTxs = Seq(gTx1, gTx2, gTx3, gTx4, ssTxMain, ssTxSecond, ssTxThird, paymentIssue, transferIssue) - assertDiffAndState(Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invokeTx), Block.ProtoBlockVersion), fsWithV5) { case (diff, bc) => - diff.errorMessage(invokeTx.id()) shouldBe None + assertDiffAndState(Seq(TestBlock.create(preparingTxs)), TestBlock.create(Seq(invokeTx), Block.ProtoBlockVersion), fsWithV5) { case (snapshot, bc) => + snapshot.errorMessage(invokeTx.id()) shouldBe None bc.balance(thirdAcc.toAddress, IssuedAsset(transferIssue.id())) shouldBe transferAssetAmount bc.balance(thirdAcc.toAddress, IssuedAsset(paymentIssue.id())) shouldBe paymentAssetAmount } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeFailAndRejectTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeFailAndRejectTest.scala index 305aeee348..34cffe1857 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeFailAndRejectTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeFailAndRejectTest.scala @@ -7,11 +7,11 @@ import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.script.v1.ExprScript.ExprScriptImpl import com.wavesplatform.lang.v1.compiler.Terms.TRUE import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.state.StringDataEntry import com.wavesplatform.state.TxMeta.Status import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit} -import com.wavesplatform.state.{Portfolio, StringDataEntry} import com.wavesplatform.test.{PropSpec, produce} -import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TransactionType import com.wavesplatform.transaction.TxHelpers.* import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment @@ -76,19 +76,22 @@ class SyncInvokeFailAndRejectTest extends PropSpec with WithDomain { d.appendBlock(setScript(dApp2Signer, dApp2)) d.appendBlock(invokeTx) d.blockchain.transactionInfo(invokeTx.id()).get._1.status == Status.Succeeded shouldBe false - d.liquidDiff.sponsorship shouldBe Map() - d.liquidDiff.leaseState shouldBe Map() - d.liquidDiff.issuedAssets shouldBe Map() - d.liquidDiff.updatedAssets shouldBe Map() - d.liquidDiff.accountData shouldBe Map() + d.liquidSnapshot.sponsorships should be(empty) + d.liquidSnapshot.newLeases should be(empty) + d.liquidSnapshot.cancelledLeases should be(empty) + d.liquidSnapshot.assetStatics should be(empty) + d.liquidSnapshot.assetNamesAndDescriptions should be(empty) + d.liquidSnapshot.assetVolumes should be(empty) + d.liquidSnapshot.accountData should be(empty) d.blockchain.accountData(dApp2Address, "old").get.value shouldBe "value" - d.liquidDiff.portfolios shouldBe { + d.liquidSnapshot.balances shouldBe { val reward = d.blockchain.blockReward(d.blockchain.height).get val setScriptFee = FeeConstants(TransactionType.SetScript) * FeeUnit val previousBlockReward = (0.6 * setScriptFee).toLong val currentBlockReward = (0.4 * invokeFee).toLong - val total = reward + previousBlockReward + currentBlockReward - invokeFee - Map(defaultAddress -> Portfolio.waves(total)) + val balanceDiff = reward + previousBlockReward + currentBlockReward - invokeFee + val dbBalance = d.rocksDBWriter.balance(defaultAddress) + Map((defaultAddress, Waves) -> (balanceDiff + dbBalance)) } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeLeaseTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeLeaseTest.scala index f5a2165bf2..57127d2452 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeLeaseTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeLeaseTest.scala @@ -5,12 +5,12 @@ import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit} -import com.wavesplatform.state.reader.LeaseDetails.Status.{Active, Cancelled} import com.wavesplatform.test.{PropSpec, produce} import com.wavesplatform.transaction.TransactionType import com.wavesplatform.transaction.TxHelpers.* +import org.scalatest.OptionValues -class SyncInvokeLeaseTest extends PropSpec with WithDomain { +class SyncInvokeLeaseTest extends PropSpec with WithDomain with OptionValues { import DomainPresets.* private val dApp1Signer = secondSigner @@ -42,14 +42,14 @@ class SyncInvokeLeaseTest extends PropSpec with WithDomain { withDomain(RideV5, AddrWithBalance.enoughBalances(dApp1Signer)) { d => d.appendBlock(setScript(dApp1Signer, twoLeaseDApp(555))) d.appendAndAssertSucceed(invoke(dApp1Address)) - val lease1 = d.liquidDiff.leaseState.head._2 - lease1.status shouldBe a[Cancelled] - lease1.recipient shouldBe dApp2Address - lease1.amount shouldBe 1 - val lease2 = d.liquidDiff.leaseState.last._2 - lease2.status shouldBe Active - lease2.recipient shouldBe dApp2Address - lease2.amount shouldBe 555 + d.liquidSnapshot.cancelledLeases.size shouldBe 1 + val cancelledLeaseId = d.liquidSnapshot.cancelledLeases.head._1 + val cancelledLease = d.liquidSnapshot.newLeases.get(cancelledLeaseId).value + cancelledLease.recipientAddress shouldBe dApp2Address + cancelledLease.amount.value shouldBe 1 + val lease2 = d.liquidSnapshot.newLeases.removed(cancelledLeaseId).head._2 + lease2.recipientAddress shouldBe dApp2Address + lease2.amount.value shouldBe 555 } } @@ -57,7 +57,8 @@ class SyncInvokeLeaseTest extends PropSpec with WithDomain { withDomain(RideV5, AddrWithBalance.enoughBalances(dApp1Signer)) { d => d.appendBlock(setScript(dApp1Signer, twoLeaseDApp(1))) d.appendAndAssertFailed(invoke(dApp1Address), "already in the state") - d.liquidDiff.leaseState shouldBe Map() + d.liquidSnapshot.newLeases should be(empty) + d.liquidSnapshot.cancelledLeases should be(empty) } } @@ -83,7 +84,8 @@ class SyncInvokeLeaseTest extends PropSpec with WithDomain { ) d.appendBlock(setScript(dApp1Signer, dApp)) d.appendAndAssertFailed(invoke(dApp1Address), "Cannot cancel already cancelled lease") - d.liquidDiff.leaseState shouldBe Map() + d.liquidSnapshot.newLeases should be(empty) + d.liquidSnapshot.cancelledLeases should be(empty) } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeTotalPaymentsTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeTotalPaymentsTest.scala index a3b0ae863d..58c6c383dd 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeTotalPaymentsTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncInvokeTotalPaymentsTest.scala @@ -75,7 +75,7 @@ class SyncInvokeTotalPaymentsTest extends PropSpec with WithDomain { if (error) if (fail) { d.appendAndAssertFailed(tx) - d.liquidDiff.errorMessage(tx.id()).get.text should include("Invoke payments limit = 100 is exceeded") + d.liquidSnapshot.errorMessage(tx.id()).get.text should include("Invoke payments limit = 100 is exceeded") } else d.appendBlockE(tx) should produce("Invoke payments limit = 100 is exceeded") else 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 7b0559ec41..bff3d5b6ff 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.affected shouldBe Set(dApp1Address, dApp2Address, dApp3Address, defaultAddress) + d.liquidSnapshot.transactions.head._2.affected shouldBe Set(dApp1Address, dApp2Address, dApp3Address, defaultAddress) } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/freecall/InvokeExpressionTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/freecall/InvokeExpressionTest.scala index c74dc25897..055f1ae760 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/freecall/InvokeExpressionTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/freecall/InvokeExpressionTest.scala @@ -13,7 +13,7 @@ import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit} import com.wavesplatform.state.diffs.ci.ciFee import com.wavesplatform.state.diffs.{ENOUGH_AMT, FeeValidation} -import com.wavesplatform.state.{BinaryDataEntry, BooleanDataEntry, NewAssetInfo} +import com.wavesplatform.state.{AssetInfo, AssetStaticInfo, AssetVolumeInfo, BinaryDataEntry, BooleanDataEntry} import com.wavesplatform.test.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.assets.{IssueTransaction, SponsorFeeTransaction} @@ -156,8 +156,8 @@ class InvokeExpressionTest extends PropSpec with ScalaCheckPropertyChecks with W d.appendBlock(invoke) d.blockchain.accountData(invoke.sender.toAddress, "check").get shouldBe BooleanDataEntry("check", true) d.blockchain.accountData(invoke.sender.toAddress, "transactionId").get shouldBe BinaryDataEntry("transactionId", invoke.txId) - d.liquidDiff.issuedAssets.size shouldBe 1 - checkAsset(invoke, d.liquidDiff.issuedAssets.head._2) + d.liquidSnapshot.assetStatics.size shouldBe 1 + checkAsset(invoke, d.liquidSnapshot.assetStatics.head._2, d.liquidSnapshot.assetNamesAndDescriptions.head._2, d.liquidSnapshot.assetVolumes.head._2) } } @@ -261,7 +261,7 @@ class InvokeExpressionTest extends PropSpec with ScalaCheckPropertyChecks with W } ignore("available versions") { // TODO check is commented in CommonValidation - val unsupportedVersion = InvokeExpressionTransaction.supportedVersions.max + 1 + val unsupportedVersion = 4 val (genesisTxs, invoke) = scenario(version = unsupportedVersion.toByte) withDomain(ContinuationTransaction) { d => d.appendBlock(genesisTxs*) @@ -324,21 +324,23 @@ class InvokeExpressionTest extends PropSpec with ScalaCheckPropertyChecks with W withDomain(ContinuationTransaction) { d => d.appendBlock(genesisTxs*) d.appendBlock(invoke) - d.liquidDiff.errorMessage(invoke.id.value()).get.text should include("Explicit script termination") + d.liquidSnapshot.errorMessage(invoke.id.value()).get.text should include("Explicit script termination") } } private[this] def checkAsset( invoke: InvokeExpressionTransaction, - asset: NewAssetInfo + static: AssetStaticInfo, + info: AssetInfo, + volume: AssetVolumeInfo ): Assertion = { - asset.dynamic.name.toStringUtf8 shouldBe TestAssetName - asset.dynamic.description.toStringUtf8 shouldBe TestAssetDesc - asset.volume.volume shouldBe TestAssetVolume - asset.volume.isReissuable shouldBe TestAssetReissuable - asset.static.decimals shouldBe TestAssetDecimals - asset.static.nft shouldBe false - asset.static.issuer shouldBe invoke.sender + info.name.toStringUtf8 shouldBe TestAssetName + info.description.toStringUtf8 shouldBe TestAssetDesc + volume.volume shouldBe TestAssetVolume + volume.isReissuable shouldBe TestAssetReissuable + static.decimals shouldBe TestAssetDecimals + static.nft shouldBe false + static.issuer shouldBe invoke.sender } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/package.scala b/node/src/test/scala/com/wavesplatform/state/diffs/package.scala index 2b4c14025d..cbb712e2a9 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/package.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/package.scala @@ -3,5 +3,5 @@ package com.wavesplatform.state package object diffs { val ENOUGH_AMT: Long = Long.MaxValue / 3 - def produceRejectOrFailedDiff(errorMessage: String, requireFailed: Boolean = false): DiffProduceError = new DiffProduceError(errorMessage, requireFailed) + def produceRejectOrFailedDiff(errorMessage: String, requireFailed: Boolean = false): SnapshotProduceError = new SnapshotProduceError(errorMessage, requireFailed) } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/DiffComplexityCountTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/DiffComplexityCountTest.scala index ef7d4936d5..e37b574963 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/DiffComplexityCountTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/DiffComplexityCountTest.scala @@ -21,7 +21,8 @@ class DiffComplexityCountTest extends PropSpec with Inside with WithState with D private val activationHeight = 4 - private val fsWithV5 = TestFunctionalitySettings.Enabled.copy(preActivatedFeatures = Map( + private val fsWithV5 = TestFunctionalitySettings.Enabled.copy( + preActivatedFeatures = Map( BlockchainFeatures.SmartAccounts.id -> 0, BlockchainFeatures.SmartAssets.id -> 0, BlockchainFeatures.Ride4DApps.id -> 0, @@ -30,7 +31,9 @@ class DiffComplexityCountTest extends PropSpec with Inside with WithState with D BlockchainFeatures.BlockReward.id -> 0, BlockchainFeatures.BlockV5.id -> 0, BlockchainFeatures.SynchronousCalls.id -> activationHeight - ), estimatorPreCheckHeight = Int.MaxValue) + ), + estimatorPreCheckHeight = Int.MaxValue + ) // ~1900 complexity val groth: String = @@ -57,23 +60,23 @@ class DiffComplexityCountTest extends PropSpec with Inside with WithState with D private def dApp(asset: IssuedAsset): Script = TestCompiler(V4).compileContract( s""" - | {-# STDLIB_VERSION 4 #-} - | {-# CONTENT_TYPE DAPP #-} - | {-# SCRIPT_TYPE ACCOUNT #-} - | - | @Callable(i) - | func default() = { - | strict cond = - | if (true) - | then true - | else ($groth) - | - | [ - | ScriptTransfer(i.caller, 1, base58'$asset'), - | Burn(base58'$asset', 1), - | Reissue(base58'$asset', 1, true) - | ] - | } + | {-# STDLIB_VERSION 4 #-} + | {-# CONTENT_TYPE DAPP #-} + | {-# SCRIPT_TYPE ACCOUNT #-} + | + | @Callable(i) + | func default() = { + | strict cond = + | if (true) + | then true + | else ($groth) + | + | [ + | ScriptTransfer(i.caller, 1, base58'$asset'), + | Burn(base58'$asset', 1), + | Reissue(base58'$asset', 1, true) + | ] + | } """.stripMargin ) @@ -95,15 +98,15 @@ class DiffComplexityCountTest extends PropSpec with Inside with WithState with D (balances, Seq(issue, transfer1, setVerifier, setDApp), invokeFromScripted) } - property(s"evaluated complexity is used for diff instead of estimated one after activation ${BlockchainFeatures.SynchronousCalls}") { + property(s"evaluated complexity is used instead of estimated one after activation ${BlockchainFeatures.SynchronousCalls}") { val (balances, preparingTxs, invoke) = paymentPreconditions withDomain(domainSettingsWithFS(fsWithV5), balances) { d => d.appendBlock(preparingTxs*) val invoke1 = invoke() d.appendBlock(invoke1) - d.liquidDiff.errorMessage(invoke1.id()) shouldBe empty - d.liquidDiff.scriptsComplexity shouldBe 11459 // 3 actions + 2 payments + verifier = 6 * 1900 = 11400 + d.liquidSnapshot.errorMessage(invoke1.id()) shouldBe empty + d.liquidSnapshot.scriptsComplexity shouldBe 11459 // 3 actions + 2 payments + verifier = 6 * 1900 = 11400 // for dApp evaluated complexity is always used after implementation of the snapshots d.appendBlock() @@ -111,8 +114,8 @@ class DiffComplexityCountTest extends PropSpec with Inside with WithState with D val invoke2 = invoke() d.appendBlock(invoke2) - d.liquidDiff.errorMessage(invoke2.id()) shouldBe empty - d.liquidDiff.scriptsComplexity shouldBe 17 + d.liquidSnapshot.errorMessage(invoke2.id()) shouldBe empty + d.liquidSnapshot.scriptsComplexity shouldBe 17 } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/EstimationSwitchTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/EstimationSwitchTest.scala index ed6e94e50c..3b2ee6609c 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/EstimationSwitchTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/EstimationSwitchTest.scala @@ -45,13 +45,13 @@ class EstimationSwitchTest extends PropSpec with WithDomain with TransactionGenB d.appendBlock(genesis1, genesis2) d.appendBlock(setScript(), invoke()) - d.liquidDiff.scripts.head._2.get.complexitiesByEstimator(3)("default") shouldBe 5 - d.liquidDiff.scriptsComplexity shouldBe 7 + d.liquidSnapshot.accountScripts.head._2.get.complexitiesByEstimator(3)("default") shouldBe 5 + d.liquidSnapshot.scriptsComplexity shouldBe 7 // bigger than estimator because of ignoring predefined user function complexities d.appendBlock(setScript(), invoke()) - d.liquidDiff.scripts.head._2.get.complexitiesByEstimator(3)("default") shouldBe 1 - d.liquidDiff.scriptsComplexity shouldBe 1 + d.liquidSnapshot.accountScripts.head._2.get.complexitiesByEstimator(3)("default") shouldBe 1 + d.liquidSnapshot.scriptsComplexity shouldBe 1 // condition decreased by 1, // accessing to ref ([] = nil) decreased by 1, // != decreased by 4 (because of using predefined user function complexities) diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/MaxCallableComplexityTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/MaxCallableComplexityTest.scala index 4af4a6af51..025bcd8807 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/MaxCallableComplexityTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/MaxCallableComplexityTest.scala @@ -41,8 +41,8 @@ class MaxCallableComplexityTest extends PropSpec with WithDomain with Transactio val setLargeScript = SetScriptTransaction.selfSigned(TxVersion.V2, dApp, Some(largeScript(V6, 300)), 0.022.waves, ts).explicitGet() d.appendBlock(genDApp, genInvoker, setScript) - val invokeDiff = d.transactionDiffer(invokeScript(invoker, dApp.toAddress, "test")).resultE.explicitGet() - invokeDiff.scriptsComplexity shouldBe 51585 + val invokeSnapshot = d.transactionDiffer(invokeScript(invoker, dApp.toAddress, "test")).resultE.explicitGet() + invokeSnapshot.scriptsComplexity shouldBe 51585 d.appendAndCatchError(setLargeScript).toString should include("Contract function (test) is too complex: 54301 > 52000") } } @@ -61,8 +61,8 @@ class MaxCallableComplexityTest extends PropSpec with WithDomain with Transactio val setInvokeScript = SetScriptTransaction.selfSigned(TxVersion.V2, invokeDApp, Some(invokeScript(V5, largeDApp.toAddress)), 0.01.waves, ts).explicitGet() d.appendBlock(genInvoker, genLargeDApp, genInvokeDApp, setLargeScript, setInvokeScript) - val invokeDiff = d.transactionDiffer(invokeScript(invoker, invokeDApp.toAddress, "invokeTest")).resultE.explicitGet() - invokeDiff.scriptsComplexity shouldBe 18177 + val invokeSnapshot = d.transactionDiffer(invokeScript(invoker, invokeDApp.toAddress, "invokeTest")).resultE.explicitGet() + invokeSnapshot.scriptsComplexity shouldBe 18177 } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/RideExceptionsTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/RideExceptionsTest.scala new file mode 100644 index 0000000000..18c9497a19 --- /dev/null +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/RideExceptionsTest.scala @@ -0,0 +1,172 @@ +package com.wavesplatform.state.diffs.smart + +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.db.WithDomain +import com.wavesplatform.db.WithState.AddrWithBalance +import com.wavesplatform.features.BlockchainFeatures.LightNode +import com.wavesplatform.lang.contract.DApp +import com.wavesplatform.lang.contract.DApp.{CallableAnnotation, CallableFunction} +import com.wavesplatform.lang.directives.values.V7 +import com.wavesplatform.lang.script.ContractScript.ContractScriptImpl +import com.wavesplatform.lang.script.v1.ExprScript.ExprScriptImpl +import com.wavesplatform.lang.v1.FunctionHeader.{Native, User} +import com.wavesplatform.lang.v1.compiler.Terms.* +import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.lang.v1.evaluator.FunctionIds.* +import com.wavesplatform.protobuf.dapp.DAppMeta +import com.wavesplatform.test.DomainPresets.{TransactionStateSnapshot, WavesSettingsOps} +import com.wavesplatform.test.{PropSpec, produce} +import com.wavesplatform.transaction.TxHelpers.* + +class RideExceptionsTest extends PropSpec with WithDomain { + property("throwing java exception from ride functions should correctly fail or reject invoke after light node activation") { + assert( + FUNCTION_CALL(Native(ACCOUNTWAVESBALANCE), List(REF("unit"))), + "Unexpected recipient type Unit", + rejectBefore = false + ) + assert( + FUNCTION_CALL(Native(ACCOUNTASSETONLYBALANCE), List(REF("unit"), CONST_BYTESTR(ByteStr.empty).explicitGet())), + "Unexpected recipient type Unit", + rejectBefore = false + ) + assert( + FUNCTION_CALL(Native(ACCOUNTSCRIPTHASH), List(REF("unit"))), + "Unexpected recipient type Unit", + rejectBefore = false + ) + assert( + FUNCTION_CALL(Native(CALCULATE_LEASE_ID), List(FUNCTION_CALL(User("Lease"), List(REF("unit"))))), + "Unexpected recipient type Unit", + rejectBefore = false + ) + assert( + FUNCTION_CALL(Native(CALLDAPP), List(CONST_LONG(1), CONST_LONG(1), CONST_LONG(1), CONST_LONG(1))), + "Unexpected recipient arg", + rejectBefore = true, + checkVerifier = false + ) + assert( + FUNCTION_CALL(Native(CALLDAPP), List(FUNCTION_CALL(User("Address"), List(CONST_LONG(1))), CONST_LONG(1), CONST_LONG(1), CONST_LONG(1))), + "Unexpected address bytes", + rejectBefore = true, + checkVerifier = false + ) + assert( + FUNCTION_CALL( + Native(CALLDAPP), + List(FUNCTION_CALL(User("Alias"), List(CONST_STRING("alias").explicitGet())), CONST_LONG(1), CONST_LONG(1), CONST_LONG(1)) + ), + "Alias 'alias:T:alias' does not exist", + rejectBefore = true, + checkVerifier = false + ) + assert( + FUNCTION_CALL( + Native(CALLDAPP), + List(FUNCTION_CALL(User("Alias"), List(CONST_LONG(1))), CONST_LONG(1), CONST_LONG(1), CONST_LONG(1)) + ), + "Unexpected alias arg", + rejectBefore = true, + checkVerifier = false + ) + assert( + FUNCTION_CALL( + Native(CALLDAPP), + List(FUNCTION_CALL(User("Address"), List(CONST_BYTESTR(ByteStr.empty).explicitGet())), CONST_LONG(1), CONST_LONG(1), CONST_LONG(1)) + ), + "Unexpected name arg", + rejectBefore = true, + checkVerifier = false + ) + assert( + FUNCTION_CALL( + Native(CALLDAPP), + List( + FUNCTION_CALL(User("Address"), List(CONST_BYTESTR(ByteStr.empty).explicitGet())), + REF("unit"), + CONST_LONG(1), + ARR(Vector(CONST_LONG(1)), limited = false).explicitGet() + ) + ), + "Unexpected payment arg", + rejectBefore = true, + checkVerifier = false + ) + assert( + FUNCTION_CALL( + Native(CREATE_MERKLE_ROOT), + List( + ARR(Vector.fill(16)(CONST_BYTESTR(ByteStr.fill(32)(1)).explicitGet()), true).explicitGet(), + CONST_BYTESTR(ByteStr.fill(32)(1)).explicitGet(), + CONST_LONG(Long.MaxValue) + ) + ), + "integer overflow", + rejectBefore = false + ) + assert( + FUNCTION_CALL( + Native(CREATE_MERKLE_ROOT), + List( + ARR(Vector.fill(1)(CONST_BYTESTR(ByteStr.fill(32)(1)).explicitGet()), true).explicitGet(), + CONST_BYTESTR(ByteStr.fill(32)(1)).explicitGet(), + CONST_LONG(100) + ) + ), + "Index 100 out of range allowed by proof list length 1", + rejectBefore = false + ) + } + + private def assert(expr: EXPR, error: String, rejectBefore: Boolean, checkVerifier: Boolean = true) = + withDomain( + TransactionStateSnapshot.setFeaturesHeight(LightNode -> 7), + AddrWithBalance.enoughBalances(defaultSigner, secondSigner, signer(2)) + ) { d => + val func = FUNC("default", Nil, expr) + val dApp = DApp(DAppMeta(), Nil, List(CallableFunction(CallableAnnotation("i"), func)), None) + d.appendBlock(setScript(secondSigner, ContractScriptImpl(V7, dApp))) + + // dApp before activation + if (rejectBefore) { + d.appendBlockE(invoke()) should produce(error) + d.appendBlock() + } else + d.appendAndAssertFailed(invoke(), error) + + // dApp before activation with enough complexity to fail + val complexCond = TestCompiler(V7).compileExpression(s"${(1 to 6).map(_ => "sigVerify(base58'', base58'', base58'')").mkString(" || ")}") + val complexExpr = IF(complexCond.expr, TRUE, expr) + val complexFunc = FUNC("default", Nil, complexExpr) + val complexDApp = DApp(DAppMeta(), Nil, List(CallableFunction(CallableAnnotation("i"), complexFunc)), None) + d.appendBlock(setScript(secondSigner, ContractScriptImpl(V7, complexDApp))) + if (rejectBefore) { + d.appendBlockE(invoke()) should produce(error) + d.appendBlock() + } else + d.appendAndAssertFailed(invoke(), error) + + // verifier before activation + if (checkVerifier) { + d.appendBlock(setScript(signer(2), ExprScriptImpl(V7, false, complexExpr))) + d.appendBlockE(transfer(signer(2), defaultAddress)) should produce(error) + } else + d.appendBlock() + + // dApp after activation + d.blockchain.isFeatureActivated(LightNode) shouldBe false + d.appendBlock(setScript(secondSigner, ContractScriptImpl(V7, dApp))) + d.blockchain.isFeatureActivated(LightNode) shouldBe true + d.appendBlockE(invoke()) should produce(error) + + // dApp after activation with enough complexity to fail + d.appendBlock(setScript(secondSigner, ContractScriptImpl(V7, complexDApp))) + d.appendAndAssertFailed(invoke(), error) + + // verifier after activation + if (checkVerifier) + d.appendBlockE(transfer(signer(2), defaultAddress)) should produce(error) + } +} diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/SmartAccountFeeTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/SmartAccountFeeTest.scala index df1a4d5b85..6aeebc0de3 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/SmartAccountFeeTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/SmartAccountFeeTest.scala @@ -136,8 +136,8 @@ class SmartAccountFeeTest extends PropSpec with WithDomain { notEnoughPaidVerifierTxs.foreach(tx => appendAndAssertNotEnoughFee(tx(), d)) d.appendAndAssertSucceed(freeVerifierTxs.map(_())*) - d.liquidDiff.scriptsComplexity should be > 0L - d.liquidDiff.scriptResults.size shouldBe 2 + d.liquidSnapshot.scriptsComplexity should be > 0L + d.liquidSnapshot.scriptResults.size shouldBe 2 } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/SmartAssetEvalTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/SmartAssetEvalTest.scala index 4fa3b29568..2c3cea3ec0 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/SmartAssetEvalTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/SmartAssetEvalTest.scala @@ -31,7 +31,7 @@ class SmartAssetEvalTest extends PropSpec with WithState { """.stripMargin val parsedEmptyScript = Parser.parseExpr(emptyScript).get.value val emptyExprScript = - ExprScript(V3, ExpressionCompiler(compilerContext(V3, Expression, isAssetScript = true), parsedEmptyScript).explicitGet()._1) + ExprScript(V3, ExpressionCompiler(compilerContext(V3, Expression, isAssetScript = true), V3, parsedEmptyScript).explicitGet()._1) .explicitGet() val issue = TxHelpers.issue(firstAcc, 100, script = Some(emptyExprScript), reissuable = false) val asset = IssuedAsset(issue.id()) @@ -50,7 +50,7 @@ class SmartAssetEvalTest extends PropSpec with WithState { | """.stripMargin val untypedScript = Parser.parseExpr(assetScript).get.value - val typedScript = ExprScript(V3, ExpressionCompiler(compilerContext(V3, Expression, isAssetScript = true), untypedScript).explicitGet()._1) + val typedScript = ExprScript(V3, ExpressionCompiler(compilerContext(V3, Expression, isAssetScript = true), V3, untypedScript).explicitGet()._1) .explicitGet() val setAssetScript = TxHelpers.setAssetScript(firstAcc, asset, typedScript) diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumInvokeTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumInvokeTest.scala index 482414a7f5..ca0ce7c2cb 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumInvokeTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumInvokeTest.scala @@ -13,7 +13,6 @@ import com.wavesplatform.lang.script.v1.ExprScript import com.wavesplatform.lang.v1.compiler.{Terms, TestCompiler} import com.wavesplatform.lang.v1.evaluator.ctx.impl.GlobalValNames import com.wavesplatform.lang.v1.traits.domain.AttachedPayments.* -import com.wavesplatform.state.Portfolio import com.wavesplatform.state.diffs.ENOUGH_AMT import com.wavesplatform.state.diffs.ci.ciFee import com.wavesplatform.state.diffs.smart.predef.{assertProvenPart, provenPart} @@ -150,14 +149,19 @@ class EthereumInvokeTest extends PropSpec with WithDomain with EthHelpers with I d.appendBlock(preparingTxs*) d.appendBlock(ethInvoke) - d.liquidDiff.errorMessage(ethInvoke.id()) shouldBe None - d.liquidDiff.accountData(dApp)("check").value shouldBe true - if (syncCall) d.liquidDiff.accountData(dApp2)("check").value shouldBe true + d.liquidSnapshot.errorMessage(ethInvoke.id()) shouldBe None + d.liquidSnapshot.accountData(dApp)("check").value shouldBe true + if (syncCall) d.liquidSnapshot.accountData(dApp2)("check").value shouldBe true - val assetsPortfolio = assets.map(Portfolio.build(_, paymentAmount)).fold(Portfolio())((p1, p2) => p1.combine(p2).explicitGet()) - d.liquidDiff.portfolios.getOrElse(dApp, Portfolio()) shouldBe assetsPortfolio - d.liquidDiff.portfolios(ethInvoke.senderAddress()) shouldBe Portfolio(-ethInvoke.underlying.getGasPrice.longValue()).minus(assetsPortfolio) - inside(d.liquidDiff.scriptResults.toSeq) { case Seq((_, call1)) => + val ethSender = ethInvoke.senderAddress() + assets.foreach { asset => + d.liquidSnapshot.balances((dApp, asset)) shouldBe d.rocksDBWriter.balance(dApp, asset) + paymentAmount + d.liquidSnapshot.balances((ethSender, asset)) shouldBe d.rocksDBWriter.balance(ethSender, asset) - paymentAmount + } + d.liquidSnapshot.balances.get((dApp, Waves)) shouldBe None + d.liquidSnapshot.balances((ethSender, Waves)) shouldBe d.rocksDBWriter.balance(ethSender) - ethInvoke.underlying.getGasPrice.longValue() + + inside(d.liquidSnapshot.scriptResults.toSeq) { case Seq((_, call1)) => if (syncCall) inside(call1.invokes) { case Seq(call2) => call2.stateChanges.error shouldBe empty diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumTransferSmartTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumTransferSmartTest.scala index 42e07a2247..1964204c44 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumTransferSmartTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/eth/EthereumTransferSmartTest.scala @@ -10,7 +10,6 @@ import com.wavesplatform.lang.directives.DirectiveDictionary import com.wavesplatform.lang.directives.values.{StdLibVersion, V3, V6} import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.lang.v1.evaluator.ctx.impl.GlobalValNames -import com.wavesplatform.state.Portfolio import com.wavesplatform.state.diffs.ENOUGH_AMT import com.wavesplatform.state.diffs.smart.predef.{assertProvenPart, provenPart} import com.wavesplatform.test.* @@ -21,7 +20,6 @@ import com.wavesplatform.transaction.transfer.TransferTransaction import com.wavesplatform.transaction.{Asset, ERC20Address, EthTxGenerator, EthereumTransaction, TxHelpers} import com.wavesplatform.utils.EthHelpers -import scala.collection.immutable.VectorMap import scala.math.Ordering.Implicits.infixOrderingOps class EthereumTransferSmartTest extends PropSpec with WithDomain with EthHelpers { @@ -95,15 +93,21 @@ class EthereumTransferSmartTest extends PropSpec with WithDomain with EthHelpers d.appendBlock(setVerifier()) d.appendBlock(ProtoBlockVersion, ethTransfer) - val transferPortfolio = Portfolio.build(asset, transferAmount) - d.liquidDiff.portfolios(recipient.toAddress) shouldBe transferPortfolio - d.liquidDiff.portfolios(ethSender) shouldBe Portfolio.waves(-ethTransfer.underlying.getGasLimit.longValue()).minus(transferPortfolio) + d.liquidSnapshot.balances((recipient.toAddress, asset)) shouldBe d.rocksDBWriter.balance(recipient.toAddress, asset) + transferAmount + if (asset == Waves) + d.liquidSnapshot.balances((ethSender, Waves)) shouldBe + d.rocksDBWriter.balance(ethSender, Waves) - ethTransfer.underlying.getGasLimit.longValue() - transferAmount + else { + d.liquidSnapshot.balances((ethSender, Waves)) shouldBe + d.rocksDBWriter.balance(ethSender, Waves) - ethTransfer.underlying.getGasLimit.longValue() + d.liquidSnapshot.balances((ethSender, asset)) shouldBe d.rocksDBWriter.balance(ethSender, asset) - transferAmount + } d.appendBlock() if (version >= V6) { d.appendBlock(setVerifier()) // just for account script execution - d.liquidDiff.scriptsComplexity should be > 0L + d.liquidSnapshot.scriptsComplexity should be > 0L } else if (version >= V3) { (the[Exception] thrownBy d.appendBlock(setVerifier())).getMessage should include( "value() called on unit value on function 'transferTransactionById' call" @@ -137,14 +141,14 @@ class EthereumTransferSmartTest extends PropSpec with WithDomain with EthHelpers d.appendBlock(issue, preTransfer) d.appendBlock(ProtoBlockVersion, ethTransfer) - d.liquidDiff.errorMessage(ethTransfer.id()) shouldBe None - d.liquidDiff.portfolios(recipient.toAddress) shouldBe Portfolio.build(asset, transferAmount) - d.liquidDiff.portfolios(ethTransfer.senderAddress()) shouldBe Portfolio( - -ethTransfer.underlying.getGasPrice.longValue(), - assets = VectorMap(asset -> -transferAmount) - ) + d.liquidSnapshot.errorMessage(ethTransfer.id()) shouldBe None + d.liquidSnapshot.scriptsComplexity should be > 0L - d.liquidDiff.scriptsComplexity should be > 0L + d.liquidSnapshot.balances((recipient.toAddress, asset)) shouldBe transferAmount + d.liquidSnapshot.balances((ethTransfer.senderAddress(), Waves)) shouldBe + d.rocksDBWriter.balance(ethTransfer.senderAddress(), Waves) - ethTransfer.underlying.getGasPrice.longValue() + d.liquidSnapshot.balances((ethTransfer.senderAddress(), asset)) shouldBe + d.rocksDBWriter.balance(ethTransfer.senderAddress(), asset) - transferAmount } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/performance/SigVerifyPerformanceTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/performance/SigVerifyPerformanceTest.scala index 1d72d4f469..72a48d31fc 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/performance/SigVerifyPerformanceTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/performance/SigVerifyPerformanceTest.scala @@ -34,7 +34,7 @@ class SigVerifyPerformanceTest extends PropSpec with WithState { ignore("parallel native signature verification vs sequential scripted signature verification") { val textScript = "sigVerify(tx.bodyBytes,tx.proofs[0],tx.senderPk)" val untypedScript = Parser.parseExpr(textScript).get.value - val typedScript = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), untypedScript).explicitGet()._1 + val typedScript = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), V1, untypedScript).explicitGet()._1 val (gen, setScript, transfers, scriptTransfers) = differentTransfers(typedScript) diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/CalculateDelayTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/CalculateDelayTest.scala index c9df847c92..af47ac69af 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/CalculateDelayTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/CalculateDelayTest.scala @@ -50,7 +50,7 @@ class CalculateDelayTest extends PropSpec with WithDomain { val minDelays = (1 to 200).map { _ => d.appendBlock(invoke()) - d.liquidDiff.accountData(secondAddress).values.minBy(_.value.asInstanceOf[Long]).key + d.liquidSnapshot.accountData(secondAddress).values.minBy(_.value.asInstanceOf[Long]).key } val lowestIsMiner = minDelays.count(_ == "lowest") val mediumIsMiner = minDelays.count(_ == "medium") diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/CommonFunctionsTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/CommonFunctionsTest.scala index 846ea80cec..7c52bf51ab 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/CommonFunctionsTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/CommonFunctionsTest.scala @@ -6,13 +6,11 @@ import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.Testing.* import com.wavesplatform.lang.v1.compiler.Terms.CONST_BYTESTR import com.wavesplatform.lang.v1.evaluator.ctx.impl.* -import com.wavesplatform.test.* import com.wavesplatform.state.IntegerDataEntry -import com.wavesplatform.test.{NumericExt, PropSpec} +import com.wavesplatform.test.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.transfer.MassTransferTransaction -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer -import com.wavesplatform.transaction.{TxHelpers, TxNonNegativeAmount, TxVersion} +import com.wavesplatform.transaction.{TxHelpers, TxVersion} import org.scalatest.Assertions import shapeless.Coproduct @@ -43,11 +41,11 @@ class CommonFunctionsTest extends PropSpec { val result = runScript( """ - |match tx { - | case ttx : TransferTransaction => isDefined(ttx.assetId) - | case _ => throw() - | } - |""".stripMargin, + |match tx { + | case ttx : TransferTransaction => isDefined(ttx.assetId) + | case _ => throw() + | } + |""".stripMargin, Coproduct(transfer) ) result shouldEqual evaluated(transfer.assetId != Waves) @@ -85,25 +83,25 @@ class CommonFunctionsTest extends PropSpec { resultAmount shouldBe evaluated(massTransfer.transfers(0).amount.value) val resultAddress = runScript( """ - |match tx { - | case mttx : MassTransferTransaction => - | match mttx.transfers[0].recipient { - | case address : Address => address.bytes - | case _ => throw() - | } - | case _ => throw() - | } - |""".stripMargin, + |match tx { + | case mttx : MassTransferTransaction => + | match mttx.transfers[0].recipient { + | case address : Address => address.bytes + | case _ => throw() + | } + | case _ => throw() + | } + |""".stripMargin, Coproduct(massTransfer) ) resultAddress shouldBe evaluated(ByteStr(massTransfer.transfers(0).address.bytes)) val resultLen = runScript( """ - |match tx { - | case mttx : MassTransferTransaction => size(mttx.transfers) - | case _ => throw() - | } - |""".stripMargin, + |match tx { + | case mttx : MassTransferTransaction => size(mttx.transfers) + | case _ => throw() + | } + |""".stripMargin, Coproduct(massTransfer) ) resultLen shouldBe evaluated(massTransfer.transfers.size.toLong) @@ -206,14 +204,14 @@ class CommonFunctionsTest extends PropSpec { property("shadowing of variable considered external") { runScript( s""" - |match { - | let aaa = 1 - | tx - |} { - | case tx: TransferTransaction => tx == tx - | case _ => throw() - | } - |""".stripMargin + |match { + | let aaa = 1 + | tx + |} { + | case tx: TransferTransaction => tx == tx + | case _ => throw() + | } + |""".stripMargin ) should produce("already defined") } @@ -299,7 +297,7 @@ class CommonFunctionsTest extends PropSpec { val recipients = (1 to 10).map(idx => TxHelpers.address(idx + 1)) TxHelpers.massTransfer( from = sender, - to = recipients.map(addr => ParsedTransfer(addr, TxNonNegativeAmount.unsafeFrom(1.waves))), + to = recipients.map(addr => (addr, 1.waves)), version = TxVersion.V1 ) } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/MatcherBlockchainTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/MatcherBlockchainTest.scala index 96a8a45c23..151d899a55 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/MatcherBlockchainTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/MatcherBlockchainTest.scala @@ -13,7 +13,6 @@ import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.lang.v1.traits.domain.Recipient import com.wavesplatform.settings.BlockchainSettings import com.wavesplatform.state.* -import com.wavesplatform.state.reader.LeaseDetails import com.wavesplatform.test.PropSpec import com.wavesplatform.transaction.Asset.Waves import com.wavesplatform.transaction.smart.script.ScriptRunner @@ -102,7 +101,8 @@ class MatcherBlockchainTest extends PropSpec with MockFactory with WithDomain { newEvaluatorMode = true, checkWeakPk = true, enableExecutionLog = false, - fixBigScriptField = true + fixBigScriptField = true, + fixedThrownError = true ) ._3 shouldBe Right(CONST_BOOLEAN(true)) } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ObsoleteTransactionBindingsTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ObsoleteTransactionBindingsTest.scala index f34cf5f73d..dd3690471a 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ObsoleteTransactionBindingsTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ObsoleteTransactionBindingsTest.scala @@ -74,7 +74,7 @@ class ObsoleteTransactionBindingsTest extends PropSpec with WithState { recipients.map { recipient => val payment = TxHelpers.payment(master, recipient.toAddress, ENOUGH_AMT * 2) val untypedScript = Parser.parseExpr(script(genesis, payment)).get.value - val typedScript = ExprScript(ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), untypedScript).explicitGet()._1) + val typedScript = ExprScript(ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), V1, untypedScript).explicitGet()._1) .explicitGet() val setScriptTransaction = TxHelpers.setScript(recipient, typedScript) val nextTransfer = TxHelpers.transfer(recipient, master.toAddress) diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/RideCreateMerkleRootTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/RideCreateMerkleRootTest.scala index 98b1f992c8..1288d0837a 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/RideCreateMerkleRootTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/RideCreateMerkleRootTest.scala @@ -42,7 +42,7 @@ class RideCreateMerkleRootTest extends PropSpec with WithDomain { val invokeTx = invoke(func = Some("merkle"), args = Seq(digests, id, index)) d.appendBlock(d.createBlock(blockVersion, Seq(invokeTx))) - d.liquidDiff.scriptResults.head._2.error shouldBe None + d.liquidSnapshot.scriptResults.head._2.error shouldBe None d.blockchain.accountData(secondAddress, "root").get.value shouldBe root } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ScriptVersionsTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ScriptVersionsTest.scala index 0b52b3541c..445763153b 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ScriptVersionsTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/ScriptVersionsTest.scala @@ -30,7 +30,7 @@ class ScriptVersionsTest extends FreeSpec { ): Either[String, EVALUATED] = { val expr = Parser.parseExpr(script).get.value for { - compileResult <- ExpressionCompiler(compilerContext(version, Expression, isAssetScript = false), expr) + compileResult <- ExpressionCompiler(compilerContext(version, Expression, isAssetScript = false), version, expr) (typedExpr, _) = compileResult s <- ExprScript(version, typedExpr, checkSize = false) r <- eval(s, tx, blockchain) diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/SerContextFunctionsTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/SerContextFunctionsTest.scala index e905d11339..2799e788f6 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/SerContextFunctionsTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/SerContextFunctionsTest.scala @@ -49,7 +49,7 @@ class SerContextFunctionsTest extends PropSpec { ) val untypedScript = Parser.parseExpr(scriptWithAllV1Functions(dtx, ttx)).get.value - val compiledScript = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), untypedScript).explicitGet()._1 + val compiledScript = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), V1, untypedScript).explicitGet()._1 val bytes = Base64.decode("BAAAAANybmQJAAAAAAAAAgkAAGoAAAACCAUAAAACdHgAAAAJdGltZXN0YW1wAAAAAAAAAAACAAAAAAAAAAAABAAAAAdsb25nQWxsAwMDAwkAAAAAAAACCQAAaAAAAAIAAAAAAAAAA+gAAAAAAAAAAAIAAAAAAAAAB9AJAAAAAAAAAgkAAGkAAAACAAAAAAAAAAPoAAAAAAAAAAACAAAAAAAAAAH0BwkAAAAAAAACCQAAagAAAAIAAAAAAAAAA+gAAAAAAAAAAAIAAAAAAAAAAAAHCQAAAAAAAAIJAABkAAAAAgAAAAAAAAAD6AAAAAAAAAAAAgAAAAAAAAAD6gcJAAAAAAAAAgkAAGUAAAACAAAAAAAAAAPoAAAAAAAAAAACAAAAAAAAAAPmBwQAAAAJc3VtU3RyaW5nCQAAAAAAAAIJAAEsAAAAAgkAASwAAAACAgAAAAJoYQIAAAABLQIAAAACaGECAAAABWhhLWhhBAAAAA1zdW1CeXRlVmVjdG9yBAAAAAckbWF0Y2gwBQAAAAJ0eAMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAAPRGF0YVRyYW5zYWN0aW9uBAAAAAJkMAUAAAAHJG1hdGNoMAQAAAAEYm9keQgFAAAAAmQwAAAACWJvZHlCeXRlcwkAAAAAAAACCQAAywAAAAIFAAAABGJvZHkBAAAAZAwB1SiqvsNcoQDYfHt6EoYy+vGc1EUxgZRXRFEToyoh7yIABAADaW50AAAAAAAAAAAYAARib29sAQEABGJsb2ICAAVhbGljZQADc3RyAwAEdGVzdAAAAWODBPoKAAAAAAABhqAJAADLAAAAAgEAAABkDAHVKKq+w1yhANh8e3oShjL68ZzURTGBlFdEUROjKiHvIgAEAANpbnQAAAAAAAAAABgABGJvb2wBAQAEYmxvYgIABWFsaWNlAANzdHIDAAR0ZXN0AAABY4ME+goAAAAAAAGGoAEAAABkDAHVKKq+w1yhANh8e3oShjL68ZzURTGBlFdEUROjKiHvIgAEAANpbnQAAAAAAAAAABgABGJvb2wBAQAEYmxvYgIABWFsaWNlAANzdHIDAAR0ZXN0AAABY4ME+goAAAAAAAGGoAMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAATVHJhbnNmZXJUcmFuc2FjdGlvbgYHBAAAAAdlcVVuaW9uBAAAAAckbWF0Y2gwBQAAAAJ0eAMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAAPRGF0YVRyYW5zYWN0aW9uBgMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAATVHJhbnNmZXJUcmFuc2FjdGlvbgQAAAACdDAFAAAAByRtYXRjaDAJAAAAAAAAAggFAAAAAnQwAAAACXJlY2lwaWVudAkBAAAAB0FkZHJlc3MAAAABAQAAABoBVGQXjd+A7J8+39ZW6Opokqso6ed67rqcnQcEAAAABWJhc2ljAwMDBQAAAAdsb25nQWxsBQAAAAlzdW1TdHJpbmcHBQAAAA1zdW1CeXRlVmVjdG9yBwUAAAAHZXFVbmlvbgcEAAAABm5lUHJpbQMDCQEAAAACIT0AAAACAAAAAAAAAAPoAAAAAAAAAAPnCQEAAAACIT0AAAACCQABLAAAAAICAAAAAmhhAgAAAAJoYQIAAAAFaGEtaGEHCQEAAAACIT0AAAACCAUAAAACdHgAAAAJYm9keUJ5dGVzAQAAAASFqFqFBwQAAAAYbmVEYXRhRW50cnlBbmRHZXRFbGVtZW50BAAAAAckbWF0Y2gwBQAAAAJ0eAMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAAPRGF0YVRyYW5zYWN0aW9uBAAAAAJkMQUAAAAHJG1hdGNoMAkBAAAAAiE9AAAAAgkAAZEAAAACCAUAAAACZDEAAAAEZGF0YQAAAAAAAAAAAAkBAAAACURhdGFFbnRyeQAAAAICAAAAAmhhBgMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAATVHJhbnNmZXJUcmFuc2FjdGlvbgYHBAAAABhuZU9wdGlvbkFuZEV4dHJhY3RIZWlnaHQEAAAAByRtYXRjaDAFAAAAAnR4AwkAAAEAAAACBQAAAAckbWF0Y2gwAgAAAA9EYXRhVHJhbnNhY3Rpb24GAwkAAAEAAAACBQAAAAckbWF0Y2gwAgAAABNUcmFuc2ZlclRyYW5zYWN0aW9uCQEAAAACIT0AAAACCQEAAAAHZXh0cmFjdAAAAAEJAAPpAAAAAQgFAAAAAnR4AAAAAmlkAAAAAAAAAAAABwQAAAACbmUDAwUAAAAGbmVQcmltBQAAABhuZURhdGFFbnRyeUFuZEdldEVsZW1lbnQHBQAAABhuZU9wdGlvbkFuZEV4dHJhY3RIZWlnaHQHBAAAAAdndGVMb25nAwkAAGYAAAACAAAAAAAAAAPoAAAAAAAAAAPnCQAAZwAAAAIAAAAAAAAAA+gAAAAAAAAAA+cHBAAAAAtnZXRMaXN0U2l6ZQQAAAAHJG1hdGNoMAUAAAACdHgDCQAAAQAAAAIFAAAAByRtYXRjaDACAAAAD0RhdGFUcmFuc2FjdGlvbgQAAAACZDIFAAAAByRtYXRjaDAJAQAAAAIhPQAAAAIJAAGQAAAAAQgFAAAAAmQyAAAABGRhdGEAAAAAAAAAAAADCQAAAQAAAAIFAAAAByRtYXRjaDACAAAAE1RyYW5zZmVyVHJhbnNhY3Rpb24GBwQAAAAFdW5hcnkDCQAAAAAAAAIA//////////8A//////////8JAAAAAAAAAgcJAQAAAAEhAAAAAQYHBAAAAAhmckFjdGlvbgkAAAAAAAACCQAAawAAAAMAAAAAAAAAAAwAAAAAAAAAAAMAAAAAAAAAAAQAAAAAAAAAAAkEAAAACGJ5dGVzT3BzBAAAAAckbWF0Y2gwBQAAAAJ0eAMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAAPRGF0YVRyYW5zYWN0aW9uBAAAAAJkMwUAAAAHJG1hdGNoMAMDAwMJAQAAAAIhPQAAAAIJAADIAAAAAQgFAAAAAmQzAAAACWJvZHlCeXRlcwAAAAAAAAAAAAkBAAAAAiE9AAAAAgkAAMkAAAACCAUAAAACZDMAAAAJYm9keUJ5dGVzAAAAAAAAAAABAQAAAAIJMQcJAQAAAAIhPQAAAAIJAADKAAAAAggFAAAAAmQzAAAACWJvZHlCeXRlcwAAAAAAAAAAAQEAAAACCTEHCQEAAAACIT0AAAACCQEAAAAOdGFrZVJpZ2h0Qnl0ZXMAAAACCAUAAAACZDMAAAAJYm9keUJ5dGVzAAAAAAAAAAABAQAAAAIJMQcJAQAAAAIhPQAAAAIJAQAAAA5kcm9wUmlnaHRCeXRlcwAAAAIIBQAAAAJkMwAAAAlib2R5Qnl0ZXMAAAAAAAAAAAEBAAAAAgkxBwMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAATVHJhbnNmZXJUcmFuc2FjdGlvbgQAAAACdDEFAAAAByRtYXRjaDAJAAAAAAAAAgkBAAAACWlzRGVmaW5lZAAAAAEIBQAAAAJ0MQAAAApmZWVBc3NldElkBwcEAAAABnN0ck9wcwMDAwMJAQAAAAIhPQAAAAIJAAExAAAAAQIAAAAEaGFoYQAAAAAAAAAAAAkBAAAAAiE9AAAAAgkAAS8AAAACAgAAAARoYWhhAAAAAAAAAAABAgAAAAAHCQEAAAACIT0AAAACCQABMAAAAAICAAAABGhhaGEAAAAAAAAAAAACAAAAAAcJAQAAAAIhPQAAAAIJAQAAAAl0YWtlUmlnaHQAAAACAgAAAARoYWhhAAAAAAAAAAABAgAAAAAHCQEAAAACIT0AAAACCQEAAAAJZHJvcFJpZ2h0AAAAAgIAAAAEaGFoYQAAAAAAAAAAAAIAAAAABwQAAAAEcHVyZQMDAwMDAwMFAAAABWJhc2ljBQAAAAJuZQcFAAAAB2d0ZUxvbmcHBQAAAAtnZXRMaXN0U2l6ZQcFAAAABXVuYXJ5BwUAAAAIZnJBY3Rpb24HBQAAAAhieXRlc09wcwcFAAAABnN0ck9wcwcEAAAABnR4QnlJZAQAAAAHJG1hdGNoMAUAAAACdHgDCQAAAQAAAAIFAAAAByRtYXRjaDACAAAAD0RhdGFUcmFuc2FjdGlvbgYDCQAAAQAAAAIFAAAAByRtYXRjaDACAAAAE1RyYW5zZmVyVHJhbnNhY3Rpb24EAAAAAWcJAQAAAAdleHRyYWN0AAAAAQkAA+gAAAABAQAAACCB+LHr7irQ7N6spxF91VJYssai++EyJEw1WFYwXb0UCwkAAAAAAAACCAUAAAABZwAAAAJpZAEAAAAggfix6+4q0OzerKcRfdVSWLLGovvhMiRMNVhWMF29FAsHBAAAAAdlbnRyaWVzBAAAAAckbWF0Y2gwBQAAAAJ0eAMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAAPRGF0YVRyYW5zYWN0aW9uBAAAAAFkBQAAAAckbWF0Y2gwBAAAAANpbnQJAQAAAAdleHRyYWN0AAAAAQkABBAAAAACCAUAAAABZAAAAARkYXRhAgAAAANpbnQEAAAABGJvb2wJAQAAAAdleHRyYWN0AAAAAQkABBEAAAACCAUAAAABZAAAAARkYXRhAgAAAARib29sBAAAAARibG9iCQEAAAAHZXh0cmFjdAAAAAEJAAQSAAAAAggFAAAAAWQAAAAEZGF0YQIAAAAEYmxvYgQAAAADc3RyCQEAAAAHZXh0cmFjdAAAAAEJAAQTAAAAAggFAAAAAWQAAAAEZGF0YQIAAAADc3RyBAAAAAlkYXRhQnlLZXkDAwMJAAAAAAAAAgkAAaQAAAABBQAAAANpbnQCAAAAAjI0BgkAAAAAAAACCQABpQAAAAEFAAAABGJvb2wCAAAABHRydWUGCQAAZgAAAAIJAADIAAAAAQUAAAAEYmxvYgAAAAAAAAAAAAYJAAAAAAAAAgUAAAADc3RyAgAAAAR0ZXN0BAAAAAJkMAkBAAAAB2V4dHJhY3QAAAABCQEAAAAKZ2V0SW50ZWdlcgAAAAIIBQAAAAFkAAAABGRhdGEAAAAAAAAAAAAEAAAAAmQxCQEAAAAHZXh0cmFjdAAAAAEJAQAAAApnZXRCb29sZWFuAAAAAggFAAAAAWQAAAAEZGF0YQAAAAAAAAAAAQQAAAACZDIJAQAAAAlnZXRCaW5hcnkAAAACCAUAAAABZAAAAARkYXRhAAAAAAAAAAACBAAAAAJkMwkBAAAACWdldFN0cmluZwAAAAIIBQAAAAFkAAAABGRhdGEAAAAAAAAAAAMEAAAAC2RhdGFCeUluZGV4AwMDCQAAAAAAAAIJAAGaAAAAAQUAAAACZDABAAAABGm3HXkGCQAAAAAAAAIJAAGcAAAAAQUAAAACZDEBAAAABIIYo5IGCQEAAAAJaXNEZWZpbmVkAAAAAQUAAAACZDIGCQAAAAAAAAIJAAGbAAAAAQkBAAAAB2V4dHJhY3QAAAABBQAAAAJkMwEAAAAEmnopqgMFAAAACWRhdGFCeUtleQUAAAALZGF0YUJ5SW5kZXgHAwkAAAEAAAACBQAAAAckbWF0Y2gwAgAAABNUcmFuc2ZlclRyYW5zYWN0aW9uBAAAAANhZGQJAQAAAAdBZGRyZXNzAAAAAQEAAAAaAVRkF43fgOyfPt/WVujqaJKrKOnneu66nJ0EAAAABGxvbmcJAAAAAAAAAgkBAAAAB2V4dHJhY3QAAAABCQAEGgAAAAIFAAAAA2FkZAIAAAADaW50AAAAAAAAAAAYBAAAAAVib29sMQkAAAAAAAACCQEAAAAHZXh0cmFjdAAAAAEJAAQbAAAAAgUAAAADYWRkAgAAAARib29sBgQAAAADYmluCQAAAAAAAAIJAQAAAAdleHRyYWN0AAAAAQkABBwAAAACBQAAAANhZGQCAAAABGJsb2IBAAAABWFsaWNlBAAAAARzdHIxCQAAAAAAAAIJAQAAAAdleHRyYWN0AAAAAQkABB0AAAACBQAAAANhZGQCAAAAA3N0cgIAAAAEdGVzdAMDAwUAAAAEbG9uZwUAAAAFYm9vbDEHBQAAAANiaW4HBQAAAARzdHIxBwMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAAWQ3JlYXRlQWxpYXNUcmFuc2FjdGlvbgkAAAIAAAABAgAAAAVvaCBubwMJAAABAAAAAgUAAAAHJG1hdGNoMAIAAAAPQnVyblRyYW5zYWN0aW9uCQEAAAAFdGhyb3cAAAAABwQAAAAHYUZyb21QSwkAAAAAAAACCQEAAAAUYWRkcmVzc0Zyb21QdWJsaWNLZXkAAAABCAUAAAACdHgAAAAPc2VuZGVyUHVibGljS2V5CAUAAAACdHgAAAAGc2VuZGVyBAAAAA9hRnJvbVN0ck9yUmVjaXAEAAAAByRtYXRjaDAFAAAAAnR4AwkAAAEAAAACBQAAAAckbWF0Y2gwAgAAAA9EYXRhVHJhbnNhY3Rpb24JAAAAAAAAAgkBAAAAEWFkZHJlc3NGcm9tU3RyaW5nAAAAAQIAAAAjM041R1JxekRCaGpWWG5DbjQ0YmFIY3oyR29aeTVxTHh0VGgJAQAAAAdBZGRyZXNzAAAAAQEAAAAaAVSoYvWtn5xSOgZyscKLnKFwwV9ogl+Q7gADCQAAAQAAAAIFAAAAByRtYXRjaDACAAAAE1RyYW5zZmVyVHJhbnNhY3Rpb24EAAAAAnQxBQAAAAckbWF0Y2gwCQAAAAAAAAIJAAQkAAAAAQgFAAAAAnQxAAAACXJlY2lwaWVudAkBAAAAB0FkZHJlc3MAAAABAQAAABoBVGQXjd+A7J8+39ZW6Opokqso6ed67rqcnQcEAAAACGJhbGFuY2VzAwkAAGYAAAACCQAD6wAAAAIIBQAAAAJ0eAAAAAZzZW5kZXIFAAAABHVuaXQAAAAAAAAAAAAJAQAAAAIhPQAAAAIJAQAAAAx3YXZlc0JhbGFuY2UAAAABCAUAAAACdHgAAAAGc2VuZGVyAAAAAAAAAAAABwQAAAAFd2F2ZXMDAwMDAwUAAAAGdHhCeUlkBQAAAAdlbnRyaWVzBwUAAAAIYmFsYW5jZXMHBQAAAAdhRnJvbVBLBwUAAAAPYUZyb21TdHJPclJlY2lwBwkAAGYAAAACBQAAAAZoZWlnaHQAAAAAAAAAAAAHBAAAAANia3MDAwkBAAAAAiE9AAAAAgkAAfYAAAABAQAAAAABAAAAAAkBAAAAAiE9AAAAAgkAAfUAAAABAQAAAAABAAAAAAcJAQAAAAIhPQAAAAIJAAH3AAAAAQEAAAAAAQAAAAAHBAAAAANzaWcJAQAAAAIhPQAAAAIJAAH0AAAAAwEAAAACGr4BAAAAAgA8AQAAAAI1uAYEAAAABXN0cjU4CQAAAAAAAAIJAAJZAAAAAQkAAlgAAAABCAUAAAACdHgAAAACaWQIBQAAAAJ0eAAAAAJpZAQAAAAFc3RyNjQJAAAAAAAAAgkAAlsAAAABCQACWgAAAAEIBQAAAAJ0eAAAAAJpZAgFAAAAAnR4AAAAAmlkBAAAAAZjcnlwdG8DAwMFAAAAA2JrcwUAAAADc2lnBwUAAAAFc3RyNTgHBQAAAAVzdHI2NAcDBQAAAANybmQDBQAAAARwdXJlBQAAAAV3YXZlcwcFAAAABmNyeXB0bw==") SerdeV1.serialize(compiledScript) shouldBe bytes } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/TransactionBindingsTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/TransactionBindingsTest.scala index f307953d23..c9eeaf2bc2 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/TransactionBindingsTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/TransactionBindingsTest.scala @@ -881,7 +881,7 @@ class TransactionBindingsTest extends PropSpec with PathMockFactory with EitherV ByteStr.empty ) for { - compileResult <- compiler.ExpressionCompiler(ctx.compilerContext, expr) + compileResult <- compiler.ExpressionCompiler(ctx.compilerContext, V3, expr) (typedExpr, _) = compileResult r <- EvaluatorV1().apply[EVALUATED](ctx.evaluationContext(environment), typedExpr).leftMap(_.message) } yield r @@ -913,7 +913,7 @@ class TransactionBindingsTest extends PropSpec with PathMockFactory with EitherV ) for { - compileResult <- ExpressionCompiler(ctx.compilerContext, expr) + compileResult <- ExpressionCompiler(ctx.compilerContext, V2, expr) (typedExpr, _) = compileResult r <- EvaluatorV1().apply[EVALUATED](ctx.evaluationContext(env), typedExpr).leftMap(_.message) } yield r diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/package.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/package.scala index 43b9d510d8..a454344ab2 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/package.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/predef/package.scala @@ -16,7 +16,7 @@ import com.wavesplatform.state.Blockchain import com.wavesplatform.transaction.smart.BlockchainContext.In import com.wavesplatform.transaction.smart.{BlockchainContext, buildThisValue} import com.wavesplatform.transaction.transfer.TransferTransaction -import com.wavesplatform.transaction.{Authorized, DataTransaction, EthereumTransaction, Proofs, ProvenTransaction, Transaction, VersionedTransaction} +import com.wavesplatform.transaction.{Authorized, DataTransaction, EthereumTransaction, Proofs, ProvenTransaction, Transaction, Versioned} import com.wavesplatform.utils.EmptyBlockchain import monix.eval.Coeval import shapeless.Coproduct @@ -27,7 +27,7 @@ package object predef { def runScript[T <: EVALUATED](script: String, version: StdLibVersion, t: In, blockchain: Blockchain, chainId: Byte): Either[String, T] = { val expr = Parser.parseExpr(script).get.value for { - compileResult <- ExpressionCompiler(compilerContext(version, Expression, isAssetScript = false), expr) + compileResult <- ExpressionCompiler(compilerContext(version, Expression, isAssetScript = false), version, expr) (typedExpr, _) = compileResult directives = DirectiveSet(version, Account, Expression).explicitGet() evalContext <- BlockchainContext.build( @@ -209,7 +209,7 @@ package object predef { def provenPart(t: Transaction with Authorized, emptyBodyBytes: Boolean = false, checkProofs: Boolean = true): String = { val version = t match { case _: EthereumTransaction => 0 - case v: VersionedTransaction => v.version + case v: Versioned => v.version case _ => 1 } val proofs = t match { diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/AddressFromRecipientScenarioTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/AddressFromRecipientScenarioTest.scala index 93e5646af9..8e7ea048ec 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/AddressFromRecipientScenarioTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/AddressFromRecipientScenarioTest.scala @@ -49,7 +49,7 @@ class AddressFromRecipientScenarioTest extends PropSpec with WithState { val (gen, _, _, transferViaAlias) = preconditionsAndAliasCreations assertDiffAndState(Seq(TestBlock.create(gen)), TestBlock.create(Seq())) { case (_, state) => - runScript(script, transferViaAlias, state) should produce(" does not exists") + runScript(script, transferViaAlias, state) should produce(" does not exist") } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/BalancesV4Test.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/BalancesV4Test.scala index ed46169e84..ac2a53c24c 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/BalancesV4Test.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/BalancesV4Test.scala @@ -1,11 +1,11 @@ package com.wavesplatform.state.diffs.smart.scenarios import cats.syntax.semigroup.* +import com.wavesplatform.api.common.CommonAccountsApi import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.db.WithState import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.history.SnapshotOps import com.wavesplatform.lagonaki.mocks.TestBlock import com.wavesplatform.lang.Global import com.wavesplatform.lang.directives.DirectiveSet @@ -20,7 +20,6 @@ import com.wavesplatform.lang.v1.traits.Environment import com.wavesplatform.settings.{Constants, FunctionalitySettings, TestFunctionalitySettings} import com.wavesplatform.state.* import com.wavesplatform.state.diffs.* -import com.wavesplatform.state.reader.SnapshotBlockchain import com.wavesplatform.test.* import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.* @@ -95,13 +94,12 @@ class BalancesV4Test extends PropSpec with WithState { Seq(TestBlock.create(b)), TestBlock.create(Seq(ci)), rideV4Activated - ) { case (d, s) => + ) { case (snapshot, blockchain) => val apiBalance = - com.wavesplatform.api.common - .CommonAccountsApi(() => SnapshotBlockchain(s, SnapshotOps.fromDiff(d, s).explicitGet()), rdb, s) + CommonAccountsApi(() => SnapshotBlockchain(blockchain, snapshot), rdb, blockchain) .balanceDetails(acc1.toAddress) .explicitGet() - val data = d.accountData(dapp.toAddress) + val data = snapshot.accountData(dapp.toAddress) data("available") shouldBe IntegerDataEntry("available", apiBalance.available) apiBalance.available shouldBe 16 * Constants.UnitsInWave data("regular") shouldBe IntegerDataEntry("regular", apiBalance.regular) @@ -133,7 +131,7 @@ class BalancesV4Test extends PropSpec with WithState { | assetBalance(Address(base58'$acc'), this.id) == $a && assetBalance(Alias("alias"), this.id) == $a """.stripMargin val parsedScript = Parser.parseExpr(script).get.value - ExprScript(V4, ExpressionCompiler(ctx.compilerContext, parsedScript).explicitGet()._1) + ExprScript(V4, ExpressionCompiler(ctx.compilerContext, V4, parsedScript).explicitGet()._1) .explicitGet() } @@ -195,7 +193,7 @@ class BalancesV4Test extends PropSpec with WithState { | wavesBalance(Address(base58'$acc')).regular == $w """.stripMargin val parsedScript = Parser.parseExpr(script).get.value - ExprScript(V4, ExpressionCompiler(ctx.compilerContext, parsedScript).explicitGet()._1) + ExprScript(V4, ExpressionCompiler(ctx.compilerContext, V4, parsedScript).explicitGet()._1) .explicitGet() } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/MultiSig2of3Test.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/MultiSig2of3Test.scala index 94e729bea6..dd3ccb8635 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/MultiSig2of3Test.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/MultiSig2of3Test.scala @@ -37,7 +37,7 @@ class MultiSig2of3Test extends PropSpec with WithState { | """.stripMargin val untyped = Parser.parseExpr(script).get.value - ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), untyped).explicitGet()._1 + ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), V1, untyped).explicitGet()._1 } val preconditionsAndTransfer: (GenesisTransaction, SetScriptTransaction, TransferTransaction, Seq[ByteStr]) = { diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/NotaryControlledTransferScenarioTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/NotaryControlledTransferScenarioTest.scala index aa4eae87db..05e1d718a4 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/NotaryControlledTransferScenarioTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/NotaryControlledTransferScenarioTest.scala @@ -58,7 +58,7 @@ class NotaryControlledTransferScenarioTest extends PropSpec with WithState { | } """.stripMargin val untypedScript = Parser.parseExpr(assetScript).get.value - val typedScript = ExprScript(ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), untypedScript).explicitGet()._1) + val typedScript = ExprScript(ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), V1, untypedScript).explicitGet()._1) .explicitGet() val issue = TxHelpers.issue(company, 100, script = Some(typedScript)) @@ -88,7 +88,7 @@ class NotaryControlledTransferScenarioTest extends PropSpec with WithState { private def eval(code: String) = { val untyped = Parser.parseExpr(code).get.value - val typed = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), untyped).map(_._1) + val typed = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), V1, untyped).map(_._1) typed.flatMap(r => EvaluatorV1().apply[EVALUATED](dummyEvalContext, r).leftMap(_.message)) } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OneProofForNonScriptedAccountTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OneProofForNonScriptedAccountTest.scala index e917798d07..921b02d846 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OneProofForNonScriptedAccountTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OneProofForNonScriptedAccountTest.scala @@ -27,7 +27,7 @@ class OneProofForNonScriptedAccountTest extends PropSpec with WithState { val (genesis, _, transfer) = s val transferWithExtraProof = transfer.copy(proofs = Proofs(Seq(ByteStr.empty, ByteStr(Array(1: Byte))))) assertDiffEi(Seq(TestBlock.create(Seq(genesis))), TestBlock.create(Seq(transferWithExtraProof)), smartEnabledFS)( - totalDiffEi => totalDiffEi should produce("must have exactly 1 proof") + snapshotEi => snapshotEi should produce("must have exactly 1 proof") ) } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OnlyTransferIsAllowedTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OnlyTransferIsAllowedTest.scala index 6959cea124..af9942b319 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OnlyTransferIsAllowedTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OnlyTransferIsAllowedTest.scala @@ -32,12 +32,12 @@ class OnlyTransferIsAllowedTest extends PropSpec with WithState { | } """.stripMargin val untyped = Parser.parseExpr(scriptText).get.value - val transferAllowed = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), untyped).explicitGet()._1 + val transferAllowed = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), V1, untyped).explicitGet()._1 val (genesis, script, lease, transfer) = preconditions(transferAllowed) assertDiffAndState(Seq(TestBlock.create(Seq(genesis, script))), TestBlock.create(Seq(transfer)), smartEnabledFS) { case _ => () } assertDiffEi(Seq(TestBlock.create(Seq(genesis, script))), TestBlock.create(Seq(lease)), smartEnabledFS)( - totalDiffEi => totalDiffEi should produce("TransactionNotAllowedByScript") + snapshotEi => snapshotEi should produce("TransactionNotAllowedByScript") ) } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OracleDataTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OracleDataTest.scala index c23560e044..b6f6a8d77c 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OracleDataTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/OracleDataTest.scala @@ -55,7 +55,7 @@ class OracleDataTest extends PropSpec with WithState { |}""".stripMargin val untypedAllFieldsRequiredScript = Parser.parseExpr(allFieldsRequiredScript).get.value val typedAllFieldsRequiredScript = - ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), untypedAllFieldsRequiredScript).explicitGet()._1 + ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), V1, untypedAllFieldsRequiredScript).explicitGet()._1 val setScript = TxHelpers.setScript(master, ExprScript(typedAllFieldsRequiredScript).explicitGet()) val transferFromScripted = TxHelpers.transfer(master, alice.toAddress) diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/TransactionFieldAccessTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/TransactionFieldAccessTest.scala index fa52ffda1e..722fd8fca3 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/TransactionFieldAccessTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/TransactionFieldAccessTest.scala @@ -24,7 +24,7 @@ class TransactionFieldAccessTest extends PropSpec with WithState { val genesis = TxHelpers.genesis(master.toAddress) val untyped = Parser.parseExpr(code).get.value - val typed = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), untyped).explicitGet()._1 + val typed = ExpressionCompiler(compilerContext(V1, Expression, isAssetScript = false), V1, untyped).explicitGet()._1 val setScript = TxHelpers.setScript(master, ExprScript(typed).explicitGet()) val transfer = TxHelpers.transfer(master, recipient.toAddress, ENOUGH_AMT / 2) val lease = TxHelpers.lease(master, recipient.toAddress, ENOUGH_AMT / 2) @@ -47,7 +47,7 @@ class TransactionFieldAccessTest extends PropSpec with WithState { val (genesis, setScript, lease, transfer) = preconditionsTransferAndLease(script) assertDiffAndState(Seq(TestBlock.create(Seq(genesis, setScript))), TestBlock.create(Seq(transfer)), smartEnabledFS) { case _ => () } assertDiffEi(Seq(TestBlock.create(Seq(genesis, setScript))), TestBlock.create(Seq(lease)), smartEnabledFS)( - totalDiffEi => totalDiffEi should produce("TransactionNotAllowedByScript") + snapshotEi => snapshotEi should produce("TransactionNotAllowedByScript") ) } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/TransferByIdTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/TransferByIdTest.scala index eb3ba6de55..925ea4e4b6 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/TransferByIdTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/TransferByIdTest.scala @@ -34,7 +34,7 @@ class TransferByIdTest extends PropSpec with WithState { val expr: EXPR = { val parsed = Parser.parseExpr(scriptSrc).get.value - ExpressionCompiler(compilerContext(V3, Expression, isAssetScript = false), parsed).explicitGet()._1 + ExpressionCompiler(compilerContext(V3, Expression, isAssetScript = false), V3, parsed).explicitGet()._1 } property("Transfer by id works fine") { diff --git a/node/src/test/scala/com/wavesplatform/state/patch/CancelLeasesToDisabledAliasesSpec.scala b/node/src/test/scala/com/wavesplatform/state/patch/CancelLeasesToDisabledAliasesSpec.scala index 548f9671fc..08f7b503b9 100644 --- a/node/src/test/scala/com/wavesplatform/state/patch/CancelLeasesToDisabledAliasesSpec.scala +++ b/node/src/test/scala/com/wavesplatform/state/patch/CancelLeasesToDisabledAliasesSpec.scala @@ -11,10 +11,9 @@ import com.wavesplatform.settings.WavesSettings import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.* import com.wavesplatform.transaction.TxHelpers -import org.scalamock.scalatest.PathMockFactory import org.scalatest.BeforeAndAfterAll -class CancelLeasesToDisabledAliasesSpec extends FlatSpec with PathMockFactory with WithDomain with BeforeAndAfterAll { +class CancelLeasesToDisabledAliasesSpec extends FlatSpec with WithDomain with BeforeAndAfterAll { val MainnetSettings: WavesSettings = { import SettingsFromDefaultConfig.blockchainSettings.functionalitySettings as fs SettingsFromDefaultConfig.copy( diff --git a/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotProtoTest.scala b/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotProtoTest.scala deleted file mode 100644 index 7f2d5ae0a1..0000000000 --- a/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotProtoTest.scala +++ /dev/null @@ -1,102 +0,0 @@ -package com.wavesplatform.state.snapshot - -import com.google.protobuf.ByteString -import com.wavesplatform.account.Alias -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.lang.directives.values.V6 -import com.wavesplatform.lang.v1.compiler.TestCompiler -import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot.AssetStatic -import com.wavesplatform.state.* -import com.wavesplatform.state.reader.LeaseDetails -import com.wavesplatform.state.reader.LeaseDetails.Status -import com.wavesplatform.test.PropSpec -import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} -import com.wavesplatform.transaction.TxHelpers.{defaultAddress, defaultSigner, secondAddress, secondSigner} - -import scala.collection.immutable.VectorMap - -class StateSnapshotProtoTest extends PropSpec { - property("serialization") { - val snapshot = StateSnapshot( - transactions = VectorMap(), - VectorMap( - (defaultAddress, Waves) -> 123, - (secondAddress, IssuedAsset(ByteStr.fromBytes(7, 17, 77))) -> 456 - ), - Map( - defaultAddress -> LeaseBalance(999, 888), - secondAddress -> LeaseBalance.empty - ), - VectorMap( - IssuedAsset(ByteStr.fromBytes(1, 1, 1)) -> AssetStatic( - ByteString.copyFrom(Array[Byte](1, 1, 1)), - ByteString.copyFromUtf8("txId"), - ByteString.copyFromUtf8("pk"), - 5, - nft = true - ), - IssuedAsset(ByteStr.fromBytes(2, 2, 2)) -> AssetStatic( - ByteString.copyFrom(Array[Byte](2, 2, 2)), - ByteString.copyFromUtf8("txId"), - ByteString.copyFromUtf8("pk"), - 5, - nft = false - ) - ), - Map( - IssuedAsset(ByteStr.fromBytes(1, 1, 1)) -> AssetVolumeInfo(isReissuable = true, BigInt(123)), - IssuedAsset(ByteStr.fromBytes(2, 2, 2)) -> AssetVolumeInfo(isReissuable = false, BigInt(0)) - ), - Map( - IssuedAsset(ByteStr.fromBytes(1, 1, 1)) -> AssetInfo("name1", "desc1", Height @@ 888), - IssuedAsset(ByteStr.fromBytes(2, 2, 2)) -> AssetInfo("name2", "desc2", Height @@ 999) - ), - Map( - IssuedAsset(ByteStr.fromBytes(2, 2, 2)) -> AssetScriptInfo(TestCompiler(V6).compileAsset("this != this"), 0) - ), - Map( - IssuedAsset(ByteStr.fromBytes(1, 1, 1)) -> SponsorshipValue(99999), - IssuedAsset(ByteStr.fromBytes(2, 2, 2)) -> SponsorshipValue(0) - ), - Map( - ByteStr.fromBytes(4, 5, 6) -> LeaseDetails(defaultSigner.publicKey, secondAddress, 123, Status.Active, ByteStr.fromBytes(1, 2, 3), 4), - ByteStr.fromBytes(7, 8, 9) -> LeaseDetails( - secondSigner.publicKey, - defaultAddress, - 0, - Status.Cancelled(2, Some(ByteStr.fromBytes(5, 5, 5))), - ByteStr.fromBytes(1, 2, 3), - 777777777 - ) - ), - Map( - Alias.create("alias").explicitGet() -> defaultAddress, - Alias.create("alias2").explicitGet() -> secondAddress - ), - Map( - ByteStr.fromBytes(1, 1, 1) -> VolumeAndFee(100, 200), - ByteStr.fromBytes(2, 2, 2) -> VolumeAndFee(0, 0) - ), - Map( - defaultSigner.publicKey -> Some(AccountScriptInfo(defaultSigner.publicKey, TestCompiler(V6).compileExpression("this != this"), 123)), - secondSigner.publicKey -> None - ), - Map( - defaultAddress -> Map( - "a" -> IntegerDataEntry("a", 123), - "b" -> StringDataEntry("b", "string"), - "c" -> BooleanDataEntry("c", true), - "d" -> BinaryDataEntry("d", ByteStr.fromBytes(7, 7, 7)), - "e" -> EmptyDataEntry("e") - ), - secondAddress -> Map( - "a" -> BinaryDataEntry("a", ByteStr.fromBytes(8, 8, 8)), - "b" -> EmptyDataEntry("b") - ) - ) - ) - Seq(TxMeta.Status.Succeeded, TxMeta.Status.Failed, TxMeta.Status.Elided) - .foreach(txStatus => StateSnapshot.fromProtobuf(snapshot.toProtobuf(txStatus)) shouldBe (snapshot, txStatus)) - } -} diff --git a/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotStorageTest.scala b/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotStorageTest.scala index 34a4c6c90a..4efc079976 100644 --- a/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotStorageTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotStorageTest.scala @@ -9,21 +9,17 @@ import com.wavesplatform.db.WithDomain import com.wavesplatform.lang.directives.values.V6 import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.lang.v1.traits.domain.{Issue, Lease, Recipient} -import com.wavesplatform.protobuf.ByteStrExt -import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot.AssetStatic +import com.wavesplatform.protobuf.PBSnapshots import com.wavesplatform.state.* import com.wavesplatform.state.TxMeta.Status.{Failed, Succeeded} import com.wavesplatform.state.diffs.BlockDiffer.CurrentBlockFeePart import com.wavesplatform.state.diffs.ENOUGH_AMT -import com.wavesplatform.state.reader.LeaseDetails -import com.wavesplatform.state.reader.LeaseDetails.Status.{Active, Cancelled} import com.wavesplatform.test.DomainPresets.* import com.wavesplatform.test.{NumericExt, PropSpec} import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxHelpers.* import com.wavesplatform.transaction.assets.exchange.OrderType.{BUY, SELL} -import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer -import com.wavesplatform.transaction.{EthTxGenerator, Transaction, TxHelpers, TxNonNegativeAmount} +import com.wavesplatform.transaction.{EthTxGenerator, Transaction, TxHelpers, TxPositiveAmount} import scala.collection.immutable.VectorMap import scala.math.pow @@ -50,7 +46,11 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { if (failed) d.appendAndAssertFailed(tx) else d.appendAndAssertSucceed(tx) d.appendBlock() val status = if (failed) Failed else Succeeded - StateSnapshot.fromProtobuf(d.rocksDBWriter.transactionSnapshot(tx.id()).get) shouldBe (expectedSnapshotWithMiner, status) + PBSnapshots.fromProtobuf( + d.rocksDBWriter.transactionSnapshot(tx.id()).get, + tx.id(), + d.blockchain.height - 1 + ) shouldBe (expectedSnapshotWithMiner, status) } // Genesis @@ -95,7 +95,7 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { (senderAddress, Waves) -> (d.balance(senderAddress) - 1.waves) ), assetStatics = VectorMap( - asset -> AssetStatic(asset.id.toByteString, issueTx.id().toByteString, sender.publicKey.toByteString, issueTx.decimals.value) + asset -> AssetStaticInfo(asset.id, TransactionId(issueTx.id()), sender.publicKey, issueTx.decimals.value, false) ), assetVolumes = Map( asset -> AssetVolumeInfo(isReissuable = true, BigInt(issueTx.quantity.value)) @@ -174,8 +174,8 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { senderAddress -> LeaseBalance(0, leaseTx.amount.value), recipient -> LeaseBalance(leaseTx.amount.value, 0) ), - leaseStates = Map( - leaseTx.id() -> LeaseDetails(sender.publicKey, recipient, leaseTx.amount.value, Active, leaseTx.id(), d.solidStateHeight + 2) + newLeases = Map( + leaseTx.id() -> LeaseStaticInfo(leaseTx.sender, recipient, leaseTx.amount, leaseTx.id(), d.blockchain.height + 1) ) ) ) @@ -192,15 +192,8 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { senderAddress -> LeaseBalance(0, 0), recipient -> LeaseBalance(0, 0) ), - leaseStates = Map( - leaseTx.id() -> LeaseDetails( - sender.publicKey, - recipient, - leaseTx.amount.value, - Cancelled(d.solidStateHeight + 2, Some(leaseCancelTx.id())), - leaseTx.id(), - d.solidStateHeight - ) + cancelledLeases = Map( + leaseTx.id() -> LeaseDetails.Status.Cancelled(d.blockchain.height + 1, Some(leaseCancelTx.id())) ) ) ) @@ -222,8 +215,8 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { sender, fee = fee, to = Seq( - ParsedTransfer(TxHelpers.signer(4).toAddress, TxNonNegativeAmount(123)), - ParsedTransfer(TxHelpers.signer(5).toAddress, TxNonNegativeAmount(456)) + TxHelpers.signer(4).toAddress -> 123, + TxHelpers.signer(5).toAddress -> 456 ) ), StateSnapshot( @@ -324,7 +317,7 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { senderAddress -> LeaseBalance(123, 0) ), assetStatics = VectorMap( - dAppAssetId -> AssetStatic(dAppAssetId.id.toByteString, invokeId.toByteString, dAppPk.toByteString, 4) + dAppAssetId -> AssetStaticInfo(dAppAssetId.id, TransactionId(invokeId), dAppPk, 4, false) ), assetVolumes = Map( dAppAssetId -> AssetVolumeInfo(true, 1000) @@ -332,8 +325,8 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { assetNamesAndDescriptions = Map( dAppAssetId -> AssetInfo("name", "description", Height(height)) ), - leaseStates = Map( - leaseId -> LeaseDetails(dAppPk, senderAddress, 123, Active, invokeId, height) + newLeases = Map( + leaseId -> LeaseStaticInfo(dAppPk, senderAddress, TxPositiveAmount(123), invokeId, height) ), accountData = Map( dAppPk.toAddress -> Map("key" -> StringDataEntry("key", "abc")) @@ -358,7 +351,7 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { (senderAddress, Waves) -> (d.balance(senderAddress) - fee) ), assetNamesAndDescriptions = Map( - asset2 -> AssetInfo(updateAssetTx.name, updateAssetTx.description, Height(d.solidStateHeight + 2)) + asset2 -> AssetInfo(updateAssetTx.name, updateAssetTx.description, Height(d.blockchain.height + 1)) ) ) ) diff --git a/node/src/test/scala/com/wavesplatform/state/snapshot/TxStateSnapshotHashSpec.scala b/node/src/test/scala/com/wavesplatform/state/snapshot/TxStateSnapshotHashSpec.scala index 75ea473f46..b17fe3586a 100644 --- a/node/src/test/scala/com/wavesplatform/state/snapshot/TxStateSnapshotHashSpec.scala +++ b/node/src/test/scala/com/wavesplatform/state/snapshot/TxStateSnapshotHashSpec.scala @@ -1,187 +1,318 @@ package com.wavesplatform.state.snapshot -import cats.data.Ior -import com.google.common.primitives.{Ints, Longs, UnsignedBytes} -import com.wavesplatform.account.{AddressScheme, Alias} +import com.google.common.primitives.Ints +import com.google.protobuf.ByteString.copyFrom as bs import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.* -import com.wavesplatform.crypto.DigestLength -import com.wavesplatform.db.WithDomain -import com.wavesplatform.db.WithState.AddrWithBalance -import com.wavesplatform.history.SnapshotOps -import com.wavesplatform.lang.v1.estimator.ScriptEstimatorV1 +import com.wavesplatform.crypto.fastHash +import com.wavesplatform.lang.directives.values.V6 +import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.protobuf.snapshot.{TransactionStatus, TransactionStateSnapshot as TSS} +import com.wavesplatform.protobuf.transaction.DataEntry +import com.wavesplatform.protobuf.{Amount, PBSnapshots} import com.wavesplatform.state.* -import com.wavesplatform.state.TxMeta.Status.* -import com.wavesplatform.state.TxStateSnapshotHashBuilder.{KeyType, TxStatusInfo} -import com.wavesplatform.state.reader.LeaseDetails import com.wavesplatform.test.* -import com.wavesplatform.transaction.Asset.IssuedAsset -import com.wavesplatform.transaction.TxHelpers.invoke -import com.wavesplatform.transaction.smart.script.ScriptCompiler -import com.wavesplatform.transaction.{AssetIdLength, TxHelpers} - -import java.nio.charset.StandardCharsets -import scala.collection.immutable.VectorMap - -class TxStateSnapshotHashSpec extends PropSpec with WithDomain { - val stateHash = new StateHashBuilder - private val address1 = TxHelpers.address(1) - private val address2 = TxHelpers.address(2) - private val assetId1 = IssuedAsset(ByteStr.fill(AssetIdLength)(1)) - private val assetId2 = IssuedAsset(ByteStr.fill(AssetIdLength)(2)) - private val assetId3 = IssuedAsset(ByteStr.fill(AssetIdLength)(3)) - private val assetId4 = IssuedAsset(ByteStr.fill(AssetIdLength)(4)) - - private val orderId = ByteStr.fill(DigestLength)(5) - private val volumeAndFee = VolumeAndFee(11, 2) - - private val leaseId = ByteStr.fill(DigestLength)(6) - private val leaseDetails = LeaseDetails(TxHelpers.signer(1).publicKey, address2, 1.waves, LeaseDetails.Status.Active, leaseId, 2) - - private val addr1Balance = 10.waves - private val addr2Balance = 20.waves - private val addr1PortfolioDiff = Portfolio(balance = 2.waves, lease = LeaseBalance(3.waves, 1.waves)) - private val addr2PortfolioDiff = Portfolio(assets = VectorMap(assetId1 -> 123)) - - private val addr1Alias1 = Alias(AddressScheme.current.chainId, "addr1Alias1") - private val addr1Alias2 = Alias(AddressScheme.current.chainId, "addr1Alias2") - private val addr2Alias = Alias(AddressScheme.current.chainId, "addr2") - - private val assetInfo1 = NewAssetInfo( - AssetStaticInfo(assetId1.id, TransactionId(assetId1.id), TxHelpers.signer(1).publicKey, 8, false), - AssetInfo("test1", "desc1", Height(2)), - AssetVolumeInfo(true, BigInt(123)) - ) - private val assetInfo2 = NewAssetInfo( - AssetStaticInfo(assetId2.id, TransactionId(assetId2.id), TxHelpers.signer(1).publicKey, 8, false), - AssetInfo("test2", "desc2", Height(2)), - AssetVolumeInfo(true, BigInt(123)) - ) - private val assetInfo3 = NewAssetInfo( - AssetStaticInfo(assetId3.id, TransactionId(assetId3.id), TxHelpers.signer(1).publicKey, 8, false), - AssetInfo("test3", "desc3", Height(2)), - AssetVolumeInfo(true, BigInt(123)) - ) - private val assetInfo4 = NewAssetInfo( - AssetStaticInfo(assetId4.id, TransactionId(assetId4.id), TxHelpers.signer(1).publicKey, 8, false), - AssetInfo("test4", "desc4", Height(2)), - AssetVolumeInfo(true, BigInt(123)) - ) - private val updatedAssetInfo1 = AssetInfo("updTest1", "updDesc1", Height(2)) - private val updatedAssetVolumeInfo1 = AssetVolumeInfo(false, 124) - private val updatedAssetInfo2 = AssetInfo("updTest2", "updDesc2", Height(2)) - private val updatedAssetVolumeInfo3 = AssetVolumeInfo(false, 125) - private val sponsorship = SponsorshipValue(12) - - private val testScript = ScriptCompiler - .compile( - """ - |{-# STDLIB_VERSION 2 #-} - |{-# CONTENT_TYPE EXPRESSION #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - |true - |""".stripMargin, - ScriptEstimatorV1 +import com.wavesplatform.transaction.TxHelpers +import org.bouncycastle.util.encoders.Hex + +class TxStateSnapshotHashSpec extends PropSpec { + private def hashInt(i: Int) = bs(fastHash(Ints.toByteArray(i))) + + val stateHash = new StateHashBuilder + private val signer101 = TxHelpers.signer(101) + private val signer102 = TxHelpers.signer(102) + private val signer103 = TxHelpers.signer(103) + + private val address1 = signer101.toAddress + private val address2 = signer102.toAddress + private val address3 = signer103.toAddress + + private val assetId1 = hashInt(0xaa22aa44) + private val assetId2 = hashInt(0xbb22aa44) + + private val leaseId = hashInt(0x11aaef22) + private val orderId1 = hashInt(0xee23ef22) + private val orderId2 = hashInt(0xbb77ef29) + + private val testScript = bs(TestCompiler(V6).compileExpression("true").bytes().arr) + + private val wavesBalances = TSS(balances = + Seq( + TSS.Balance(bs(address1.bytes), Some(Amount(amount = 10.waves))), + TSS.Balance(bs(address2.bytes), Some(Amount(amount = 20.waves))) + ) + ) + + private val assetBalances = TSS(balances = + Seq( + TSS.Balance(bs(address1.bytes), Some(Amount(assetId1, 10_000))), + TSS.Balance(bs(address2.bytes), Some(Amount(assetId2, 20_000))) + ) + ) + + private val dataEntries = TSS(accountData = + Seq( + TSS.AccountData( + bs(address1.bytes), + Seq( + DataEntry("foo", DataEntry.Value.Empty), + DataEntry("bar", DataEntry.Value.StringValue("StringValue")), + DataEntry("baz", DataEntry.Value.BinaryValue(bs(address1.bytes))) + ) + ), + TSS.AccountData( + bs(address2.bytes), + Seq( + DataEntry("foo", DataEntry.Value.IntValue(1200)), + DataEntry("bar", DataEntry.Value.BoolValue(true)) + ) + ) ) - .explicitGet() - ._1 - private val accountScriptInfo = AccountScriptInfo(TxHelpers.signer(2).publicKey, testScript, 1) - private val assetScriptInfo = AssetScriptInfo(testScript, 1) + ) - private val dataEntry = StringDataEntry("key", "value") + private val accountScript = TSS(accountScripts = + Some( + TSS.AccountScript( + bs(signer101.publicKey.arr), + testScript, + 250 + ) + ) + ) - private val diff = Diff( - portfolios = Map(address1 -> addr1PortfolioDiff, address2 -> addr2PortfolioDiff), - issuedAssets = VectorMap(assetId1 -> assetInfo1, assetId2 -> assetInfo2, assetId3 -> assetInfo3, assetId4 -> assetInfo4), - updatedAssets = Map( - assetId1 -> Ior.Both(updatedAssetInfo1, updatedAssetVolumeInfo1), - assetId2 -> Ior.Left(updatedAssetInfo2), - assetId3 -> Ior.Right(updatedAssetVolumeInfo3) + private val assetScript = TSS(assetScripts = Some(TSS.AssetScript(assetId2, testScript))) + + private val newLease = TSS( + leaseBalances = Seq( + TSS.LeaseBalance(bs(address1.bytes), out = 45.waves), + TSS.LeaseBalance(bs(address2.bytes), in = 55.waves) ), - aliases = Map(addr1Alias1 -> address1, addr2Alias -> address2, addr1Alias2 -> address1), - orderFills = Map(orderId -> volumeAndFee), - leaseState = Map(leaseId -> leaseDetails), - scripts = Map(TxHelpers.signer(2).publicKey -> Some(accountScriptInfo)), - assetScripts = Map(assetId1 -> Some(assetScriptInfo)), - accountData = Map(address1 -> Map(dataEntry.key -> dataEntry)), - sponsorship = Map(assetId1 -> sponsorship) + newLeases = Seq( + TSS.NewLease(leaseId, bs(signer101.publicKey.arr), bs(address2.bytes), 25.waves) + ) ) - def hash(bs: Seq[Array[Byte]]): ByteStr = ByteStr(com.wavesplatform.crypto.fastHash(bs.reduce(_ ++ _))) + private val cancelledLease = TSS( + leaseBalances = Seq( + TSS.LeaseBalance(bs(address3.bytes), out = 20.waves), TSS.LeaseBalance(bs(TxHelpers.address(104).bytes), in = 0.waves)), + cancelledLeases = Seq( + TSS.CancelledLease(leaseId) + ) + ) - property("correctly create transaction state snapshot hash from snapshot") { - withDomain(DomainPresets.RideV6, balances = Seq(AddrWithBalance(address1, addr1Balance), AddrWithBalance(address2, addr2Balance))) { d => - val snapshot = SnapshotOps.fromDiff(diff, d.blockchain).explicitGet() - val tx = invoke() - testHash(snapshot, Some(TxStatusInfo(tx.id(), Succeeded)), Array()) - testHash(snapshot, Some(TxStatusInfo(tx.id(), Failed)), tx.id().arr :+ 1) - testHash(snapshot, Some(TxStatusInfo(tx.id(), Elided)), tx.id().arr :+ 2) - testHash(snapshot, None, Array()) - } - } + private val sponsorship = TSS( + sponsorships = Seq(TSS.Sponsorship(assetId2, 5500)) + ) - private def testHash(snapshot: StateSnapshot, txInfoOpt: Option[TxStatusInfo], txStatusBytes: Array[Byte]) = - TxStateSnapshotHashBuilder.createHashFromSnapshot(snapshot, txInfoOpt).txStateSnapshotHash shouldBe hash( - Seq( - Array(KeyType.WavesBalance.id.toByte) ++ address1.bytes ++ Longs.toByteArray(addr1PortfolioDiff.balance + addr1Balance), - Array(KeyType.AssetBalance.id.toByte) ++ address2.bytes ++ assetId1.id.arr ++ Longs.toByteArray(addr2PortfolioDiff.assets.head._2), - Array(KeyType.DataEntry.id.toByte) ++ address1.bytes ++ dataEntry.key.getBytes(StandardCharsets.UTF_8) ++ dataEntry.valueBytes, - Array(KeyType.AccountScript.id.toByte) ++ address2.bytes ++ accountScriptInfo.script.bytes().arr ++ accountScriptInfo.publicKey.arr ++ Longs - .toByteArray(accountScriptInfo.verifierComplexity), - Array(KeyType.AssetScript.id.toByte) ++ assetId1.id.arr ++ testScript.bytes().arr, - Array(KeyType.LeaseBalance.id.toByte) ++ address1.bytes ++ Longs.toByteArray(addr1PortfolioDiff.lease.in) ++ Longs.toByteArray( - addr1PortfolioDiff.lease.out - ), - Array(KeyType.LeaseStatus.id.toByte) ++ leaseId.arr ++ (if (leaseDetails.isActive) Array(1: Byte) else Array(0: Byte)), - Array(KeyType.Sponsorship.id.toByte) ++ assetId1.id.arr ++ Longs.toByteArray(sponsorship.minFee), - Array(KeyType.Alias.id.toByte) ++ address1.bytes ++ addr1Alias1.name.getBytes(StandardCharsets.UTF_8), - Array(KeyType.Alias.id.toByte) ++ address1.bytes ++ addr1Alias2.name.getBytes(StandardCharsets.UTF_8), - Array(KeyType.Alias.id.toByte) ++ address2.bytes ++ addr2Alias.name.getBytes(StandardCharsets.UTF_8), - Array(KeyType.VolumeAndFee.id.toByte) ++ orderId.arr ++ Longs.toByteArray(volumeAndFee.volume) ++ Longs.toByteArray(volumeAndFee.fee), - Array(KeyType.AssetStatic.id.toByte) ++ assetId1.id.arr ++ assetInfo1.static.issuer.arr ++ - Array(assetInfo1.static.decimals.toByte) ++ (if (assetInfo1.static.nft) Array(1: Byte) else Array(0: Byte)), - Array(KeyType.AssetStatic.id.toByte) ++ assetId2.id.arr ++ assetInfo2.static.issuer.arr ++ - Array(assetInfo2.static.decimals.toByte) ++ (if (assetInfo2.static.nft) Array(1: Byte) else Array(0: Byte)), - Array(KeyType.AssetStatic.id.toByte) ++ assetId3.id.arr ++ assetInfo3.static.issuer.arr ++ - Array(assetInfo3.static.decimals.toByte) ++ (if (assetInfo3.static.nft) Array(1: Byte) else Array(0: Byte)), - Array(KeyType.AssetStatic.id.toByte) ++ assetId4.id.arr ++ assetInfo4.static.issuer.arr ++ - Array(assetInfo4.static.decimals.toByte) ++ (if (assetInfo4.static.nft) Array(1: Byte) else Array(0: Byte)), - Array(KeyType.AssetVolume.id.toByte) ++ assetId1.id.arr ++ - (if (updatedAssetVolumeInfo1.isReissuable) Array(1: Byte) else Array(0: Byte)) ++ snapshot.assetVolumes(assetId1).volume.toByteArray, - Array(KeyType.AssetVolume.id.toByte) ++ assetId2.id.arr ++ - (if (assetInfo2.volume.isReissuable) Array(1: Byte) else Array(0: Byte)) ++ snapshot.assetVolumes(assetId2).volume.toByteArray, - Array(KeyType.AssetVolume.id.toByte) ++ assetId3.id.arr ++ - (if (updatedAssetVolumeInfo3.isReissuable) Array(1: Byte) else Array(0: Byte)) ++ snapshot.assetVolumes(assetId3).volume.toByteArray, - Array(KeyType.AssetVolume.id.toByte) ++ assetId4.id.arr ++ - (if (assetInfo4.volume.isReissuable) Array(1: Byte) else Array(0: Byte)) ++ snapshot.assetVolumes(assetId4).volume.toByteArray, - Array( - KeyType.AssetNameDescription.id.toByte - ) ++ assetId1.id.arr ++ updatedAssetInfo1.name.toByteArray ++ updatedAssetInfo1.description.toByteArray ++ Ints.toByteArray( - updatedAssetInfo1.lastUpdatedAt - ), - Array( - KeyType.AssetNameDescription.id.toByte - ) ++ assetId2.id.arr ++ updatedAssetInfo2.name.toByteArray ++ updatedAssetInfo2.description.toByteArray ++ Ints.toByteArray( - updatedAssetInfo2.lastUpdatedAt - ), - Array( - KeyType.AssetNameDescription.id.toByte - ) ++ assetId3.id.arr ++ assetInfo3.dynamic.name.toByteArray ++ assetInfo3.dynamic.description.toByteArray ++ Ints.toByteArray( - assetInfo3.dynamic.lastUpdatedAt - ), - Array( - KeyType.AssetNameDescription.id.toByte - ) ++ assetId4.id.arr ++ assetInfo4.dynamic.name.toByteArray ++ assetInfo4.dynamic.description.toByteArray ++ Ints.toByteArray( - assetInfo4.dynamic.lastUpdatedAt - ), - if (txStatusBytes.nonEmpty) Array(KeyType.TransactionStatus.id.toByte) ++ txStatusBytes else Array[Byte]() - ).sorted((x: Array[Byte], y: Array[Byte]) => UnsignedBytes.lexicographicalComparator().compare(x, y)) + private val alias = TSS( + aliases = Some(TSS.Alias(bs(address2.bytes), "wavesevo")) + ) + + private val volumeAndFee = TSS( + orderFills = Seq( + TSS.OrderFill(orderId1, 10.waves, 2000), + TSS.OrderFill(orderId2, 10.waves, 2000) ) + ) - property("correctly compute hash using previous value") { - val txStateHash = TxStateSnapshotHashBuilder.Result(ByteStr.fill(DigestLength)(1)) - val prevHash = ByteStr.fill(DigestLength)(2) + private val newAsset = TSS( + assetStatics = Seq( + TSS.NewAsset(assetId1, hashInt(0x88aadd55), nft = true), + TSS.NewAsset(assetId2, hashInt(0x88aadd55), decimals = 8) + ), + assetVolumes = Seq( + TSS.AssetVolume(assetId2, true, bs((BigInt(Long.MaxValue) * 10).toByteArray)), + TSS.AssetVolume(assetId1, false, bs(BigInt(1).toByteArray)) + ), + assetNamesAndDescriptions = Seq() + ) - txStateHash.createHash(prevHash) shouldBe hash(Seq(prevHash.arr ++ txStateHash.txStateSnapshotHash.arr)) + private val reissuedAsset = TSS( + assetVolumes = Seq( + TSS.AssetVolume(hashInt(0x23aadd55), false, bs((BigInt(10000000_00L)).toByteArray)), + ) + ) + private val renamedAsset = TSS( + assetNamesAndDescriptions = Seq( + TSS.AssetNameAndDescription( + assetId2, "newname", "some fancy description" + ) + ) + ) + private val failedTransaction = TSS( + balances = Seq( + TSS.Balance(bs(address2.bytes), Some(Amount(amount = 25.995.waves))) + ), + transactionStatus = TransactionStatus.FAILED + ) + private val elidedTransaction = TSS( + transactionStatus = TransactionStatus.ELIDED + ) + + private val all = TSS( + assetBalances.balances ++ wavesBalances.balances, + newLease.leaseBalances ++ cancelledLease.leaseBalances, + newLease.newLeases, + cancelledLease.cancelledLeases, + newAsset.assetStatics, + newAsset.assetVolumes ++ reissuedAsset.assetVolumes, + newAsset.assetNamesAndDescriptions ++ renamedAsset.assetNamesAndDescriptions, + newAsset.assetScripts, + alias.aliases, + volumeAndFee.orderFills, + accountScript.accountScripts, + dataEntries.accountData, + sponsorship.sponsorships, + failedTransaction.transactionStatus + ) + + private val testData = Table( + ("clue", "state snapshot", "base64 bytes", "tx id", "previous state hash", "expected result"), + ( + "waves balances", + wavesBalances, + "CiQKGgFUYP1Q7yDeRXEgffuciL58HC+KIscK2I+1EgYQgJTr3AMKJAoaAVRCxcljc/UP2BNQYE8cFPKmySVq2v0ZsCoSBhCAqNa5Bw==", + ByteStr.empty, + "", + "954bf440a83542e528fe1e650471033e42d97c5896cc571aec39fccc912d7db0" + ), + ( + "asset balances", + assetBalances, + "CkMKGgFUYP1Q7yDeRXEgffuciL58HC+KIscK2I+1EiUKIF5mn4IKZ9CIbYdHjPBDoqx4XMevVdwxzhB1OUvTUKJbEJBOCkQKGgFUQsXJY3P1D9gTUGBPHBTypsklatr9GbAqEiYKIHidwBEj1TYPcIKv1LRquL/otRYLv7UmwEPl/Hg6T4lOEKCcAQ==", + ByteStr.empty, + "954bf440a83542e528fe1e650471033e42d97c5896cc571aec39fccc912d7db0", + "534e27c3a787536e18faf844ff217a8f14e5323dfcd3cc5b9ab3a8e261f60cf7" + ), + ( + "data entries", + dataEntries, + "YloKGgFUYP1Q7yDeRXEgffuciL58HC+KIscK2I+1EgUKA2ZvbxISCgNiYXJqC1N0cmluZ1ZhbHVlEiEKA2JhemIaAVRg/VDvIN5FcSB9+5yIvnwcL4oixwrYj7ViLwoaAVRCxcljc/UP2BNQYE8cFPKmySVq2v0ZsCoSCAoDZm9vULAJEgcKA2JhclgB", + ByteStr.empty, + "534e27c3a787536e18faf844ff217a8f14e5323dfcd3cc5b9ab3a8e261f60cf7", + "b1440780a268eeaf9f6bb285a97ee35582cb84382576e84432b2e61b86d64581" + ), + ( + "account script", + accountScript, + "Wi4KIFDHWa9Cd6VU8M20LLFHzbBTveERf1sEOw19SUS40GBoEgcGAQaw0U/PGPoB", + ByteStr.empty, + "b1440780a268eeaf9f6bb285a97ee35582cb84382576e84432b2e61b86d64581", + "ca42620b03b437e025bec14152c3f7d8ff65b8fb1062b5013363186484176cb7" + ), + ( + "asset script", + assetScript, + "QisKIHidwBEj1TYPcIKv1LRquL/otRYLv7UmwEPl/Hg6T4lOEgcGAQaw0U/P", + ByteStr.empty, + "ca42620b03b437e025bec14152c3f7d8ff65b8fb1062b5013363186484176cb7", + "4e9cbb5349a31d2954d57f67d2fc5cf73dd1ce90b508299cf5f92b1b45ca668f" + ), + ( + "new lease", + newLease, + "EiIKGgFUYP1Q7yDeRXEgffuciL58HC+KIscK2I+1GICa4uEQEiIKGgFUQsXJY3P1D9gTUGBPHBTypsklatr9GbAqEICuzb4UGmYKILiCMyyFggW8Zd2LGt/AtMr7WWp+kfWbzlN93pXZqzqNEiBQx1mvQnelVPDNtCyxR82wU73hEX9bBDsNfUlEuNBgaBoaAVRCxcljc/UP2BNQYE8cFPKmySVq2v0ZsCoggPKLqAk=", + ByteStr.empty, + "4e9cbb5349a31d2954d57f67d2fc5cf73dd1ce90b508299cf5f92b1b45ca668f", + "8615df0268bcc76e851a9925e07f212a875a1bd047b0cccbc4ad3d842895f16e" + ), + ( + "cancelled lease", + cancelledLease, + "EiIKGgFUMCPLqLW81X2Atgaj2KwF9QkaJq47Cev9GICo1rkHEhwKGgFUYSJd8vzI9rq7GdIuDy65JMc8zi497E98IiIKILiCMyyFggW8Zd2LGt/AtMr7WWp+kfWbzlN93pXZqzqN", + ByteStr.empty, + "8615df0268bcc76e851a9925e07f212a875a1bd047b0cccbc4ad3d842895f16e", + "3bb24694ea57c1d6b2eec2c549d5ba591853bd7f21959027d2793d5c0846cc8d" + ), + ( + "sponsorship", + sponsorship, + "aiUKIHidwBEj1TYPcIKv1LRquL/otRYLv7UmwEPl/Hg6T4lOEPwq", + ByteStr.empty, + "3bb24694ea57c1d6b2eec2c549d5ba591853bd7f21959027d2793d5c0846cc8d", + "4fd2ceeb81d4d9c7ebad4391fbd938cfc40564088d2cc71801d308d56eca9b75" + ), + ( + "alias", + alias, + "SiYKGgFUQsXJY3P1D9gTUGBPHBTypsklatr9GbAqEgh3YXZlc2V2bw==", + ByteStr.empty, + "4fd2ceeb81d4d9c7ebad4391fbd938cfc40564088d2cc71801d308d56eca9b75", + "0f02911227a9835c1248822f4e500213c4fc4c05a83a5d27680f67d1d1f6a8ee" + ), + ( + "order fill", + volumeAndFee, + "UisKIMkknO8yHpMUT/XKkkdlrbYCG0Dt+qvVgphfgtRbyRDMEICU69wDGNAPUisKIJZ9YwvJObbWItHAD2zhbaFOTFx2zQ4p0Xbo81GXHKeEEICU69wDGNAP", + ByteStr.empty, + "0f02911227a9835c1248822f4e500213c4fc4c05a83a5d27680f67d1d1f6a8ee", + "4d0d2c893b435d1bbc3464c59ceda196961b94a81cfb9bb2c50fe03c06f23d00" + ), + ( + "new asset", + newAsset, + "KkYKIF5mn4IKZ9CIbYdHjPBDoqx4XMevVdwxzhB1OUvTUKJbEiDcYGFqY9MotHTpDpskoycN/Mt62bZfPxIC4fpU0ZTBniABKkYKIHidwBEj1TYPcIKv1LRquL/otRYLv7UmwEPl/Hg6T4lOEiDcYGFqY9MotHTpDpskoycN/Mt62bZfPxIC4fpU0ZTBnhgIMi8KIHidwBEj1TYPcIKv1LRquL/otRYLv7UmwEPl/Hg6T4lOEAEaCQT/////////9jIlCiBeZp+CCmfQiG2HR4zwQ6KseFzHr1XcMc4QdTlL01CiWxoBAQ==", + ByteStr.empty, + "4d0d2c893b435d1bbc3464c59ceda196961b94a81cfb9bb2c50fe03c06f23d00", + "e2baa6d7e863fc1f5f6cec326b01e577c4509e927f3b13ed7818af9075be82c3" + ), + ( + "reissued asset", + reissuedAsset, + "MigKIDhvjT3TTlJ+v4Ni205vcYc1m9WWgnQPFovjmJI1H62yGgQ7msoA", + ByteStr.empty, + "e2baa6d7e863fc1f5f6cec326b01e577c4509e927f3b13ed7818af9075be82c3", + "a161ea70fa027f6127763cdb946606e3d65445915ec26c369e0ff28b37bee8cd" + ), + ( + "renamed asset", + renamedAsset, + "OkMKIHidwBEj1TYPcIKv1LRquL/otRYLv7UmwEPl/Hg6T4lOEgduZXduYW1lGhZzb21lIGZhbmN5IGRlc2NyaXB0aW9u", + ByteStr.empty, + "a161ea70fa027f6127763cdb946606e3d65445915ec26c369e0ff28b37bee8cd", + "7a43e1fb599e8a921ecb2a83b4b871bd46db4569bf5c4f6c9225479191450a58" + ), + ( + "failed transaction", + failedTransaction, + "CiQKGgFUQsXJY3P1D9gTUGBPHBTypsklatr9GbAqEgYQ4PHE1wlwAQ==", + ByteStr(fastHash(Ints.toByteArray(0xaabbef20))), + "7a43e1fb599e8a921ecb2a83b4b871bd46db4569bf5c4f6c9225479191450a58", + "dfa190a84d59edda03428c93c4b1be4c50f12adf3c11528ef0bdd8db1edaf49b" + ), + ( + "elided transaction", + elidedTransaction, + "cAI=", + ByteStr(fastHash(Ints.toByteArray(0xaabbef40))), + "dfa190a84d59edda03428c93c4b1be4c50f12adf3c11528ef0bdd8db1edaf49b", + "002f4f7f3741668c10a8ba92b4b183680fd6659bafd36037be6f9a636510b128" + ), + ( + "all together", + all, + "CkMKGgFUYP1Q7yDeRXEgffuciL58HC+KIscK2I+1EiUKIF5mn4IKZ9CIbYdHjPBDoqx4XMevVdwxzhB1OUvTUKJbEJBOCkQKGgFUQsXJY3P1D9gTUGBPHBTypsklatr9GbAqEiYKIHidwBEj1TYPcIKv1LRquL/otRYLv7UmwEPl/Hg6T4lOEKCcAQokChoBVGD9UO8g3kVxIH37nIi+fBwviiLHCtiPtRIGEICU69wDCiQKGgFUQsXJY3P1D9gTUGBPHBTypsklatr9GbAqEgYQgKjWuQcSIgoaAVRg/VDvIN5FcSB9+5yIvnwcL4oixwrYj7UYgJri4RASIgoaAVRCxcljc/UP2BNQYE8cFPKmySVq2v0ZsCoQgK7NvhQSIgoaAVQwI8uotbzVfYC2BqPYrAX1CRomrjsJ6/0YgKjWuQcSHAoaAVRhIl3y/Mj2ursZ0i4PLrkkxzzOLj3sT3waZgoguIIzLIWCBbxl3Ysa38C0yvtZan6R9ZvOU33eldmrOo0SIFDHWa9Cd6VU8M20LLFHzbBTveERf1sEOw19SUS40GBoGhoBVELFyWNz9Q/YE1BgTxwU8qbJJWra/RmwKiCA8ouoCSIiCiC4gjMshYIFvGXdixrfwLTK+1lqfpH1m85Tfd6V2as6jSpGCiBeZp+CCmfQiG2HR4zwQ6KseFzHr1XcMc4QdTlL01CiWxIg3GBhamPTKLR06Q6bJKMnDfzLetm2Xz8SAuH6VNGUwZ4gASpGCiB4ncARI9U2D3CCr9S0ari/6LUWC7+1JsBD5fx4Ok+JThIg3GBhamPTKLR06Q6bJKMnDfzLetm2Xz8SAuH6VNGUwZ4YCDIvCiB4ncARI9U2D3CCr9S0ari/6LUWC7+1JsBD5fx4Ok+JThABGgkE//////////YyJQogXmafggpn0Ihth0eM8EOirHhcx69V3DHOEHU5S9NQolsaAQEyKAogOG+NPdNOUn6/g2LbTm9xhzWb1ZaCdA8Wi+OYkjUfrbIaBDuaygA6QwogeJ3AESPVNg9wgq/UtGq4v+i1Fgu/tSbAQ+X8eDpPiU4SB25ld25hbWUaFnNvbWUgZmFuY3kgZGVzY3JpcHRpb25KJgoaAVRCxcljc/UP2BNQYE8cFPKmySVq2v0ZsCoSCHdhdmVzZXZvUisKIMkknO8yHpMUT/XKkkdlrbYCG0Dt+qvVgphfgtRbyRDMEICU69wDGNAPUisKIJZ9YwvJObbWItHAD2zhbaFOTFx2zQ4p0Xbo81GXHKeEEICU69wDGNAPWi4KIFDHWa9Cd6VU8M20LLFHzbBTveERf1sEOw19SUS40GBoEgcGAQaw0U/PGPoBYloKGgFUYP1Q7yDeRXEgffuciL58HC+KIscK2I+1EgUKA2ZvbxISCgNiYXJqC1N0cmluZ1ZhbHVlEiEKA2JhemIaAVRg/VDvIN5FcSB9+5yIvnwcL4oixwrYj7ViLwoaAVRCxcljc/UP2BNQYE8cFPKmySVq2v0ZsCoSCAoDZm9vULAJEgcKA2JhclgBaiUKIHidwBEj1TYPcIKv1LRquL/otRYLv7UmwEPl/Hg6T4lOEPwqcAE=", + ByteStr(fastHash(Ints.toByteArray(0xaabbef50))), + "002f4f7f3741668c10a8ba92b4b183680fd6659bafd36037be6f9a636510b128", + "a65304008a49f4ae10cd4af0e61c5d59ba048f0766846d9239d0b28275a0184b" + ) + ) + + property("correctly create transaction state snapshot hash from snapshot") { + forAll(testData) { case (clue, pbSnapshot, b64str, txId, prev, expectedResult) => + withClue(clue) { + TSS.parseFrom(Base64.decode(b64str)) shouldEqual pbSnapshot + + val (snapshot, meta) = PBSnapshots.fromProtobuf(pbSnapshot, txId, 10) + val raw = Hex.toHexString( + TxStateSnapshotHashBuilder + .createHashFromSnapshot(snapshot, Some(TxStateSnapshotHashBuilder.TxStatusInfo(txId, meta))) + .createHash(ByteStr.decodeBase64(prev).get) + .arr + ) + PBSnapshots.toProtobuf(snapshot, meta) shouldEqual pbSnapshot + raw shouldEqual expectedResult + } + } } } diff --git a/node/src/test/scala/com/wavesplatform/state/utils/package.scala b/node/src/test/scala/com/wavesplatform/state/utils/package.scala index 12cecbbd77..dcb7a10e42 100644 --- a/node/src/test/scala/com/wavesplatform/state/utils/package.scala +++ b/node/src/test/scala/com/wavesplatform/state/utils/package.scala @@ -15,13 +15,13 @@ package object utils { def addressTransactions( rdb: RDB, - diff: => Option[(Height, StateSnapshot)], + snapshot: => Option[(Height, StateSnapshot)], address: Address, types: Set[Transaction.Type], fromId: Option[ByteStr] )(implicit s: Scheduler): Seq[(Height, Transaction)] = AddressTransactions - .allAddressTransactions(rdb, diff, address, None, types, fromId) + .allAddressTransactions(rdb, snapshot, address, None, types, fromId) .map { case (tm, tx, _) => tm.height -> tx } .toListL .runSyncUnsafe() diff --git a/node/src/test/scala/com/wavesplatform/test/SharedDomain.scala b/node/src/test/scala/com/wavesplatform/test/SharedDomain.scala index 568730401e..cedd61b6de 100644 --- a/node/src/test/scala/com/wavesplatform/test/SharedDomain.scala +++ b/node/src/test/scala/com/wavesplatform/test/SharedDomain.scala @@ -13,7 +13,7 @@ import com.wavesplatform.{NTPTime, TestHelpers} import org.scalatest.{BeforeAndAfterAll, Suite} trait SharedDomain extends BeforeAndAfterAll with NTPTime with DBCacheSettings { _: Suite => - private val path = Files.createTempDirectory("rocks-temp").toAbsolutePath + private val path = Files.createTempDirectory(s"rocks-temp-${getClass.getSimpleName}").toAbsolutePath private val rdb = RDB.open(dbSettings.copy(directory = path.toAbsolutePath.toString)) private val (bui, ldb) = TestStorageFactory(settings, rdb, ntpTime, BlockchainUpdateTriggers.noop) diff --git a/node/src/test/scala/com/wavesplatform/transaction/ChainIdSpecification.scala b/node/src/test/scala/com/wavesplatform/transaction/ChainIdSpecification.scala index 4e0134f973..f36d379cc8 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/ChainIdSpecification.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/ChainIdSpecification.scala @@ -19,24 +19,21 @@ import com.wavesplatform.transaction.transfer.{MassTransferTransaction, Transfer import org.scalacheck.Gen class ChainIdSpecification extends PropSpec { - private val otherChainId = 'W'.toByte private val aliasFromOther = Alias.createWithChainId("sasha", otherChainId, Some(otherChainId)).explicitGet() private val addressFromOther = Address.fromBytes(Base58.tryDecodeWithLimit("3P3oxTkpCWJgCr6SJrBzdP5N8jFqHCiy7L2").get, Some(otherChainId)).explicitGet() private val addressOrAlias = Gen.oneOf(aliasFromOther, addressFromOther) - private def addressOrAliasWithVersion( - vs: Set[TxVersion] - ): Gen[(AddressOrAlias, TxVersion, KeyPair, TxPositiveAmount, TxPositiveAmount, TxTimestamp)] = + private def addressOrAliasWithVersion: Gen[(AddressOrAlias, TxVersion, KeyPair, TxPositiveAmount, TxPositiveAmount, TxTimestamp)] = for { addressOrAlias <- addressOrAlias - version <- Gen.oneOf(vs.toSeq) + version <- Gen.oneOf(1, 2, 3) sender <- accountGen amount <- Gen.choose(1, 10000000L) fee <- Gen.choose(1000000L, 10000000L) ts <- Gen.choose(1, 1000000L) - } yield (addressOrAlias, version, sender, TxPositiveAmount.unsafeFrom(amount), TxPositiveAmount.unsafeFrom(fee), ts) + } yield (addressOrAlias, version.toByte, sender, TxPositiveAmount.unsafeFrom(amount), TxPositiveAmount.unsafeFrom(fee), ts) private def validateFromOtherNetwork(tx: Transaction): Unit = { tx.chainId should not be AddressScheme.current.chainId @@ -53,7 +50,7 @@ class ChainIdSpecification extends PropSpec { } property("TransferTransaction validation") { - forAll(addressOrAliasWithVersion(TransferTransaction.supportedVersions)) { case (addressOrAlias, version, sender, amount, fee, ts) => + forAll(addressOrAliasWithVersion) { case (addressOrAlias, version, sender, amount, fee, ts) => TransferTransaction( version, sender.publicKey, @@ -87,7 +84,7 @@ class ChainIdSpecification extends PropSpec { } property("PaymentTransaction validation") { - forAll(addressOrAliasWithVersion(PaymentTransaction.supportedVersions)) { case (_, _, sender, amount, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, sender, amount, fee, ts) => PaymentTransaction( sender.publicKey, addressFromOther, @@ -113,7 +110,7 @@ class ChainIdSpecification extends PropSpec { } property("LeaseTransaction validation") { - forAll(addressOrAliasWithVersion(LeaseTransaction.supportedVersions)) { case (addressOrAlias, version, sender, amount, fee, ts) => + forAll(addressOrAliasWithVersion) { case (addressOrAlias, version, sender, amount, fee, ts) => LeaseTransaction( version, sender.publicKey, @@ -141,7 +138,7 @@ class ChainIdSpecification extends PropSpec { } property("InvokeScriptTransaction validation") { - forAll(addressOrAliasWithVersion(InvokeScriptTransaction.supportedVersions)) { case (addressOrAlias, version, sender, _, fee, ts) => + forAll(addressOrAliasWithVersion) { case (addressOrAlias, version, sender, _, fee, ts) => InvokeScriptTransaction( version, sender.publicKey, @@ -173,7 +170,7 @@ class ChainIdSpecification extends PropSpec { } property("GenesisTransaction validation") { - forAll(addressOrAliasWithVersion(GenesisTransaction.supportedVersions)) { case (_, _, _, amount, _, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, _, amount, _, ts) => GenesisTransaction( addressFromOther, TxNonNegativeAmount.unsafeFrom(amount.value), @@ -185,7 +182,7 @@ class ChainIdSpecification extends PropSpec { } property("BurnTransaction validation") { - forAll(addressOrAliasWithVersion(BurnTransaction.supportedVersions)) { case (_, _, sender, amount, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, sender, amount, fee, ts) => validateFromOtherNetwork( BurnTransaction( TxVersion.V3, @@ -202,7 +199,7 @@ class ChainIdSpecification extends PropSpec { } property("CreateAliasTransaction validation") { - forAll(addressOrAliasWithVersion(CreateAliasTransaction.supportedVersions)) { case (_, _, sender, _, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, sender, _, fee, ts) => validateFromOtherNetwork( CreateAliasTransaction( TxVersion.V3, @@ -218,7 +215,7 @@ class ChainIdSpecification extends PropSpec { } property("DataTransaction validation") { - forAll(addressOrAliasWithVersion(DataTransaction.supportedVersions)) { case (_, _, sender, _, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, sender, _, fee, ts) => validateFromOtherNetwork( DataTransaction( TxVersion.V2, @@ -234,7 +231,7 @@ class ChainIdSpecification extends PropSpec { } property("ExchangeTransaction validation") { - forAll(addressOrAliasWithVersion(ExchangeTransaction.supportedVersions)) { case (_, _, sender, amount, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, sender, amount, fee, ts) => val pair = AssetPair(Waves, IssuedAsset(ByteStr(bytes32gen.sample.get))) validateFromOtherNetwork( ExchangeTransaction( @@ -255,7 +252,7 @@ class ChainIdSpecification extends PropSpec { } property("IssueTransaction validation") { - forAll(addressOrAliasWithVersion(IssueTransaction.supportedVersions)) { case (_, _, sender, quantity, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, sender, quantity, fee, ts) => validateFromOtherNetwork( IssueTransaction( TxVersion.V3, @@ -276,7 +273,7 @@ class ChainIdSpecification extends PropSpec { } property("LeaseCancelTransaction validation") { - forAll(addressOrAliasWithVersion(LeaseCancelTransaction.supportedVersions)) { case (_, _, sender, _, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, sender, _, fee, ts) => validateFromOtherNetwork( LeaseCancelTransaction( TxVersion.V3, @@ -292,7 +289,7 @@ class ChainIdSpecification extends PropSpec { } property("MassTransferTransaction validation") { - forAll(addressOrAliasWithVersion(MassTransferTransaction.supportedVersions)) { case (addressOrAlias, _, sender, amount, fee, ts) => + forAll(addressOrAliasWithVersion) { case (addressOrAlias, _, sender, amount, fee, ts) => validateFromOtherNetwork( MassTransferTransaction( TxVersion.V2, @@ -310,7 +307,7 @@ class ChainIdSpecification extends PropSpec { } property("ReissueTransaction validation") { - forAll(addressOrAliasWithVersion(ReissueTransaction.supportedVersions)) { case (_, _, sender, quantity, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, sender, quantity, fee, ts) => validateFromOtherNetwork( ReissueTransaction( TxVersion.V3, @@ -328,7 +325,7 @@ class ChainIdSpecification extends PropSpec { } property("SetAssetScriptTransaction validation") { - forAll(addressOrAliasWithVersion(SetAssetScriptTransaction.supportedVersions)) { case (_, _, sender, _, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, sender, _, fee, ts) => validateFromOtherNetwork( SetAssetScriptTransaction( TxVersion.V2, @@ -345,7 +342,7 @@ class ChainIdSpecification extends PropSpec { } property("SetScriptTransaction validation") { - forAll(addressOrAliasWithVersion(SetScriptTransaction.supportedVersions)) { case (_, _, sender, _, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, sender, _, fee, ts) => validateFromOtherNetwork( SetScriptTransaction( TxVersion.V2, @@ -361,7 +358,7 @@ class ChainIdSpecification extends PropSpec { } property("SponsorFeeTransaction validation") { - forAll(addressOrAliasWithVersion(SponsorFeeTransaction.supportedVersions)) { case (_, _, sender, _, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, _, sender, _, fee, ts) => validateFromOtherNetwork( SponsorFeeTransaction( TxVersion.V2, @@ -378,7 +375,7 @@ class ChainIdSpecification extends PropSpec { } property("UpdateAssetInfoTransaction validation") { - forAll(addressOrAliasWithVersion(UpdateAssetInfoTransaction.supportedVersions)) { case (_, version, sender, _, fee, ts) => + forAll(addressOrAliasWithVersion) { case (_, version, sender, _, fee, ts) => validateFromOtherNetwork( UpdateAssetInfoTransaction( version, diff --git a/node/src/test/scala/com/wavesplatform/transaction/MassTransferTransactionSpecification.scala b/node/src/test/scala/com/wavesplatform/transaction/MassTransferTransactionSpecification.scala index f688277c1e..bfd762e24e 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/MassTransferTransactionSpecification.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/MassTransferTransactionSpecification.scala @@ -67,7 +67,7 @@ class MassTransferTransactionSpecification extends PropSpec { ) val bytes = tx.bytes() val recovered = { - if (tx.isProtobufVersion) PBTransactionSerializer.parseBytes(bytes) + if (PBSince.affects(tx)) PBTransactionSerializer.parseBytes(bytes) else MassTransferTransaction.parseBytes(bytes) }.get recovered shouldEqual tx @@ -125,7 +125,7 @@ class MassTransferTransactionSpecification extends PropSpec { proofs = Proofs(proofs), chainId = chainId ) - if (!tx.isProtobufVersion && tx.chainId == AddressScheme.current.chainId) { + if (!PBSince.affects(tx) && tx.chainId == AddressScheme.current.chainId) { val recovered = TransactionParsers.parseBytes(tx.bytes()).get recovered.bytes() shouldEqual tx.bytes() } diff --git a/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala index e1aebcc959..6d396c8f64 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala @@ -36,12 +36,12 @@ object TxHelpers { def signer(i: Int): SeedKeyPair = KeyPair(Ints.toByteArray(i)) def address(i: Int): Address = signer(i).toAddress - def defaultSigner: SeedKeyPair = signer(0) - def defaultAddress: Address = defaultSigner.toAddress - def secondSigner: SeedKeyPair = signer(1) - def secondAddress: Address = secondSigner.toAddress + val defaultSigner: SeedKeyPair = signer(0) + val defaultAddress: Address = defaultSigner.toAddress + val secondSigner: SeedKeyPair = signer(1) + val secondAddress: Address = secondSigner.toAddress - def defaultEthSigner: ECKeyPair = defaultSigner.toEthKeyPair + val defaultEthSigner: ECKeyPair = defaultSigner.toEthKeyPair def accountSeqGenerator(numberAccounts: Int, amount: Long): Seq[ParsedTransfer] = { val firstAccountNum = 100 @@ -107,14 +107,16 @@ object TxHelpers { def massTransfer( from: KeyPair = defaultSigner, - to: Seq[ParsedTransfer] = Seq(ParsedTransfer(secondAddress, TxNonNegativeAmount.unsafeFrom(1.waves))), + to: Seq[(AddressOrAlias, Long)] = Seq(secondAddress -> 1.waves), asset: Asset = Waves, fee: Long = FeeConstants(TransactionType.MassTransfer) * FeeUnit, timestamp: TxTimestamp = timestamp, version: Byte = TxVersion.V2, chainId: Byte = AddressScheme.current.chainId ): MassTransferTransaction = - MassTransferTransaction.selfSigned(version, from, asset, to, fee, timestamp, ByteStr.empty, chainId).explicitGet() + MassTransferTransaction.selfSigned(version, from, asset, + to.map { case (r, a) => MassTransferTransaction.ParsedTransfer(r, TxNonNegativeAmount.unsafeFrom(a)) }, + fee, timestamp, ByteStr.empty, chainId).explicitGet() def issue( issuer: KeyPair = defaultSigner, diff --git a/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/EthOrderSpec.scala b/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/EthOrderSpec.scala index 16080b9d34..d5884cfe62 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/EthOrderSpec.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/assets/exchange/EthOrderSpec.scala @@ -6,7 +6,6 @@ import com.wavesplatform.test.{FlatSpec, TestTime} import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.BlockchainStubHelpers import com.wavesplatform.common.utils.* -import com.wavesplatform.history.SnapshotOps.TransactionStateSnapshotExt import com.wavesplatform.state.diffs.TransactionDiffer import com.wavesplatform.transaction.{TxExchangeAmount, TxHelpers, TxMatcherFee, TxOrderPrice, TxVersion} import com.wavesplatform.utils.{DiffMatchers, EthEncoding, EthHelpers, JsonMatchers} @@ -142,8 +141,8 @@ class EthOrderSpec val differ = blockchain.stub.transactionDiffer(TestTime(100)) _ val transaction = TxHelpers.exchange(ethBuyOrder, ethSellOrder, price = 100, version = TxVersion.V3, timestamp = 100) - val diff = differ(transaction).resultE.explicitGet() - diff should containAppliedTx(transaction.id()) + val snapshot = differ(transaction).resultE.explicitGet() + snapshot should containAppliedTx(transaction.id()) } it should "work in exchange transaction with an old order" in { @@ -175,8 +174,8 @@ class EthOrderSpec val differ = TransactionDiffer(Some(1L), 100L)(blockchain, _) val transaction = TxHelpers.exchange(buyOrder, ethSellOrder, price = 100, version = TxVersion.V3, timestamp = 100) - val diff = differ(transaction).resultE.explicitGet().toDiff(blockchain) - diff should containAppliedTx(transaction.id()) + val snapshot = differ(transaction).resultE.explicitGet() + snapshot should containAppliedTx(transaction.id()) } it should "recover valid ids of exchange tx" in { @@ -354,8 +353,8 @@ class EthOrderSpec val differ = TransactionDiffer(Some(1L), 100L)(blockchain, _) val transaction = TxHelpers.exchange(buyOrder, ethSellOrder, price = 100, version = TxVersion.V3, timestamp = 100) - val diff = differ(transaction).resultE.explicitGet().toDiff(blockchain) - diff should containAppliedTx(transaction.id()) + val snapshot = differ(transaction).resultE.explicitGet() + snapshot should containAppliedTx(transaction.id()) } it should "work in exchange transaction with matcher script" in { @@ -393,8 +392,8 @@ class EthOrderSpec val differ = blockchain.stub.transactionDiffer(TestTime(100)) _ val transaction = TxHelpers.exchange(ethBuyOrder, ethSellOrder, price = 100, version = TxVersion.V3, timestamp = 100) - val diff = differ(transaction).resultE.explicitGet() - diff should containAppliedTx(transaction.id()) + val snapshot = differ(transaction).resultE.explicitGet() + snapshot should containAppliedTx(transaction.id()) } it should "be serialized correctly to EIP-712 json with and without attachment (NODE-996)" in { diff --git a/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionSpec.scala b/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionSpec.scala index 339036b83a..c3d1e65655 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionSpec.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionSpec.scala @@ -4,7 +4,6 @@ import com.wavesplatform.account.AddressScheme import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.* import com.wavesplatform.features.BlockchainFeatures -import com.wavesplatform.state.Portfolio import com.wavesplatform.state.diffs.produceRejectOrFailedDiff import com.wavesplatform.test.{FlatSpec, TestTime, produce} import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} @@ -106,9 +105,14 @@ class EthereumTransactionSpec val transfer = EthTxGenerator.generateEthTransfer(senderAccount, recipientAddress, LongMaxMinusFee, Waves) val assetTransfer = EthTxGenerator.generateEthTransfer(senderAccount, recipientAddress, Long.MaxValue, TestAsset) - differ(transfer).combineF(differ(assetTransfer)).explicitGet().portfolios shouldBe Map( - senderAddress -> Portfolio.build(-Long.MaxValue, TestAsset, -Long.MaxValue), - recipientAddress -> Portfolio.build(LongMaxMinusFee, TestAsset, Long.MaxValue) + differ(assetTransfer).balances shouldBe Map( + (senderAddress, TestAsset) -> 0, + (senderAddress, Waves) -> (LongMaxMinusFee + transfer.fee.longValue()), + (recipientAddress, TestAsset) -> Long.MaxValue + ) + differ(transfer).balances shouldBe Map( + (senderAddress, Waves) -> transfer.fee.longValue(), + (recipientAddress, Waves) -> LongMaxMinusFee ) } @@ -236,23 +240,23 @@ class EthereumTransactionSpec ), Seq(Payment(321, IssuedAsset(ByteStr(EthStubBytes32)))) ) - val diff = differ(transaction).resultE.explicitGet() - diff should containAppliedTx(transaction.id()) - Json.toJson(diff.scriptResults.values.head) should matchJson("""{ - | "data" : [ ], - | "transfers" : [ { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : null, - | "amount" : 123 - | } ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - |}""".stripMargin) + val snapshot = differ(transaction).resultE.explicitGet() + snapshot should containAppliedTx(transaction.id()) + Json.toJson(snapshot.scriptResults.values.head) should matchJson("""{ + | "data" : [ ], + | "transfers" : [ { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : null, + | "amount" : 123 + | } ], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + |}""".stripMargin) } it should "not work with union type" in { @@ -291,8 +295,8 @@ class EthereumTransactionSpec Seq(Payment(321, IssuedAsset(ByteStr(EthStubBytes32)))) ) - val diff = differ(transaction).resultE - diff should produce("Function not defined: 1f9773e9") + val snapshot = differ(transaction).resultE + snapshot should produce("Function not defined: 1f9773e9") } it should "work with no arguments" in { @@ -332,23 +336,23 @@ class EthereumTransactionSpec Seq(), Seq(Payment(321, IssuedAsset(ByteStr(EthStubBytes32)))) ) - val diff = differ(transaction).resultE.explicitGet() - diff should containAppliedTx(transaction.id()) - Json.toJson(diff.scriptResults.values.head) should matchJson("""{ - | "data" : [ ], - | "transfers" : [ { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : null, - | "amount" : 123 - | } ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - |}""".stripMargin) + val snapshot = differ(transaction).resultE.explicitGet() + snapshot should containAppliedTx(transaction.id()) + Json.toJson(snapshot.scriptResults.values.head) should matchJson("""{ + | "data" : [ ], + | "transfers" : [ { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : null, + | "amount" : 123 + | } ], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + |}""".stripMargin) } it should "work with no payments" in { @@ -382,23 +386,23 @@ class EthereumTransactionSpec val differ = blockchain.stub.transactionDiffer(TestTime(System.currentTimeMillis())) val transaction = EthTxGenerator.generateEthInvoke(invokerAccount, dAppAccount.toAddress, "deposit", Seq(), Seq()) - val diff = differ(transaction).resultE.explicitGet() - diff should containAppliedTx(transaction.id()) - Json.toJson(diff.scriptResults.values.head) should matchJson("""{ - | "data" : [ ], - | "transfers" : [ { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : null, - | "amount" : 123 - | } ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - |}""".stripMargin) + val snapshot = differ(transaction).resultE.explicitGet() + snapshot should containAppliedTx(transaction.id()) + Json.toJson(snapshot.scriptResults.values.head) should matchJson("""{ + | "data" : [ ], + | "transfers" : [ { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : null, + | "amount" : 123 + | } ], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + |}""".stripMargin) } it should "fail with max+1 payments" in { @@ -478,23 +482,23 @@ class EthereumTransactionSpec Seq(), Seq(Payment(321, IssuedAsset(ByteStr(EthStubBytes32)))) ) - val diff = differ(transaction).resultE.explicitGet() - diff should containAppliedTx(transaction.id()) - Json.toJson(diff.scriptResults.values.head) should matchJson("""{ - | "data" : [ ], - | "transfers" : [ { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : null, - | "amount" : 123 - | } ], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - |}""".stripMargin) + val snapshot = differ(transaction).resultE.explicitGet() + snapshot should containAppliedTx(transaction.id()) + Json.toJson(snapshot.scriptResults.values.head) should matchJson("""{ + | "data" : [ ], + | "transfers" : [ { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : null, + | "amount" : 123 + | } ], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + |}""".stripMargin) } it should "return money in transfers asset+waves" in { @@ -535,28 +539,28 @@ class EthereumTransactionSpec Seq(), Nil ) - val diff = differ(transaction).resultE.explicitGet() - diff should containAppliedTx(transaction.id()) - Json.toJson(diff.scriptResults.values.head) should matchJson(s"""{ - | "data" : [ ], - | "transfers" : [ { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : null, - | "amount" : 123 - | }, - | { - | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", - | "asset" : "$TestAsset", - | "amount" : 123 - | }], - | "issues" : [ ], - | "reissues" : [ ], - | "burns" : [ ], - | "sponsorFees" : [ ], - | "leases" : [ ], - | "leaseCancels" : [ ], - | "invokes" : [ ] - |}""".stripMargin) + val snapshot = differ(transaction).resultE.explicitGet() + snapshot should containAppliedTx(transaction.id()) + Json.toJson(snapshot.scriptResults.values.head) should matchJson(s"""{ + | "data" : [ ], + | "transfers" : [ { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : null, + | "amount" : 123 + | }, + | { + | "address" : "3NByUD1YE9SQPzmf2KqVqrjGMutNSfc4oBC", + | "asset" : "$TestAsset", + | "amount" : 123 + | }], + | "issues" : [ ], + | "reissues" : [ ], + | "burns" : [ ], + | "sponsorFees" : [ ], + | "leases" : [ ], + | "leaseCancels" : [ ], + | "invokes" : [ ] + |}""".stripMargin) } it should "test minimum fee" in { diff --git a/node/src/test/scala/com/wavesplatform/transaction/smart/script/estimator/FunctionComplexityTest.scala b/node/src/test/scala/com/wavesplatform/transaction/smart/script/estimator/FunctionComplexityTest.scala index 3a4d9ab730..e8e1a4ca84 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/smart/script/estimator/FunctionComplexityTest.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/smart/script/estimator/FunctionComplexityTest.scala @@ -106,7 +106,7 @@ class FunctionComplexityTest(estimator: ScriptEstimator) extends PropSpec { property("estimate script with all functions") { def check(version: StdLibVersion, expectedCost: Int) = { - val expr = ExpressionCompiler(ctx(version).compilerContext, getAllFuncExpression(version)).explicitGet()._1 + val expr = ExpressionCompiler(ctx(version).compilerContext, version, getAllFuncExpression(version)).explicitGet()._1 estimate(expr, ctx(version), utils.functionCosts(version)) shouldBe Right(expectedCost) } diff --git a/node/src/test/scala/com/wavesplatform/utils/DiffMatchers.scala b/node/src/test/scala/com/wavesplatform/utils/DiffMatchers.scala index 45b07ec9eb..27eded1744 100644 --- a/node/src/test/scala/com/wavesplatform/utils/DiffMatchers.scala +++ b/node/src/test/scala/com/wavesplatform/utils/DiffMatchers.scala @@ -1,25 +1,24 @@ package com.wavesplatform.utils import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.state.Diff -import com.wavesplatform.state.TxMeta.Status +import com.wavesplatform.state.StateSnapshot +import com.wavesplatform.state.TxMeta.Status.Succeeded import org.scalatest.matchers.{MatchResult, Matcher} trait DiffMatchers { def containAppliedTx(transactionId: ByteStr) = new DiffAppliedTxMatcher(transactionId, true) def containFailedTx(transactionId: ByteStr) = new DiffAppliedTxMatcher(transactionId, false) - class DiffAppliedTxMatcher(transactionId: ByteStr, shouldBeApplied: Boolean) extends Matcher[Diff] { - override def apply(diff: Diff): MatchResult = { - val isApplied = diff.transaction(transactionId) match { - case Some(nt) if nt.status == Status.Succeeded => true - case _ => false + class DiffAppliedTxMatcher(transactionId: ByteStr, shouldBeApplied: Boolean) extends Matcher[StateSnapshot] { + override def apply(snapshot: StateSnapshot): MatchResult = { + val isApplied = snapshot.transactions.get(transactionId) match { + case Some(nt) if nt.status == Succeeded => true + case _ => false } - MatchResult( shouldBeApplied == isApplied, - s"$transactionId was not ${if (shouldBeApplied) "applied" else "failed"}: $diff", - s"$transactionId was ${if (shouldBeApplied) "applied" else "failed"}: $diff" + s"$transactionId was not ${if (shouldBeApplied) "applied" else "failed"}: $snapshot", + s"$transactionId was ${if (shouldBeApplied) "applied" else "failed"}: $snapshot" ) } } diff --git a/node/src/test/scala/com/wavesplatform/utils/EmptyBlockchain.scala b/node/src/test/scala/com/wavesplatform/utils/EmptyBlockchain.scala index 6ee1556f2d..27f41f53e8 100644 --- a/node/src/test/scala/com/wavesplatform/utils/EmptyBlockchain.scala +++ b/node/src/test/scala/com/wavesplatform/utils/EmptyBlockchain.scala @@ -7,7 +7,6 @@ import com.wavesplatform.common.state.ByteStr import com.wavesplatform.lang.ValidationError import com.wavesplatform.settings.BlockchainSettings import com.wavesplatform.state.* -import com.wavesplatform.state.reader.LeaseDetails import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.transfer.TransferTransactionLike diff --git a/node/src/test/scala/com/wavesplatform/utils/ObservedLoadingCacheSpecification.scala b/node/src/test/scala/com/wavesplatform/utils/ObservedLoadingCacheSpecification.scala index ac355cad70..fab2f0fa2d 100644 --- a/node/src/test/scala/com/wavesplatform/utils/ObservedLoadingCacheSpecification.scala +++ b/node/src/test/scala/com/wavesplatform/utils/ObservedLoadingCacheSpecification.scala @@ -22,41 +22,41 @@ class ObservedLoadingCacheSpecification extends FreeSpec with MockFactory { "on refresh" in test { (loadingCache, changes, _) => (changes.onNext _).expects("foo").returning(Future.successful(Ack.Continue)).once() - loadingCache.refresh("foo") + loadingCache.refresh("foo") } "on put" in test { (loadingCache, changes, _) => (changes.onNext _).expects("foo").returning(Future.successful(Ack.Continue)).once() - loadingCache.put("foo", 10) + loadingCache.put("foo", 10) } "on putAll" in test { (loadingCache, changes, _) => (changes.onNext _).expects("foo").returning(Future.successful(Ack.Continue)).once() (changes.onNext _).expects("bar").returning(Future.successful(Ack.Continue)).once() - loadingCache.putAll(Map[String, Integer]("foo" -> 10, "bar" -> 11).asJava) + loadingCache.putAll(Map[String, Integer]("foo" -> 10, "bar" -> 11).asJava) } "on invalidate" in test { (loadingCache, changes, _) => (changes.onNext _).expects("foo").returning(Future.successful(Ack.Continue)).once() - loadingCache.invalidate("foo") + loadingCache.invalidate("foo") } "on invalidateAll" in test { (loadingCache, changes, _) => (changes.onNext _).expects("foo").returning(Future.successful(Ack.Continue)).once() (changes.onNext _).expects("bar").returning(Future.successful(Ack.Continue)).once() - loadingCache.invalidateAll(Seq("foo", "bar").asJava) + loadingCache.invalidateAll(Seq("foo", "bar").asJava) } } "don't notify" - { "on cache expiration" in test { (loadingCache, changes, ticker) => (changes.onNext _).expects("foo").returning(Future.successful(Ack.Continue)).once() - loadingCache.put("foo", 1) - ticker.advance(ExpiringTime.toMillis + 100, TimeUnit.MILLISECONDS) + loadingCache.put("foo", 1) + ticker.advance(ExpiringTime.toMillis + 100, TimeUnit.MILLISECONDS) } } @@ -73,6 +73,7 @@ class ObservedLoadingCacheSpecification extends FreeSpec with MockFactory { }) val loadingCache = new ObservedLoadingCache(delegate, changes) + f(loadingCache, changes, ticker) } } diff --git a/node/src/test/scala/com/wavesplatform/utx/UtxPoolSpecification.scala b/node/src/test/scala/com/wavesplatform/utx/UtxPoolSpecification.scala index 5c2668e382..52c8f395c4 100644 --- a/node/src/test/scala/com/wavesplatform/utx/UtxPoolSpecification.scala +++ b/node/src/test/scala/com/wavesplatform/utx/UtxPoolSpecification.scala @@ -14,7 +14,7 @@ import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.events.UtxEvent import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.history.Domain.BlockchainUpdaterExt -import com.wavesplatform.history.{DefaultWavesSettings, SnapshotOps, randomSig, settingsWithFeatures} +import com.wavesplatform.history.{DefaultWavesSettings, randomSig, settingsWithFeatures} import com.wavesplatform.lagonaki.mocks.TestBlock import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.script.Script @@ -52,7 +52,7 @@ import scala.util.{Random, Using} private object UtxPoolSpecification { final case class TempDB(fs: FunctionalitySettings, dbSettings: DBSettings) extends AutoCloseable { - val path: Path = Files.createTempDirectory("rocksdb-test") + val path: Path = Files.createTempDirectory("rocksdb-test-utx") val rdb = RDB.open(dbSettings.copy(directory = path.toAbsolutePath.toString)) val writer: RocksDBWriter = TestRocksDB.withFunctionalitySettings(rdb, fs) @@ -1011,7 +1011,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact } "cleanup" - { - "doesnt take the composite diff into account" in withDomain() { d => + "doesnt take the composite snapshot into account" in withDomain() { d => d.helpers.creditWavesToDefaultSigner(11.waves) val transfers = Seq.fill(10)(TxHelpers.transfer(amount = 10.waves)) transfers.foreach(tx => d.utxPool.addTransaction(tx, verify = false)) @@ -1024,7 +1024,7 @@ class UtxPoolSpecification extends FreeSpec with MockFactory with BlocksTransact val transfer1 = TxHelpers.transfer(amount = 10.waves) val transfer2 = TxHelpers.transfer(amount = 10.waves) // Double spend - d.utxPool.priorityPool.setPriorityDiffs(Seq(SnapshotOps.fromDiff(d.createDiff(transfer1), d.blockchain).explicitGet())) + d.utxPool.priorityPool.setPriorityDiffs(Seq(d.createDiff(transfer1))) d.utxPool.addTransaction(transfer2, verify = false) d.utxPool.cleanUnconfirmed() diff --git a/node/src/test/scala/com/wavesplatform/utx/UtxPriorityPoolSpecification.scala b/node/src/test/scala/com/wavesplatform/utx/UtxPriorityPoolSpecification.scala index 1d6970800a..a4d174b353 100644 --- a/node/src/test/scala/com/wavesplatform/utx/UtxPriorityPoolSpecification.scala +++ b/node/src/test/scala/com/wavesplatform/utx/UtxPriorityPoolSpecification.scala @@ -107,7 +107,7 @@ class UtxPriorityPoolSpecification extends FreeSpec with SharedDomain { domain.utxPool.priorityPool.priorityTransactions shouldBe empty } - "continues packing when priority diff contains no valid transactions" in { + "continues packing when priority snapshot contains no valid transactions" in { val bob = nextKeyPair domain.appendBlock( TxHelpers.transfer(alice, bob.toAddress, 10.02.waves, fee = 0.001.waves), diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 9d5b08bc4a..1b502ecd74 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -97,13 +97,13 @@ object Dependencies { akkaModule("slf4j") % Runtime ) - private[this] val dbDeps = - Seq( - "org.rocksdb" % "rocksdbjni" % "8.5.4" - ) + private val rocksdb = "org.rocksdb" % "rocksdbjni" % "8.5.4" + + private val scalapbJson = "com.thesamet.scalapb" %% "scalapb-json4s" % "0.11.1" lazy val node = Def.setting( Seq( + rocksdb, ("org.rudogma" %%% "supertagged" % "2.0-RC2").exclude("org.scala-js", "scalajs-library_2.13"), "commons-net" % "commons-net" % "3.10.0", "com.iheart" %% "ficus" % "1.5.2", @@ -126,12 +126,11 @@ object Dependencies { nettyHandler, "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5", "eu.timepit" %% "refined" % "0.11.0" exclude ("org.scala-lang.modules", "scala-xml_2.13"), - "eu.timepit" %% "refined-cats" % "0.11.0" exclude ("org.scala-lang.modules", "scala-xml_2.13"), "com.esaulpaugh" % "headlong" % "9.4.0", web3jModule("abi"), akkaModule("testkit") % Test, - akkaHttpModule("akka-http-testkit") % Test - ) ++ test ++ console ++ logDeps ++ dbDeps ++ protobuf.value ++ langCompilerPlugins.value + akkaHttpModule("akka-http-testkit") % Test, + ) ++ test ++ console ++ logDeps ++ protobuf.value ++ langCompilerPlugins.value ) val gProto = "com.google.protobuf" % "protobuf-java" % "3.24.4" @@ -158,8 +157,8 @@ object Dependencies { lazy val rideRunner = Def.setting( Seq( - "org.rocksdb" % "rocksdbjni" % "8.3.2", - "com.thesamet.scalapb" %% "scalapb-json4s" % "0.11.1", + rocksdb, + scalapbJson, // https://github.com/netty/netty/wiki/Native-transports // "io.netty" % "netty-transport-native-epoll" % "4.1.79.Final" classifier "linux-x86_64", "com.github.ben-manes.caffeine" % "caffeine" % "3.1.2", diff --git a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/Repl.scala b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/Repl.scala index b4872df83f..f5a3e00390 100644 --- a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/Repl.scala +++ b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/Repl.scala @@ -51,13 +51,13 @@ case class Repl( currentState, view, (oldCtx: (CompilerContext, EvaluationContext[Environment, Future])) => - engine.eval(expr, oldCtx._1, oldCtx._2).map { + engine.eval(expr, version, oldCtx._1, oldCtx._2).map { case Left(e) => (Left(e), oldCtx) case Right((r, newCtx)) => (Right(r), newCtx) }: Future[(Either[String, String], (CompilerContext, EvaluationContext[Environment, Future]))] ) - def initLibraries(): Unit = { + private def initLibraries(): Unit = { val libraryState = state( ( initialCtx.compilerContext, @@ -72,7 +72,7 @@ case class Repl( view, oldCtx => new ReplEngine[Id]() - .eval(libraries.mkString("\n"), oldCtx._1, oldCtx._2) + .eval(libraries.mkString("\n"), version, oldCtx._1, oldCtx._2) .fold( e => throw new RuntimeException(e), { case (r, ctx @ (compilerCtx, evaluationCtx)) => diff --git a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala index 270f823057..927df4b029 100644 --- a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala +++ b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala @@ -3,6 +3,7 @@ package com.wavesplatform.lang.v1.repl import cats.Monad import cats.data.EitherT import cats.implicits.* +import com.wavesplatform.lang.directives.values.StdLibVersion import com.wavesplatform.lang.v1.compiler.CompilerContext.VariableInfo import com.wavesplatform.lang.v1.compiler.Terms.EVALUATED import com.wavesplatform.lang.v1.compiler.Types.{FINAL, UNIT} @@ -20,13 +21,14 @@ class ReplEngine[F[_]: Monad] { def eval( expr: String, + version: StdLibVersion, compileCtx: CompilerContext, evalCtx: EvaluationContext[Environment, F] ): F[Either[String, (String, (CompilerContext, EvaluationContext[Environment, F]))]] = { val r = for { parsed <- EitherT.fromEither[F](parse(expr)) - (newCompileCtx, compiled, exprType) <- EitherT.fromEither[F](ExpressionCompiler.applyWithCtx(compileCtx, parsed)) + (newCompileCtx, compiled, exprType) <- EitherT.fromEither[F](ExpressionCompiler.applyWithCtx(compileCtx, version, parsed)) evaluated <- EitherT(evaluator.applyWithCtx(evalCtx, compiled)).leftMap(error => if (error.message.isEmpty) "Evaluation error" else error.message ) diff --git a/ride-runner/src/main/scala/com/wavesplatform/database/rocksdb/package.scala b/ride-runner/src/main/scala/com/wavesplatform/database/rocksdb/package.scala index 815cd6bce9..b05dbdf68e 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/database/rocksdb/package.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/database/rocksdb/package.scala @@ -15,8 +15,8 @@ import com.wavesplatform.lang.script.ScriptReader import com.wavesplatform.protobuf.transaction.PBRecipients import com.wavesplatform.protobuf.{ByteStrExt, ByteStringExt} import com.wavesplatform.state.* -import com.wavesplatform.state.reader.LeaseDetails import com.wavesplatform.transaction +import com.wavesplatform.transaction.TxPositiveAmount import com.wavesplatform.utils.* import monix.eval.Task import monix.reactive.Observable @@ -131,16 +131,16 @@ package object rocksdb { ld => pb.LeaseDetails( ByteString.copyFrom(ld.sender.arr), - Some(PBRecipients.create(ld.recipient)), - ld.amount, + Some(PBRecipients.create(ld.recipientAddress)), + ld.amount.value, ByteString.copyFrom(ld.sourceId.arr), ld.height, ld.status match { - case LeaseDetails.Status.Active => pb.LeaseDetails.Status.Active(com.google.protobuf.empty.Empty()) + case LeaseDetails.Status.Active => pb.LeaseDetails.CancelReason.Empty case LeaseDetails.Status.Cancelled(height, cancelTxId) => - pb.LeaseDetails.Status + pb.LeaseDetails.CancelReason .Cancelled(pb.LeaseDetails.Cancelled(height, cancelTxId.fold(ByteString.EMPTY)(id => ByteString.copyFrom(id.arr)))) - case LeaseDetails.Status.Expired(height) => pb.LeaseDetails.Status.Expired(pb.LeaseDetails.Expired(height)) + case LeaseDetails.Status.Expired(height) => pb.LeaseDetails.CancelReason.Expired(pb.LeaseDetails.Expired(height)) } ).toByteArray ) @@ -151,18 +151,20 @@ package object rocksdb { val d = pb.LeaseDetails.parseFrom(data) Right( LeaseDetails( + LeaseStaticInfo( d.senderPublicKey.toPublicKey, - PBRecipients.toAddressOrAlias(d.recipient.get, AddressScheme.current.chainId).explicitGet(), - d.amount, - d.status match { - case pb.LeaseDetails.Status.Active(_) => LeaseDetails.Status.Active - case pb.LeaseDetails.Status.Expired(pb.LeaseDetails.Expired(height, _)) => LeaseDetails.Status.Expired(height) - case pb.LeaseDetails.Status.Cancelled(pb.LeaseDetails.Cancelled(height, transactionId, _)) => + PBRecipients.toAddress(d.recipient.get, AddressScheme.current.chainId).explicitGet(), + TxPositiveAmount.unsafeFrom(d.amount), + d.sourceId.toByteStr, + d.height + ), + d.cancelReason match { + case pb.LeaseDetails.CancelReason.Empty => LeaseDetails.Status.Active + case pb.LeaseDetails.CancelReason.Expired(pb.LeaseDetails.Expired(height, _)) => LeaseDetails.Status.Expired(height) + case pb.LeaseDetails.CancelReason.Cancelled(pb.LeaseDetails.Cancelled(height, transactionId, _)) => LeaseDetails.Status.Cancelled(height, Some(transactionId.toByteStr).filter(!_.isEmpty)) - case pb.LeaseDetails.Status.Empty => ??? - }, - d.sourceId.toByteStr, - d.height + } + ) ) } diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/blockchain/SupportedBlockchain.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/blockchain/SupportedBlockchain.scala index c9a15ca1a8..5e1c123240 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/blockchain/SupportedBlockchain.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/blockchain/SupportedBlockchain.scala @@ -3,8 +3,7 @@ package com.wavesplatform.ride.runner.blockchain import com.wavesplatform.account.Address import com.wavesplatform.block.Block.BlockId import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.state.reader.LeaseDetails -import com.wavesplatform.state.{AssetScriptInfo, Blockchain, TxMeta, VolumeAndFee} +import com.wavesplatform.state.{AssetScriptInfo, Blockchain, LeaseDetails, TxMeta, VolumeAndFee} import com.wavesplatform.transaction.transfer.TransferTransactionLike import com.wavesplatform.transaction.{Asset, ERC20Address, Transaction} import com.wavesplatform.utils.ScorexLogging diff --git a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/caches/mem/MemCacheKey.scala b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/caches/mem/MemCacheKey.scala index b93a355321..2c7d1f4d81 100644 --- a/ride-runner/src/main/scala/com/wavesplatform/ride/runner/caches/mem/MemCacheKey.scala +++ b/ride-runner/src/main/scala/com/wavesplatform/ride/runner/caches/mem/MemCacheKey.scala @@ -5,10 +5,10 @@ import com.wavesplatform.account.{Address, Alias} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.events.protobuf.StateUpdate -import com.wavesplatform.protobuf.ByteStringExt +import com.wavesplatform.protobuf.{ByteStringExt, transaction as pb} import com.wavesplatform.protobuf.transaction.PBAmounts.toAssetAndAmount import com.wavesplatform.protobuf.transaction.PBTransactions.{toVanillaDataEntry, toVanillaScript} -import com.wavesplatform.protobuf.transaction.{CreateAliasTransactionData, DataTransactionData, Transaction} +import com.wavesplatform.protobuf.transaction.{CreateAliasTransactionData, Transaction} import com.wavesplatform.ride.runner.caches.{WeighedAccountScriptInfo, WeighedAssetDescription} import com.wavesplatform.state.{AssetDescription, AssetScriptInfo, DataEntry, Height, LeaseBalance, TransactionId} import com.wavesplatform.transaction.Asset.IssuedAsset @@ -85,7 +85,7 @@ class GrpcCacheKeyConverters(chainId: Byte) { def accountDataValueBefore(update: StateUpdate.DataEntryUpdate): Option[MemCacheKey.AccountData#ValueT] = update.dataEntryBefore.map(accountDataValue) def accountDataValueAfter(update: StateUpdate.DataEntryUpdate): Option[MemCacheKey.AccountData#ValueT] = update.dataEntry.map(accountDataValue) - def accountDataValue(dataEntry: DataTransactionData.DataEntry): MemCacheKey.AccountData#ValueT = toVanillaDataEntry(dataEntry) + def accountDataValue(dataEntry: pb.DataEntry): MemCacheKey.AccountData#ValueT = toVanillaDataEntry(dataEntry) def transactionIdKey(id: ByteString): MemCacheKey.Transaction = MemCacheKey.Transaction(TransactionId(ByteStr(id.toByteArray))) diff --git a/ride-runner/src/test/scala/com/wavesplatform/api/CommonGrpcConverters.scala b/ride-runner/src/test/scala/com/wavesplatform/api/CommonGrpcConverters.scala index 74bff800b1..02165cacd1 100644 --- a/ride-runner/src/test/scala/com/wavesplatform/api/CommonGrpcConverters.scala +++ b/ride-runner/src/test/scala/com/wavesplatform/api/CommonGrpcConverters.scala @@ -5,7 +5,7 @@ import com.google.protobuf.{ByteString, UnsafeByteOperations} import com.wavesplatform.account.Address import com.wavesplatform.events.protobuf.StateUpdate import com.wavesplatform.protobuf.AddressExt -import com.wavesplatform.protobuf.transaction.DataTransactionData +import com.wavesplatform.protobuf.transaction.DataEntry trait CommonGrpcConverters { def toByteString32(xs: Int*): ByteString = { @@ -17,9 +17,9 @@ trait CommonGrpcConverters { StateUpdate.DataEntryUpdate( address = address.toByteString, dataEntryBefore = valueBefore.map { valueBefore => - DataTransactionData.DataEntry(key, DataTransactionData.DataEntry.Value.IntValue(valueBefore)) + DataEntry(key, DataEntry.Value.IntValue(valueBefore)) }, - dataEntry = valueAfter.map { valueAfter => DataTransactionData.DataEntry(key, DataTransactionData.DataEntry.Value.IntValue(valueAfter)) } + dataEntry = valueAfter.map { valueAfter => DataEntry(key, DataEntry.Value.IntValue(valueAfter)) } ) }