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..c08030cb2f 100644 --- a/grpc-server/src/main/scala/com/wavesplatform/events/events.scala +++ b/grpc-server/src/main/scala/com/wavesplatform/events/events.scala @@ -23,7 +23,7 @@ import com.wavesplatform.transaction.assets.exchange.ExchangeTransaction import com.wavesplatform.transaction.lease.LeaseTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.transfer.{MassTransferTransaction, TransferTransaction} -import com.wavesplatform.transaction.{Asset, Authorized, CreateAliasTransaction, EthereumTransaction} +import com.wavesplatform.transaction.{Asset, Authorized, CreateAliasTransaction, EthereumTransaction, Transaction} import scala.collection.mutable import scala.collection.mutable.ArrayBuffer @@ -389,7 +389,11 @@ 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 = { + def atomic( + blockchainBeforeWithMinerReward: Blockchain, + snapshot: StateSnapshot, + txWithLeases: Iterable[(Transaction, Map[ByteStr, LeaseSnapshot])] + ): StateUpdate = { val blockchain = blockchainBeforeWithMinerReward val blockchainAfter = SnapshotBlockchain(blockchain, snapshot) @@ -426,18 +430,20 @@ object StateUpdate { assetAfter = blockchainAfter.assetDescription(asset) } yield AssetStateUpdate(asset.id, assetBefore, assetAfter) - val updatedLeases = snapshot.leaseStates.map { case (leaseId, newState) => - 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 - ) + val updatedLeases = txWithLeases.flatMap { case (sourceTxId, leases) => + leases.map { case (leaseId, newState) => + 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.toDetails(blockchain, Some(sourceTxId), blockchain.leaseDetails(leaseId)).sourceId + ) + } }.toVector val updatedScripts = snapshot.accountScriptsByAddress.map { case (address, newScript) => @@ -546,13 +552,17 @@ object StateUpdate { val accBlockchain = SnapshotBlockchain(blockchainBeforeWithReward, accSnapshot) ( accSnapshot |+| txInfo.snapshot, - updates :+ atomic(accBlockchain, txInfo.snapshot) + updates :+ atomic(accBlockchain, txInfo.snapshot, Seq((txInfo.transaction, txInfo.snapshot.leaseStates))) ) } val blockchainAfter = SnapshotBlockchain(blockchainBeforeWithReward, totalSnapshot) val metadata = transactionsMetadata(blockchainAfter, totalSnapshot) val refAssets = referencedAssets(blockchainAfter, txsStateUpdates) - val keyBlockUpdate = atomic(blockchainBeforeWithReward, keyBlockSnapshot) + val keyBlockUpdate = atomic( + blockchainBeforeWithReward, + keyBlockSnapshot, + keyBlockSnapshot.transactions.map { case (_, txInfo) => (txInfo.transaction, txInfo.snapshot.leaseStates) } + ) (keyBlockUpdate, txsStateUpdates, metadata, refAssets) } } 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/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/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/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/FailedTransactionGrpcSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/FailedTransactionGrpcSuite.scala index dbc782c9b1..f659f1e99c 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 @@ -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/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/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..1f2bfbb1c0 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,6 +439,7 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con ), AssetsApiRoute( settings.restAPISettings, + serverRequestTimeout, wallet, transactionPublisher, blockchainUpdater, 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..b67c81bf8b 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.{Blockchain, DataEntry, InvokeScriptResult, TxMeta} import com.wavesplatform.state.reader.LeaseDetails +import com.wavesplatform.state.{Blockchain, DataEntry, InvokeScriptResult, TxMeta} 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) { @@ -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..e8bfbc82b0 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,7 +18,13 @@ 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 @@ -39,9 +45,11 @@ 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, @@ -65,6 +73,8 @@ case class AssetsApiRoute( ) ) + private val assetDistRouteTimeout = new RouteTimeout(serverRequestTimeout)(distributionTaskScheduler) + override lazy val route: Route = pathPrefix("assets") { pathPrefix("balance" / AddrSegment) { address => @@ -97,9 +107,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 +191,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 +532,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/database/RocksDBWriter.scala b/node/src/main/scala/com/wavesplatform/database/RocksDBWriter.scala index b16f420538..4b11f41289 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,8 +17,7 @@ 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.snapshot.{TransactionStateSnapshot, TransactionStatus as PBStatus} import com.wavesplatform.protobuf.{ByteStrExt, ByteStringExt} import com.wavesplatform.settings.{BlockchainSettings, DBSettings} import com.wavesplatform.state.* @@ -485,7 +484,14 @@ class RocksDBWriter( expiredKeys ++= updateHistory(rw, Keys.assetDetailsHistory(asset), threshold, Keys.assetDetails(asset)) } - for ((id, details) <- snapshot.leaseStates) { + val txLeases = for { + (_, txInfo) <- snapshot.transactions + (id, lease) <- txInfo.snapshot.leaseStates + } yield (Some(txInfo.transaction), id, lease) + val txLeaseIdSet = txLeases.map(_._2).toSet + val allLeases = txLeases ++ snapshot.leaseStates.collect { case (id, lease) if !txLeaseIdSet.contains(id) => (None, id, lease) } + allLeases.foreach { case (txOpt, id, lease) => + val details = lease.toDetails(this, txOpt, leaseDetails(id)) rw.put(Keys.leaseDetails(id)(height), Some(Right(details))) expiredKeys ++= updateHistory(rw, Keys.leaseDetailsHistory(id), threshold, Keys.leaseDetails(id)) } diff --git a/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala b/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala index 90ccafbdb2..99d193f72b 100644 --- a/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala +++ b/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala @@ -437,14 +437,13 @@ class BlockchainUpdaterImpl( 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) + lt.id() -> LeaseSnapshot(lt.sender, lt.recipient, lt.amount.value, LeaseDetails.Status.Expired(height)) ) snapshot = StateSnapshot.build(rocksdb, portfolios, leaseStates = leaseStates).explicitGet() } yield lt.id() -> snapshot diff --git a/node/src/main/scala/com/wavesplatform/state/LeaseSnapshot.scala b/node/src/main/scala/com/wavesplatform/state/LeaseSnapshot.scala new file mode 100644 index 0000000000..e49fa4a09d --- /dev/null +++ b/node/src/main/scala/com/wavesplatform/state/LeaseSnapshot.scala @@ -0,0 +1,30 @@ +package com.wavesplatform.state +import com.wavesplatform.account.{AddressOrAlias, PublicKey} +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.state.reader.LeaseDetails +import com.wavesplatform.transaction.Transaction +import com.wavesplatform.transaction.lease.LeaseCancelTransaction + +import scala.util.Try + +case class LeaseSnapshot(sender: PublicKey, recipient: AddressOrAlias, amount: Long, status: LeaseDetails.Status) { + val isActive: Boolean = status == LeaseDetails.Status.Active + + def toDetails(blockchain: Blockchain, txOpt: Option[Transaction], innerDetails: => Option[LeaseDetails]): LeaseDetails = { + def height(id: ByteStr) = blockchain.transactionMeta(id).map(_.height).getOrElse(blockchain.height) + val (sourceId, sourceHeight) = txOpt match { + case Some(c: LeaseCancelTransaction) => (c.leaseId, height(c.leaseId)) + case Some(i) if isActive => (i.id(), blockchain.height) // produced by lease or invoke (including eth) + case Some(i) if !isActive && innerDetails.isEmpty => (i.id(), blockchain.height) // produced and cancelled by the same invoke + case _ => + def id = innerDetails.get.sourceId // cancelled by invoke and produced by other transaction from the state + Try((id, height(id))).getOrElse((ByteStr.empty, 0)) + } + LeaseDetails(sender, recipient, amount, status, sourceId, sourceHeight) + } +} + +object LeaseSnapshot { + def fromDetails(details: LeaseDetails): LeaseSnapshot = + LeaseSnapshot(details.sender, details.recipient, details.amount, details.status) +} diff --git a/node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala b/node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala index 7d4236fbaa..22f2ee7d90 100644 --- a/node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala +++ b/node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala @@ -3,15 +3,16 @@ 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.crypto.KeyLength 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.transaction.{PBAmounts, PBTransactions} import com.wavesplatform.protobuf.{AddressExt, ByteStrExt, ByteStringExt} import com.wavesplatform.state.reader.LeaseDetails.Status import com.wavesplatform.state.reader.{LeaseDetails, SnapshotBlockchain} @@ -30,7 +31,7 @@ case class StateSnapshot( assetNamesAndDescriptions: Map[IssuedAsset, AssetInfo] = Map(), assetScripts: Map[IssuedAsset, AssetScriptInfo] = Map(), sponsorships: Map[IssuedAsset, SponsorshipValue] = Map(), - leaseStates: Map[ByteStr, LeaseDetails] = Map(), + leaseStates: Map[ByteStr, LeaseSnapshot] = Map(), aliases: Map[Alias, Address] = Map(), orderFills: Map[ByteStr, VolumeAndFee] = Map(), accountScripts: Map[PublicKey, Option[AccountScriptInfo]] = Map(), @@ -63,24 +64,14 @@ case class StateSnapshot( 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)) => + leaseStates.map { case (leaseId, LeaseSnapshot(sender, recipient, amount, status)) => 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.Status.Active(S.LeaseState.Active(amount, sender.toByteString, recipient.asInstanceOf[Address].toByteString)) + case _: Status.Cancelled | _: Status.Expired => + S.LeaseState.Status.Cancelled(S.LeaseState.Cancelled()) } - S.LeaseState( - leaseId.toByteString, - pbStatus, - amount, - sender.toByteString, - ByteString.copyFrom(recipient.asInstanceOf[Address].bytes), - sourceId.toByteString, - height - ) + S.LeaseState(leaseId.toByteString, pbStatus) }.toSeq, accountScripts.map { case (publicKey, scriptOpt) => scriptOpt.fold( @@ -171,24 +162,25 @@ object StateSnapshot { .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 leaseStates: Map[ByteStr, LeaseSnapshot] = + pbSnapshot.leaseStates.map { ls => + ls.status match { + case TransactionStateSnapshot.LeaseState.Status.Active(value) => + ls.leaseId.toByteStr -> LeaseSnapshot( + value.sender.toPublicKey, + value.recipient.toAddress(), + value.amount, + LeaseDetails.Status.Active + ) + case _: TransactionStateSnapshot.LeaseState.Status.Cancelled | TransactionStateSnapshot.LeaseState.Status.Empty => + ls.leaseId.toByteStr -> LeaseSnapshot( + PublicKey(ByteStr.fill(KeyLength)(0)), + Address(Array.fill(Address.HashLength)(0)), + 0, + LeaseDetails.Status.Cancelled(0, None) + ) + } + }.toMap val aliases: Map[Alias, Address] = pbSnapshot.aliases @@ -254,7 +246,7 @@ object StateSnapshot { updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]] = Map(), assetScripts: Map[IssuedAsset, AssetScriptInfo] = Map(), sponsorships: Map[IssuedAsset, Sponsorship] = Map(), - leaseStates: Map[ByteStr, LeaseDetails] = Map(), + leaseStates: Map[ByteStr, LeaseSnapshot] = Map(), aliases: Map[Alias, Address] = Map(), accountData: Map[Address, Map[String, DataEntry[?]]] = Map(), accountScripts: Map[PublicKey, Option[AccountScriptInfo]] = Map(), @@ -381,9 +373,9 @@ object StateSnapshot { private def resolvedLeaseStates( blockchain: Blockchain, - leaseStates: Map[ByteStr, LeaseDetails], + leaseStates: Map[ByteStr, LeaseSnapshot], aliases: Map[Alias, Address] - ): Map[ByteStr, LeaseDetails] = + ): Map[ByteStr, LeaseSnapshot] = leaseStates.view .mapValues(details => details.copy(recipient = details.recipient match { diff --git a/node/src/main/scala/com/wavesplatform/state/TxStateSnapshotHashBuilder.scala b/node/src/main/scala/com/wavesplatform/state/TxStateSnapshotHashBuilder.scala index d2b4dc46af..7d99d89319 100644 --- a/node/src/main/scala/com/wavesplatform/state/TxStateSnapshotHashBuilder.scala +++ b/node/src/main/scala/com/wavesplatform/state/TxStateSnapshotHashBuilder.scala @@ -76,7 +76,17 @@ object TxStateSnapshotHashBuilder { } addEntry(KeyType.AssetScript, asset.id.arr)(scriptInfo.script.bytes().arr) snapshot.leaseStates.foreach { case (leaseId, details) => - addEntry(KeyType.LeaseStatus, leaseId.arr)(booleanToBytes(details.isActive)) + if (details.isActive) + addEntry(KeyType.LeaseStatus, leaseId.arr)( + booleanToBytes(true), + details.sender.arr, + details.recipient.bytes, + Longs.toByteArray(details.amount) + ) + else + addEntry(KeyType.LeaseStatus, leaseId.arr)( + booleanToBytes(false) + ) } snapshot.sponsorships.foreach { case (asset, sponsorship) => 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..66cc7a4043 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 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..5c83e03e36 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/DiffsCommon.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/DiffsCommon.scala @@ -16,7 +16,7 @@ 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, LeaseSnapshot, Portfolio, SponsorshipValue, StateSnapshot} import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.TxValidationError.GenericError @@ -166,7 +166,7 @@ object DiffsCommon { senderAddress -> Portfolio(-fee, LeaseBalance(0, amount)), recipientAddress -> Portfolio(0, LeaseBalance(amount, 0)) ) - details = LeaseDetails(sender, recipient, amount, LeaseDetails.Status.Active, txId, blockchain.height) + details = LeaseSnapshot(sender, recipient, amount, LeaseDetails.Status.Active) snapshot <- StateSnapshot.build( blockchain, portfolios = portfolioDiff, @@ -207,7 +207,7 @@ object DiffsCommon { snapshot <- StateSnapshot.build( blockchain, portfolios = portfolios, - leaseStates = Map(leaseId -> actionInfo) + leaseStates = Map(leaseId -> LeaseSnapshot.fromDetails(actionInfo)) ) } 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/invoke/InvokeDiffsCommon.scala b/node/src/main/scala/com/wavesplatform/state/diffs/invoke/InvokeDiffsCommon.scala index e154bb12c4..b00d78891e 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 @@ -30,6 +30,7 @@ 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) @@ -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 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..15c83699c3 100644 --- a/node/src/main/scala/com/wavesplatform/state/patch/CancelAllLeases.scala +++ b/node/src/main/scala/com/wavesplatform/state/patch/CancelAllLeases.scala @@ -5,7 +5,7 @@ import com.wavesplatform.account.{Address, PublicKey} 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, LeaseSnapshot, StateSnapshot} import play.api.libs.json.{Json, OFormat} case object CancelAllLeases extends PatchAtHeight('W' -> 462000, 'T' -> 51500) { @@ -13,11 +13,11 @@ 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 leaseStates: Map[ByteStr, LeaseSnapshot] = 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)) + (id, LeaseSnapshot(sender, recipient, data.amount, status = LeaseDetails.Status.Expired(height))) }.toMap } 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..e1e343e4bf 100644 --- a/node/src/main/scala/com/wavesplatform/state/patch/CancelLeasesToDisabledAliases.scala +++ b/node/src/main/scala/com/wavesplatform/state/patch/CancelLeasesToDisabledAliases.scala @@ -6,7 +6,7 @@ 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, LeaseSnapshot, Portfolio, StateSnapshot} import play.api.libs.json.{Json, Reads} case object CancelLeasesToDisabledAliases extends PatchOnFeature(BlockchainFeatures.SynchronousCalls, Set('W')) { @@ -19,7 +19,7 @@ case object CancelLeasesToDisabledAliases extends PatchOnFeature(BlockchainFeatu height: Int ) - def patchData: Map[ByteStr, (LeaseDetails, Address)] = { + def patchData: Map[ByteStr, (LeaseSnapshot, Address)] = { implicit val cancelDetailsReads: Reads[CancelDetails] = Json.reads readPatchData[Seq[CancelDetails]]().map { cancelDetails => @@ -27,13 +27,11 @@ case object CancelLeasesToDisabledAliases extends PatchOnFeature(BlockchainFeatu val sender = PublicKey(Base58.decode(cancelDetails.senderPublicKey)) val recipientAlias = Alias.fromString(cancelDetails.recipientAlias).explicitGet() val recipientAddress = Address.fromString(cancelDetails.recipientAddress).explicitGet() - leaseId -> (LeaseDetails( + leaseId -> (LeaseSnapshot( sender, recipientAlias, cancelDetails.amount, - LeaseDetails.Status.Expired(0), - leaseId, - cancelDetails.height + LeaseDetails.Status.Expired(0) ) -> recipientAddress) }.toMap } diff --git a/node/src/main/scala/com/wavesplatform/state/reader/SnapshotBlockchain.scala b/node/src/main/scala/com/wavesplatform/state/reader/SnapshotBlockchain.scala index d8347f6c35..abc3ac79d5 100644 --- a/node/src/main/scala/com/wavesplatform/state/reader/SnapshotBlockchain.scala +++ b/node/src/main/scala/com/wavesplatform/state/reader/SnapshotBlockchain.scala @@ -84,8 +84,18 @@ 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] = { + lazy val innerDetails = inner.leaseDetails(leaseId) + val txOpt = + snapshot.transactions + .collectFirst { + case (_, txInfo) if txInfo.snapshot.leaseStates.contains(leaseId) => txInfo.transaction + } + snapshot.leaseStates + .get(leaseId) + .map(_.toDetails(this, txOpt, innerDetails)) + .orElse(innerDetails) + } override def transferById(id: ByteStr): Option[(Int, TransferTransactionLike)] = snapshot.transactions 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 8a5bc384f3..fdd2ec8aa2 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala @@ -10,8 +10,8 @@ import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.consensus.{FairPoSCalculator, PoSCalculator} import com.wavesplatform.features.BlockchainFeatures +import com.wavesplatform.features.BlockchainFeatures.LightNode import com.wavesplatform.features.MultiPaymentPolicyProvider.* -import com.wavesplatform.lang.{Global, ValidationError} import com.wavesplatform.lang.directives.DirectiveSet import com.wavesplatform.lang.directives.values.StdLibVersion import com.wavesplatform.lang.script.Script @@ -21,6 +21,7 @@ import com.wavesplatform.lang.v1.evaluator.{Log, ScriptResult} import com.wavesplatform.lang.v1.traits.* import com.wavesplatform.lang.v1.traits.domain.* import com.wavesplatform.lang.v1.traits.domain.Recipient.* +import com.wavesplatform.lang.{Global, ValidationError} import com.wavesplatform.state.* import com.wavesplatform.state.BlockRewardCalculator.CurrentBlockRewardPart import com.wavesplatform.state.diffs.invoke.{InvokeScript, InvokeScriptDiff, InvokeScriptTransactionLike} @@ -157,7 +158,7 @@ class WavesEnvironment( } override def accountWavesBalanceOf(addressOrAlias: Recipient): Either[String, Environment.BalanceDetails] = { - val addressE: Either[ValidationError, account.Address] = addressOrAlias match { + val addressE = addressOrAlias match { case Address(bytes) => account.Address.fromBytes(bytes.arr) case Alias(name) => account.Alias.create(name).flatMap(a => blockchain.resolveAlias(a)) } @@ -169,7 +170,10 @@ class WavesEnvironment( } yield Environment.BalanceDetails( portfolio.balance - portfolio.lease.out, portfolio.balance, - blockchain.generatingBalance(address), + if (blockchain.isFeatureActivated(LightNode)) + currentBlockchain().generatingBalance(address) + else + blockchain.generatingBalance(address), effectiveBalance ) } 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/api/http/CustomJsonMarshallerSpec.scala b/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala index f266896a79..9f1a6b67ed 100644 --- a/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala +++ b/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala @@ -115,6 +115,7 @@ class CustomJsonMarshallerSpec private val assetsRoute = AssetsApiRoute( restAPISettings, + 60.seconds, testWallet, publisher, blockchain, diff --git a/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala index a1e2a8ab0c..6484726d1e 100644 --- a/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala @@ -57,6 +57,7 @@ class AssetsRouteSpec seal( AssetsApiRoute( restAPISettings, + 60.seconds, testWallet, DummyTransactionPublisher.accepting, d.blockchain, diff --git a/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala index 52350e5698..2908946046 100644 --- a/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala @@ -1,9 +1,8 @@ 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.* import com.wavesplatform.account.{Alias, KeyPair} import com.wavesplatform.api.common.CommonTransactionsApi import com.wavesplatform.api.http.ApiError.ApiKeyNotValid @@ -42,12 +41,12 @@ import com.wavesplatform.transaction.smart.script.ScriptCompiler import com.wavesplatform.transaction.{ERC20Address, 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 play.api.libs.json.{JsArray, JsObject, JsValue, Json} +import java.util.concurrent.TimeUnit import scala.concurrent.duration.* import scala.util.Random @@ -1619,10 +1618,10 @@ 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.defaultSigner.publicKey + val dAppAddress = dAppPk.toAddress + val invoke = TxHelpers.invoke(dAppPk.toAddress) + val canceledLeaseId = ByteStr(bytes32gen.sample.get) val amount1 = 100 val recipient1 = Recipient.Address(ByteStr.decodeBase58("3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd").get) @@ -1660,7 +1659,7 @@ class DebugApiRouteSpec | [ | Lease(Address(base58'${recipient1.bytes}'), $amount1, $nonce1), | Lease(Alias("${recipient2.name}"), $amount2, $nonce2), - | LeaseCancel(base58'$leaseCancelId') + | LeaseCancel(base58'$canceledLeaseId') | ] | else [] |} @@ -1685,13 +1684,13 @@ class DebugApiRouteSpec (blockchain.hasAccountScript _).when(*).returns(true) (blockchain.transactionMeta _) - .when(leaseCancelId) + .when(canceledLeaseId) .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))) + .when(canceledLeaseId) + .returns(Some(LeaseDetails(dAppPk, TxHelpers.defaultAddress, leaseCancelAmount, LeaseDetails.Status.Active, invoke.id(), 1))) .anyNumberOfTimes() (blockchain.leaseDetails _) @@ -1743,8 +1742,8 @@ class DebugApiRouteSpec | "cancelTransactionId" : null | } ], | "leaseCancels" : [ { - | "id" : "$leaseCancelId", - | "originTransactionId" : "$leaseCancelId", + | "id" : "$canceledLeaseId", + | "originTransactionId" : "${invoke.id()}", | "sender" : "$dAppAddress", | "recipient" : "${TxHelpers.defaultAddress}", | "amount" : $leaseCancelAmount, @@ -1797,8 +1796,8 @@ class DebugApiRouteSpec | "cancelTransactionId" : null | } ], | "leaseCancels" : [ { - | "id" : "$leaseCancelId", - | "originTransactionId" : "$leaseCancelId", + | "id" : "$canceledLeaseId", + | "originTransactionId" : "${invoke.id()}", | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", | "recipient" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", | "amount" : $leaseCancelAmount, @@ -2007,7 +2006,7 @@ class DebugApiRouteSpec | "type" : "Array", | "value" : [ { | "type" : "ByteVector", - | "value" : "$leaseCancelId" + | "value" : "$canceledLeaseId" | } ] | }, { | "name" : "LeaseCancel.@complexity", @@ -2025,7 +2024,7 @@ class DebugApiRouteSpec | "value" : { | "leaseId" : { | "type" : "ByteVector", - | "value" : "$leaseCancelId" + | "value" : "$canceledLeaseId" | } | } | }, { @@ -2071,7 +2070,7 @@ class DebugApiRouteSpec | "value" : { | "leaseId" : { | "type" : "ByteVector", - | "value" : "$leaseCancelId" + | "value" : "$canceledLeaseId" | } | } | } ] @@ -2136,7 +2135,7 @@ class DebugApiRouteSpec | "value" : { | "leaseId" : { | "type" : "ByteVector", - | "value" : "$leaseCancelId" + | "value" : "$canceledLeaseId" | } | } | } ] diff --git a/node/src/test/scala/com/wavesplatform/network/RxExtensionLoaderSpec.scala b/node/src/test/scala/com/wavesplatform/network/RxExtensionLoaderSpec.scala index 5c27d1c005..846e27b5a8 100644 --- a/node/src/test/scala/com/wavesplatform/network/RxExtensionLoaderSpec.scala +++ b/node/src/test/scala/com/wavesplatform/network/RxExtensionLoaderSpec.scala @@ -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/diffs/ExchangeTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala index 811c17bc3b..28084f99e8 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala @@ -41,6 +41,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 { @@ -1924,10 +1925,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() + 1.day.toMillis, TxMatcherFee.unsafeFrom(0.003.waves) ) val signedBuyOrder = buyOrder.copy( @@ -1990,8 +1991,8 @@ 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, TxMatcherFee.unsafeFrom(0.003.waves) 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/InvokeAssetChecksTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeAssetChecksTest.scala index 1b61cb7e64..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 @@ -23,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) 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/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/SyncDAppGeneratingBalanceTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppGeneratingBalanceTest.scala new file mode 100644 index 0000000000..492982797a --- /dev/null +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/sync/SyncDAppGeneratingBalanceTest.scala @@ -0,0 +1,44 @@ +package com.wavesplatform.state.diffs.ci.sync + +import com.wavesplatform.db.WithDomain +import com.wavesplatform.db.WithState.AddrWithBalance +import com.wavesplatform.features.BlockchainFeatures.LightNode +import com.wavesplatform.lang.directives.values.V7 +import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.test.DomainPresets.* +import com.wavesplatform.test.PropSpec +import com.wavesplatform.transaction.TxHelpers.* + +class SyncDAppGeneratingBalanceTest extends PropSpec with WithDomain { + property("sync balance changes should be taken into account for the generatingBalance field") { + val amount = 777 + val dApp = TestCompiler(V7).compileContract( + s""" + | @Callable(i) + | func default() = { + | strict generatingBefore = i.caller.wavesBalance().generating + | strict result = Address(base58'$defaultAddress').invoke("call", [], [AttachedPayment(unit, $amount)]) + | strict generatingAfter = i.caller.wavesBalance().generating + | [ + | IntegerEntry("generatingDiff", generatingBefore - generatingAfter) + | ] + | } + | + | @Callable(i) + | func call() = [] + """.stripMargin + ) + withDomain( + BlockRewardDistribution.setFeaturesHeight(LightNode -> 4), + AddrWithBalance.enoughBalances(defaultSigner, secondSigner) + ) { d => + d.appendBlock(setScript(defaultSigner, dApp), setScript(secondSigner, dApp)) + + d.appendAndAssertSucceed(invoke(secondAddress, invoker = secondSigner)) + d.liquidSnapshot.accountData.head._2.head._2.value shouldBe 0 + + d.appendAndAssertSucceed(invoke(secondAddress, invoker = secondSigner)) + d.liquidSnapshot.accountData.head._2.head._2.value shouldBe amount + } + } +} diff --git a/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotProtoTest.scala b/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotProtoTest.scala index 7f2d5ae0a1..78c5df5722 100644 --- a/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotProtoTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotProtoTest.scala @@ -1,14 +1,14 @@ package com.wavesplatform.state.snapshot import com.google.protobuf.ByteString -import com.wavesplatform.account.Alias +import com.wavesplatform.account.{Address, Alias, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.crypto.KeyLength 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} @@ -60,14 +60,12 @@ class StateSnapshotProtoTest extends PropSpec { 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, + ByteStr.fromBytes(4, 5, 6) -> LeaseSnapshot(defaultSigner.publicKey, secondAddress, 123, Status.Active), + ByteStr.fromBytes(7, 8, 9) -> LeaseSnapshot( + PublicKey(ByteStr.fill(KeyLength)(0)), + Address(Array.fill(Address.HashLength)(0)), 0, - Status.Cancelled(2, Some(ByteStr.fromBytes(5, 5, 5))), - ByteStr.fromBytes(1, 2, 3), - 777777777 + Status.Cancelled(0, None) ) ), Map( 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..abe2f5495f 100644 --- a/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotStorageTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotStorageTest.scala @@ -5,6 +5,7 @@ import com.wavesplatform.TestValues.fee import com.wavesplatform.account.{Address, Alias, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.crypto.KeyLength import com.wavesplatform.db.WithDomain import com.wavesplatform.lang.directives.values.V6 import com.wavesplatform.lang.v1.compiler.TestCompiler @@ -15,7 +16,6 @@ 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} @@ -175,7 +175,7 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { recipient -> LeaseBalance(leaseTx.amount.value, 0) ), leaseStates = Map( - leaseTx.id() -> LeaseDetails(sender.publicKey, recipient, leaseTx.amount.value, Active, leaseTx.id(), d.solidStateHeight + 2) + leaseTx.id() -> LeaseSnapshot(sender.publicKey, recipient, leaseTx.amount.value, Active) ) ) ) @@ -193,13 +193,11 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { 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 + leaseTx.id() -> LeaseSnapshot( + PublicKey(ByteStr.fill(KeyLength)(0)), + Address(Array.fill(Address.HashLength)(0)), + 0, + Cancelled(0, None) ) ) ) @@ -333,7 +331,7 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { dAppAssetId -> AssetInfo("name", "description", Height(height)) ), leaseStates = Map( - leaseId -> LeaseDetails(dAppPk, senderAddress, 123, Active, invokeId, height) + leaseId -> LeaseSnapshot(dAppPk, senderAddress, 123, Active) ), accountData = Map( dAppPk.toAddress -> Map("key" -> StringDataEntry("key", "abc")) 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 c423175ec4..eb25b4b1eb 100644 --- a/node/src/test/scala/com/wavesplatform/state/snapshot/TxStateSnapshotHashSpec.scala +++ b/node/src/test/scala/com/wavesplatform/state/snapshot/TxStateSnapshotHashSpec.scala @@ -33,8 +33,10 @@ class TxStateSnapshotHashSpec extends PropSpec with WithDomain { 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 leaseId1 = ByteStr.fill(DigestLength)(6) + private val leaseId2 = ByteStr.fill(DigestLength)(7) + private val leaseDetails1 = LeaseSnapshot(TxHelpers.signer(1).publicKey, address2, 1.waves, LeaseDetails.Status.Active) + private val leaseDetails2 = LeaseSnapshot(TxHelpers.signer(1).publicKey, address2, 1.waves, LeaseDetails.Status.Cancelled(1, None)) private val addr1Balance = 10.waves private val addr2Balance = 20.waves @@ -116,7 +118,7 @@ class TxStateSnapshotHashSpec extends PropSpec with WithDomain { ), aliases = Map(addr1Alias1 -> address1, addr2Alias -> address2, addr1Alias2 -> address1), orderFills = Map(orderId -> volumeAndFee), - leaseStates = Map(leaseId -> leaseDetails), + leaseStates = Map(leaseId1 -> leaseDetails1, leaseId2 -> leaseDetails2), accountScripts = Map(TxHelpers.signer(2).publicKey -> Some(accountScriptInfo)), assetScripts = Map(assetId1 -> assetScriptInfo), accountData = Map(address1 -> Map(dataEntry.key -> dataEntry)), @@ -147,7 +149,15 @@ class TxStateSnapshotHashSpec extends PropSpec with WithDomain { 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.LeaseStatus.id.toByte) + ++ leaseId1.arr + ++ Array(1: Byte) + ++ leaseDetails1.sender.arr + ++ leaseDetails1.recipient.bytes + ++ Longs.toByteArray(leaseDetails1.amount), + Array(KeyType.LeaseStatus.id.toByte) + ++ leaseId2.arr + ++ 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), diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 9d5b08bc4a..c5ca5a3c52 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -6,7 +6,7 @@ import scalapb.compiler.Version.scalapbVersion object Dependencies { // Node protobuf schemas private[this] val protoSchemasLib = - "com.wavesplatform" % "protobuf-schemas" % "1.5.1-SNAPSHOT" classifier "protobuf-src" intransitive () + "com.wavesplatform" % "protobuf-schemas" % "1.5.1-84-SNAPSHOT" classifier "protobuf-src" intransitive () private def akkaModule(module: String) = "com.typesafe.akka" %% s"akka-$module" % "2.6.21"