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 c08030cb2fe..62324eaaf16 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,13 +17,12 @@ 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 import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.transfer.{MassTransferTransaction, TransferTransaction} -import com.wavesplatform.transaction.{Asset, Authorized, CreateAliasTransaction, EthereumTransaction, Transaction} +import com.wavesplatform.transaction.{Asset, Authorized, CreateAliasTransaction, EthereumTransaction} import scala.collection.mutable import scala.collection.mutable.ArrayBuffer @@ -386,14 +385,7 @@ 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, - txWithLeases: Iterable[(Transaction, Map[ByteStr, LeaseSnapshot])] - ): StateUpdate = { + def atomic(blockchainBeforeWithMinerReward: Blockchain, snapshot: StateSnapshot): StateUpdate = { val blockchain = blockchainBeforeWithMinerReward val blockchainAfter = SnapshotBlockchain(blockchain, snapshot) @@ -430,27 +422,37 @@ object StateUpdate { assetAfter = blockchainAfter.assetDescription(asset) } yield AssetStateUpdate(asset.id, assetBefore, assetAfter) - val updatedLeases = txWithLeases.flatMap { case (sourceTxId, leases) => - leases.map { case (leaseId, newState) => + val newLeaseUpdates = snapshot.newLeases.collect { + case (newId, staticInfo) if !snapshot.cancelledLeases.contains(newId) => 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 + newId, + LeaseStatus.Active, + staticInfo.amount.value, + staticInfo.sender, + staticInfo.recipientAddress, + staticInfo.sourceId ) - } - }.toVector + } + + val cancelledLeaseUpdates = snapshot.cancelledLeases.map { case (id, _) => + val si = snapshot.newLeases.get(id).orElse(blockchain.leaseDetails(id).map(_.static)) + LeaseUpdate( + 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) + ) + } + + 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] = { @@ -552,17 +554,13 @@ object StateUpdate { val accBlockchain = SnapshotBlockchain(blockchainBeforeWithReward, accSnapshot) ( accSnapshot |+| txInfo.snapshot, - updates :+ atomic(accBlockchain, txInfo.snapshot, Seq((txInfo.transaction, txInfo.snapshot.leaseStates))) + updates :+ atomic(accBlockchain, txInfo.snapshot) ) } val blockchainAfter = SnapshotBlockchain(blockchainBeforeWithReward, totalSnapshot) val metadata = transactionsMetadata(blockchainAfter, totalSnapshot) val refAssets = referencedAssets(blockchainAfter, txsStateUpdates) - val keyBlockUpdate = atomic( - blockchainBeforeWithReward, - keyBlockSnapshot, - keyBlockSnapshot.transactions.map { case (_, txInfo) => (txInfo.transaction, txInfo.snapshot.leaseStates) } - ) + val keyBlockUpdate = atomic(blockchainBeforeWithReward, keyBlockSnapshot) (keyBlockUpdate, txsStateUpdates, metadata, refAssets) } } 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 61c1cb035f6..697740ce861 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/events/BlockchainUpdatesEthereumInvokeTxSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesEthereumInvokeTxSpec.scala index ca5cd1faf05..d3ffa870df5 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 3e2c0211a79..82fc0abcfa4 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 cd8ccd2c6e4..0d719ec459d 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 a9e8840699d..28e3f0e80ff 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 3d828e80253..4c7db9915cb 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 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 171f1ebdf02..929b38e139d 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 36b47da26c8..7b96f8d2117 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 28d043a2215..90f3a31a1fc 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 bb8a73ad3cb..5e01c45f5ac 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/node-it/src/test/scala/com/wavesplatform/it/api/AsyncGrpcApi.scala b/node-it/src/test/scala/com/wavesplatform/it/api/AsyncGrpcApi.scala index 828fe00f0d9..1c5b31a5953 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/SyncGrpcApi.scala b/node-it/src/test/scala/com/wavesplatform/it/api/SyncGrpcApi.scala index ce4224cd467..4cbd1b94b7b 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/sync/BlockChallengeSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/BlockChallengeSuite.scala index 05d16dbecf2..e6a8eccb07f 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/grpc/DataTransactionGrpcSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/grpc/DataTransactionGrpcSuite.scala index 113c71ad2b2..ef40d5457fd 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 f659f1e99ce..f90a6aa7896 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.* 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 6bbe813c69e..d025f584de8 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/src/main/protobuf/waves/database.proto b/node/src/main/protobuf/waves/database.proto index 5df4ad0be0a..2e723e4f7a1 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/scala/com/wavesplatform/Application.scala b/node/src/main/scala/com/wavesplatform/Application.scala index 1f2bfbb1c0f..74625fabfb1 100644 --- a/node/src/main/scala/com/wavesplatform/Application.scala +++ b/node/src/main/scala/com/wavesplatform/Application.scala @@ -441,7 +441,6 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con 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 141e608defa..f034331b1e4 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 b59eff7a734..77cfd7efce6 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/CommonAccountsApi.scala b/node/src/main/scala/com/wavesplatform/api/common/CommonAccountsApi.scala index 0e7bc89ddcc..c8767390163 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 a6bdd2e7ea6..6aacb112949 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 b67c81bf8b7..d5e510ca02b 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/TransactionJsonSerializer.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/TransactionJsonSerializer.scala @@ -16,7 +16,7 @@ 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.reader.LeaseDetails +import com.wavesplatform.state.LeaseDetails import com.wavesplatform.state.{Blockchain, DataEntry, InvokeScriptResult, TxMeta} import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction} @@ -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), 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 e8bfbc82b05..f7c447ad04a 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 @@ -27,7 +27,6 @@ import com.wavesplatform.api.http.assets.AssetsApiRoute.{ } 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 @@ -51,7 +50,6 @@ case class AssetsApiRoute( settings: RestAPISettings, serverRequestTimeout: FiniteDuration, wallet: Wallet, - transactionPublisher: TransactionPublisher, blockchain: Blockchain, compositeBlockchain: () => Blockchain, time: Time, 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 0736d9d5e4d..dd8f3a13a10 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 9126bad6c10..67eb44c4716 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 diff --git a/node/src/main/scala/com/wavesplatform/block/BlockSnapshot.scala b/node/src/main/scala/com/wavesplatform/block/BlockSnapshot.scala index 8ab14ac711c..3e59eb342f1 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 34f43c87fef..731c040ca4e 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 bafe367ba9b..08b299029ad 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 47fc85f3e83..d6c4c36830c 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 e5b5c0082fc..f2c1ba14121 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 4b11f412893..f14575ddfb0 100644 --- a/node/src/main/scala/com/wavesplatform/database/RocksDBWriter.scala +++ b/node/src/main/scala/com/wavesplatform/database/RocksDBWriter.scala @@ -18,10 +18,9 @@ import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.lang.ValidationError import com.wavesplatform.protobuf.block.PBBlocks import com.wavesplatform.protobuf.snapshot.{TransactionStateSnapshot, TransactionStatus as PBStatus} -import com.wavesplatform.protobuf.{ByteStrExt, ByteStringExt} +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 @@ -298,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]() @@ -452,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, @@ -484,15 +483,16 @@ class RocksDBWriter( expiredKeys ++= updateHistory(rw, Keys.assetDetailsHistory(asset), threshold, Keys.assetDetails(asset)) } - 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))) + 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)) } @@ -523,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) @@ -798,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) @@ -953,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 30ea8448aac..ba312758032 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,15 +16,13 @@ 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, @@ -35,6 +30,7 @@ import com.wavesplatform.transaction.{ PaymentTransaction, Transaction, TransactionParsers, + TxPositiveAmount, TxValidationError } import com.wavesplatform.utils.* @@ -44,6 +40,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 +142,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 +247,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) @@ -672,8 +653,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 +689,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 +712,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 4e86d87e386..47098d2b57d 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 64b0cb220bd..69a46ba8683 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 4facb60cd9a..d52c4acb413 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 ec84c72047a..239fdb8d06a 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 728b146088f..51823622019 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 c7dd0da5b03..394260ae147 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 5bcf2841477..f30aca1d4f9 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 00000000000..b363a964007 --- /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 00598610b88..c7a056c446b 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 448d0ef1f62..f0b949ecde4 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 99d193f72b9..b7297d641e2 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,24 +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 - 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() -> LeaseSnapshot(lt.sender, lt.recipient, lt.amount.value, LeaseDetails.Status.Expired(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") @@ -821,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 e69de29bb2d..00000000000 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 66cd1ea363e..34f1ea64b6f 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/LeaseSnapshot.scala b/node/src/main/scala/com/wavesplatform/state/LeaseSnapshot.scala deleted file mode 100644 index e49fa4a09d4..00000000000 --- a/node/src/main/scala/com/wavesplatform/state/LeaseSnapshot.scala +++ /dev/null @@ -1,30 +0,0 @@ -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/LeaseStaticInfo.scala b/node/src/main/scala/com/wavesplatform/state/LeaseStaticInfo.scala new file mode 100644 index 00000000000..5b17fe3ad34 --- /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 fb8668ecab5..9945f45b9e9 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 52f6e43c8dc..323314e8167 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 258c6704860..00000000000 --- 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 95% rename from node/src/main/scala/com/wavesplatform/state/reader/SnapshotBlockchain.scala rename to node/src/main/scala/com/wavesplatform/state/SnapshotBlockchain.scala index abc3ac79d5e..51146f08bb9 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} @@ -85,16 +83,11 @@ case class SnapshotBlockchain( SnapshotBlockchain.assetDescription(asset, snapshot, height, inner) 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) + 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)] = @@ -272,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 a8f1bac7192..612ef4bb03a 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 22f2ee7d90f..febef0993b9 100644 --- a/node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala +++ b/node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala @@ -1,21 +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, 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, 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} @@ -24,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, LeaseSnapshot] = 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(), @@ -40,58 +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, LeaseSnapshot(sender, recipient, amount, status)) => - val pbStatus = status match { - case Status.Active => - 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) - }.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] = @@ -119,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]] = @@ -130,113 +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, 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 - .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, @@ -246,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, LeaseSnapshot] = 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(), @@ -269,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, @@ -329,16 +164,9 @@ object StateSnapshot { } .map(_.toMap) - 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( @@ -371,20 +199,6 @@ object StateSnapshot { issued ++ updated } - private def resolvedLeaseStates( - blockchain: Blockchain, - leaseStates: Map[ByteStr, LeaseSnapshot], - aliases: Map[Alias, Address] - ): Map[ByteStr, LeaseSnapshot] = - 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) => @@ -406,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 7d99d893193..09f31f7897e 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,127 +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) - - snapshot.leaseStates.foreach { case (leaseId, details) => - 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) - ) + } 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.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 = @@ -140,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) } @@ -202,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 add355b2ca6..5adacff6fff 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 ca8c9b7ffdb..e5c4d4c3778 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 9d61ba93120..cc9339a6399 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 310a6daa2f0..c4402a8966d 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/DiffsCommon.scala b/node/src/main/scala/com/wavesplatform/state/diffs/DiffsCommon.scala index 5c83e03e367..fba24bac951 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, LeaseSnapshot, 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 = LeaseSnapshot(sender, recipient, amount, LeaseDetails.Status.Active) 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 -> LeaseSnapshot.fromDetails(actionInfo)) + cancelledLeases = Map(leaseId -> LeaseDetails.Status.Cancelled(blockchain.height, Some(cancelTxId))) ) } yield snapshot } 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 cedecbd0a51..f6ad69f6faa 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 4201c4a3e99..5618d892161 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 @@ -24,7 +24,7 @@ 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 @@ -640,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 f1f02c6d1c4..ecd082bffb6 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 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 78c08900af9..70ccce8b8a4 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 @@ -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 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 15c83699c37..80b43025921 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, LeaseSnapshot, 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, 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, LeaseSnapshot(sender, recipient, data.amount, status = LeaseDetails.Status.Expired(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 eeb2db63c5b..f4727c101c8 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 e1e343e4bf7..b86dabff156 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, LeaseSnapshot, 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,38 +18,33 @@ case object CancelLeasesToDisabledAliases extends PatchOnFeature(BlockchainFeatu height: Int ) - def patchData: Map[ByteStr, (LeaseSnapshot, 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 -> (LeaseSnapshot( - sender, - recipientAlias, - cancelDetails.amount, - LeaseDetails.Status.Expired(0) - ) -> 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/TxValidationError.scala b/node/src/main/scala/com/wavesplatform/transaction/TxValidationError.scala index eb8d61aa53b..ff057fd7f7b 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/TxValidationError.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/TxValidationError.scala @@ -29,7 +29,7 @@ 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 exist." } 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 fdd2ec8aa27..8e451eded75 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/WavesEnvironment.scala @@ -25,7 +25,7 @@ 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} -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/validation/impl/LeaseTxValidator.scala b/node/src/main/scala/com/wavesplatform/transaction/validation/impl/LeaseTxValidator.scala index 7c21869f2a3..c1f5548f4f8 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 c8b1cecde77..23adf485ff9 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 fd4a77d2082..ec1000cee03 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/TestValues.scala b/node/src/test/scala/com/wavesplatform/TestValues.scala index 6623c6b0e2a..e25d435d1e3 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 8bd5f4ac889..beb28b07021 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 9f1a6b67ed2..a8a48c170f8 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") { @@ -116,13 +84,12 @@ class CustomJsonMarshallerSpec private val assetsRoute = AssetsApiRoute( restAPISettings, 60.seconds, - testWallet, - publisher, - blockchain, - mock[() => SnapshotBlockchain], + domain.wallet, + domain.blockchain, + () => domain.blockchain, ntpTime, - accountsApi, - assetsApi, + domain.accountsApi, + domain.assetsApi, 1000, new RouteTimeout(60.seconds)(sharedScheduler) ).route @@ -132,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/consensus/FPPoSSelectorTest.scala b/node/src/test/scala/com/wavesplatform/consensus/FPPoSSelectorTest.scala index 3b536e78fa6..543e8faa191 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 f37a7bfa9a7..e1d08d1d7ab 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 f80301d13b4..66532bd1417 100644 --- a/node/src/test/scala/com/wavesplatform/db/WithState.scala +++ b/node/src/test/scala/com/wavesplatform/db/WithState.scala @@ -21,7 +21,7 @@ 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, NgState, StateSnapshot, TxStateSnapshotHashBuilder} import com.wavesplatform.test.* @@ -40,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) diff --git a/node/src/test/scala/com/wavesplatform/features/RideV5LimitsChangeTest.scala b/node/src/test/scala/com/wavesplatform/features/RideV5LimitsChangeTest.scala index a8b6be70636..79c3dcc7fd5 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/history/Domain.scala b/node/src/test/scala/com/wavesplatform/history/Domain.scala index f4995c52392..eacb987fda1 100644 --- a/node/src/test/scala/com/wavesplatform/history/Domain.scala +++ b/node/src/test/scala/com/wavesplatform/history/Domain.scala @@ -25,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} diff --git a/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala index 6484726d1e4..595ff39cb29 100644 --- a/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala @@ -59,7 +59,6 @@ class AssetsRouteSpec restAPISettings, 60.seconds, testWallet, - DummyTransactionPublisher.accepting, d.blockchain, () => d.blockchain.snapshotBlockchain, TestTime(), @@ -287,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 29089460466..56109d50022 100644 --- a/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/DebugApiRouteSpec.scala @@ -3,50 +3,37 @@ package com.wavesplatform.http 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.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 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.TimeUnit +import java.util.concurrent.{ConcurrentHashMap, TimeUnit} import scala.concurrent.duration.* import scala.util.Random @@ -56,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 = () @@ -89,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 { @@ -128,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) } @@ -170,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 } } } @@ -210,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 @@ -223,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] @@ -238,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] @@ -254,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] @@ -421,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] @@ -437,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" : [ ], @@ -473,14 +379,14 @@ class DebugApiRouteSpec | }, | "assetId" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${invoker.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -495,7 +401,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${invoker.toAddress}" | } | } | }, @@ -515,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", @@ -549,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" : [ ], @@ -581,7 +487,7 @@ class DebugApiRouteSpec | } ], | "transfers" : [ { | "address" : "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC", - | "asset" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "asset" : "${issue.asset}", | "amount" : 1 | } ], | "issues" : [ ], @@ -603,7 +509,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${invoker.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -618,7 +524,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${invoker.toAddress}" | } | } | }, @@ -749,7 +655,7 @@ class DebugApiRouteSpec | "value" : 1 | }, { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } ] | }, { | "name" : "ScriptTransfer.@complexity", @@ -780,7 +686,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | }, { @@ -826,7 +732,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] @@ -882,7 +788,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] @@ -950,7 +856,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] @@ -1030,7 +936,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] @@ -1122,7 +1028,7 @@ class DebugApiRouteSpec | }, | "asset" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | } | } | } ] @@ -1139,7 +1045,7 @@ class DebugApiRouteSpec |}, { | "type" : "asset", | "context" : "transfer", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "id" : "${issue.asset}", | "result" : "failure", | "vars" : [ { | "name" : "test", @@ -1169,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" : [ ], @@ -1209,7 +1115,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${invoker.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -1224,7 +1130,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${invoker.toAddress}" | } | } | }, @@ -1329,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" : [ ], @@ -1343,7 +1249,7 @@ class DebugApiRouteSpec | "transfers" : [ ], | "issues" : [ ], | "reissues" : [ { - | "assetId" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "assetId" : "${issue.asset}", | "isReissuable" : false, | "quantity" : 1 | } ], @@ -1364,7 +1270,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${invoker.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -1379,7 +1285,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${invoker.toAddress}" | } | } | }, @@ -1397,7 +1303,7 @@ class DebugApiRouteSpec | "type" : "Array", | "value" : [ { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | }, { | "type" : "Int", | "value" : 1 @@ -1421,7 +1327,7 @@ class DebugApiRouteSpec | "value" : { | "assetId" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | }, | "quantity" : { | "type" : "Int", @@ -1448,7 +1354,7 @@ class DebugApiRouteSpec |}, { | "type" : "asset", | "context" : "reissue", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "id" : "${issue.asset}", | "result" : "failure", | "vars" : [ { | "name" : "test", @@ -1478,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" : [ ], @@ -1493,7 +1399,7 @@ class DebugApiRouteSpec | "issues" : [ ], | "reissues" : [ ], | "burns" : [ { - | "assetId" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "assetId" : "${issue.asset}", | "quantity" : 1 | } ], | "sponsorFees" : [ ], @@ -1512,7 +1418,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${invoker.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -1527,7 +1433,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${invoker.toAddress}" | } | } | }, @@ -1545,7 +1451,7 @@ class DebugApiRouteSpec | "type" : "Array", | "value" : [ { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | }, { | "type" : "Int", | "value" : 1 @@ -1566,7 +1472,7 @@ class DebugApiRouteSpec | "value" : { | "assetId" : { | "type" : "ByteVector", - | "value" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx" + | "value" : "${issue.asset}" | }, | "quantity" : { | "type" : "Int", @@ -1589,7 +1495,7 @@ class DebugApiRouteSpec |}, { | "type" : "asset", | "context" : "burn", - | "id" : "5PjDJaGfSPJj4tFzMRCiuuAasKg5n8dJKXKenhuwZexx", + | "id" : "${issue.asset}", | "result" : "failure", | "vars" : [ { | "name" : "test", @@ -1618,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 canceledLeaseId = 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) @@ -1633,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'$canceledLeaseId') - | ] - | 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(canceledLeaseId) - .returns(Some(TxMeta(Height(1), Status.Succeeded, 0L))) - .anyNumberOfTimes() - - (blockchain.leaseDetails _) - .when(canceledLeaseId) - .returns(Some(LeaseDetails(dAppPk, TxHelpers.defaultAddress, leaseCancelAmount, LeaseDetails.Status.Active, invoke.id(), 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] @@ -1726,7 +1582,7 @@ class DebugApiRouteSpec | "sender" : "$dAppAddress", | "recipient" : "${recipient1.bytes}", | "amount" : 100, - | "height" : 1, + | "height" : ${domain.blockchain.height}, | "status" : "active", | "cancelHeight" : null, | "cancelTransactionId" : null @@ -1734,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" : "$canceledLeaseId", - | "originTransactionId" : "${invoke.id()}", + | "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" : [ ], @@ -1777,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" : "$canceledLeaseId", - | "originTransactionId" : "${invoke.id()}", - | "sender" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "recipient" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", - | "amount" : $leaseCancelAmount, - | "height" : 1, + | "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" : [ ] @@ -1818,7 +1673,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${caller.toAddress}" | } | } | }, @@ -1828,7 +1683,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${caller.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -1836,7 +1691,7 @@ class DebugApiRouteSpec | }, | "originCallerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${caller.publicKey}" | }, | "transactionId" : { | "type" : "ByteVector", @@ -1847,7 +1702,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${caller.toAddress}" | } | } | }, @@ -1861,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", @@ -1933,7 +1744,7 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25932 + | "value" : 51998 | }, { | "name" : "Lease.@args", | "type" : "Array", @@ -1942,7 +1753,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd" + | "value" : "${recipient1.bytes}" | } | } | }, { @@ -1959,7 +1770,7 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25931 + | "value" : 51997 | }, { | "name" : "Alias.@args", | "type" : "Array", @@ -1974,7 +1785,7 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25930 + | "value" : 51996 | }, { | "name" : "Lease.@args", | "type" : "Array", @@ -2000,45 +1811,7 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25929 - | }, { - | "name" : "LeaseCancel.@args", - | "type" : "Array", - | "value" : [ { - | "type" : "ByteVector", - | "value" : "$canceledLeaseId" - | } ] - | }, { - | "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" : "$canceledLeaseId" - | } - | } - | }, { - | "type" : "Array", - | "value" : [ ] - | } ] - | }, { - | "name" : "cons.@complexity", - | "type" : "Int", - | "value" : 1 - | }, { - | "name" : "@complexityLimit", - | "type" : "Int", - | "value" : 25927 + | "value" : 51995 | }, { | "name" : "cons.@args", | "type" : "Array", @@ -2065,15 +1838,7 @@ class DebugApiRouteSpec | } | }, { | "type" : "Array", - | "value" : [ { - | "type" : "LeaseCancel", - | "value" : { - | "leaseId" : { - | "type" : "ByteVector", - | "value" : "$canceledLeaseId" - | } - | } - | } ] + | "value" : [ ] | } ] | }, { | "name" : "cons.@complexity", @@ -2082,7 +1847,7 @@ class DebugApiRouteSpec | }, { | "name" : "@complexityLimit", | "type" : "Int", - | "value" : 25926 + | "value" : 51994 | }, { | "name" : "cons.@args", | "type" : "Array", @@ -2094,7 +1859,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3NAgxLPGnw3RGv9JT6NTDaG5D1iLUehg2xd" + | "value" : "${recipient1.bytes}" | } | } | }, @@ -2130,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" : "$canceledLeaseId" + | "recipient" : { + | "type" : "Alias", + | "value" : { + | "alias" : { + | "type" : "String", + | "value" : "some_alias" + | } + | } + | }, + | "amount" : { + | "type" : "Int", + | "value" : 20 + | }, + | "nonce" : { + | "type" : "Int", + | "value" : 2 | } | } | } ] @@ -2147,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] @@ -2850,7 +2651,7 @@ class DebugApiRouteSpec | "leases" : [ ], | "leaseCancels" : [ ], | "invokes" : [ { - | "dApp" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9", + | "dApp" : "${dAppPk.toAddress}", | "call" : { | "function" : "test", | "args" : [ ] @@ -2878,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" : [ ], @@ -2917,7 +2718,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${caller.toAddress}" | } | } | }, @@ -2927,7 +2728,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${dAppPk.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -2935,7 +2736,7 @@ class DebugApiRouteSpec | }, | "originCallerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${caller.publicKey}" | }, | "transactionId" : { | "type" : "ByteVector", @@ -2946,7 +2747,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${dAppPk.toAddress}" | } | } | }, @@ -3086,7 +2887,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${caller.toAddress}" | } | } | }, @@ -3096,7 +2897,7 @@ class DebugApiRouteSpec | }, | "callerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${caller.publicKey}" | }, | "feeAssetId" : { | "type" : "Unit", @@ -3104,7 +2905,7 @@ class DebugApiRouteSpec | }, | "originCallerPublicKey" : { | "type" : "ByteVector", - | "value" : "9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ" + | "value" : "${caller.publicKey}" | }, | "transactionId" : { | "type" : "ByteVector", @@ -3115,7 +2916,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${caller.toAddress}" | } | } | }, @@ -3136,7 +2937,7 @@ class DebugApiRouteSpec | "value" : { | "bytes" : { | "type" : "ByteVector", - | "value" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | "value" : "${dAppPk.toAddress}" | } | } | }, { @@ -3209,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() @@ -3263,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 @@ -3316,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 { @@ -3496,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)) @@ -3506,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 d58f5337c4d..3cbbe708206 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 667863482bf..9004252c75f 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.* diff --git a/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala b/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala index f05881ba678..d714fd1db6a 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 fcd39a0b456..fae6c84f9f4 100644 --- a/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala @@ -2,48 +2,39 @@ 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.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 +46,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 +78,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 +102,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 +111,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 snapshot = differ(transaction) - val transactionsApi = stub[CommonTransactionsApi] - (transactionsApi.transactionById _) - .when(transaction.id()) - .returning( - Some( - TransactionMeta.Ethereum( - Height(1), - transaction, - Status.Succeeded, - 15L, - snapshot.ethereumTransactionMeta.values.headOption, - snapshot.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 snapshot = differ(transaction) - val transactionsApi = stub[CommonTransactionsApi] - (transactionsApi.transactionById _) - .when(transaction.id()) - .returning( - Some( - TransactionMeta.Ethereum( - Height(1), - transaction, - Status.Succeeded, - 15L, - snapshot.ethereumTransactionMeta.values.headOption, - snapshot.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 +507,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 +540,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 +570,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 +597,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 +631,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 +671,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" -> 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 + } } - } - 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 +902,8 @@ class TransactionsRouteSpec "i", "default.@args", "Address.@args", + "Address.@complexity", + "@complexityLimit", "Lease.@args", "Lease.@complexity", "@complexityLimit", @@ -1162,11 +916,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 +937,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 +1049,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 +1113,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 +1131,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 +1153,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 21cb7bc5394..12625590359 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 2f8f9d41700..bdf68e8dce8 100644 --- a/node/src/test/scala/com/wavesplatform/mining/BlockWithMaxBaseTargetTest.scala +++ b/node/src/test/scala/com/wavesplatform/mining/BlockWithMaxBaseTargetTest.scala @@ -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 9c5fb734ddd..9aa7ad379a9 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 724e927fdc3..118fa75c68d 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 846e27b5a8f..eaec4bceb5d 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 diff --git a/node/src/test/scala/com/wavesplatform/state/BlockChallengeTest.scala b/node/src/test/scala/com/wavesplatform/state/BlockChallengeTest.scala index 2755cf490ae..ec22a04b94a 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/LightNodeTest.scala b/node/src/test/scala/com/wavesplatform/state/LightNodeTest.scala index dc2e309d20a..409c6a7a783 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,7 +135,9 @@ 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 = @@ -147,7 +150,7 @@ class LightNodeTest extends PropSpec with WithDomain { PeerDatabase.NoOp, Scheduler.global )( - null, + new EmbeddedChannel(), _ ) @@ -172,7 +175,8 @@ class LightNodeTest extends PropSpec with WithDomain { 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 1a7deaec2e7..db98f19acae 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 d1aab7e32ec..b6709947053 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/diffs/BlockDifferTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/BlockDifferTest.scala index 1983fbc5e04..68b26d83418 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, 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 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 b7a05d10476..6fc1a3bade6 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/ExchangeTransactionDiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala index b8ce4f3a999..233baedc0ca 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ExchangeTransactionDiffTest.scala @@ -33,7 +33,6 @@ 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} @@ -2271,7 +2270,7 @@ 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 = 1_000_000, version = TxVersion.V1 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 1a534bb85cc..c94fc2bd629 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/MassTransferTransactionDiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/MassTransferTransactionDiffTest.scala @@ -11,7 +11,7 @@ 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 WithDomain { @@ -31,7 +31,7 @@ class MassTransferTransactionDiffTest extends PropSpec with WithDomain { 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 => @@ -83,7 +83,7 @@ class MassTransferTransactionDiffTest extends PropSpec with WithDomain { 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) } @@ -100,7 +100,7 @@ class MassTransferTransactionDiffTest extends PropSpec with WithDomain { 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) } @@ -114,7 +114,7 @@ class MassTransferTransactionDiffTest extends PropSpec with WithDomain { 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) 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 a0da315ebac..3c2f74af965 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/ScriptComplexityCountTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ScriptComplexityCountTest.scala index 82808fcb643..3551e6b70fa 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/ci/RideV5FailRejectTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/RideV5FailRejectTest.scala index 77c51114845..e5e53e4259f 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 @@ -140,7 +140,8 @@ class RideV5FailRejectTest extends PropSpec with WithDomain { d.appendBlock(invokeTx) d.blockchain.transactionInfo(invokeTx.id()).get._1.status == Status.Succeeded shouldBe false d.liquidSnapshot.sponsorships shouldBe Map() - d.liquidSnapshot.leaseStates 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() 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 3b5dd17e357..34cffe18574 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 @@ -76,12 +76,13 @@ 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.liquidSnapshot.sponsorships shouldBe Map() - d.liquidSnapshot.leaseStates shouldBe Map() - d.liquidSnapshot.assetStatics shouldBe Map() - d.liquidSnapshot.assetNamesAndDescriptions shouldBe Map() - d.liquidSnapshot.assetVolumes shouldBe Map() - d.liquidSnapshot.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.liquidSnapshot.balances shouldBe { val reward = d.blockchain.blockReward(d.blockchain.height).get 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 7f1f0695582..57127d24527 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.liquidSnapshot.leaseStates.head._2 - lease1.status shouldBe a[Cancelled] - lease1.recipient shouldBe dApp2Address - lease1.amount shouldBe 1 - val lease2 = d.liquidSnapshot.leaseStates.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.liquidSnapshot.leaseStates 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.liquidSnapshot.leaseStates 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/freecall/InvokeExpressionTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/freecall/InvokeExpressionTest.scala index fad4021826c..30594e40f70 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 @@ -10,12 +10,10 @@ import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.script.v1.ExprScript import com.wavesplatform.lang.v1.ContractLimits import com.wavesplatform.lang.v1.compiler.TestCompiler -import com.wavesplatform.protobuf.ByteStringExt -import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot.AssetStatic 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.{AssetInfo, AssetVolumeInfo, BinaryDataEntry, BooleanDataEntry} +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} @@ -332,7 +330,7 @@ class InvokeExpressionTest extends PropSpec with ScalaCheckPropertyChecks with W private[this] def checkAsset( invoke: InvokeExpressionTransaction, - static: AssetStatic, + static: AssetStaticInfo, info: AssetInfo, volume: AssetVolumeInfo ): Assertion = { @@ -342,7 +340,7 @@ class InvokeExpressionTest extends PropSpec with ScalaCheckPropertyChecks with W volume.isReissuable shouldBe TestAssetReissuable static.decimals shouldBe TestAssetDecimals static.nft shouldBe false - static.issuerPublicKey.toPublicKey shouldBe invoke.sender + static.issuer shouldBe invoke.sender } } 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 846ea80cec3..7c52bf51ab5 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 77e52a6ebea..151d899a557 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 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 2523329d952..ac2a53c24c2 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 @@ -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.* 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 548f9671fc2..08f7b503b91 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 78c5df57228..00000000000 --- a/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotProtoTest.scala +++ /dev/null @@ -1,100 +0,0 @@ -package com.wavesplatform.state.snapshot - -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.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.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) -> 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(0, None) - ) - ), - 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 abe2f5495fd..4efc079976c 100644 --- a/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotStorageTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/snapshot/StateSnapshotStorageTest.scala @@ -5,25 +5,21 @@ 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 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.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() -> LeaseSnapshot(sender.publicKey, recipient, leaseTx.amount.value, Active) + newLeases = Map( + leaseTx.id() -> LeaseStaticInfo(leaseTx.sender, recipient, leaseTx.amount, leaseTx.id(), d.blockchain.height + 1) ) ) ) @@ -192,13 +192,8 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { senderAddress -> LeaseBalance(0, 0), recipient -> LeaseBalance(0, 0) ), - leaseStates = Map( - leaseTx.id() -> LeaseSnapshot( - PublicKey(ByteStr.fill(KeyLength)(0)), - Address(Array.fill(Address.HashLength)(0)), - 0, - Cancelled(0, None) - ) + cancelledLeases = Map( + leaseTx.id() -> LeaseDetails.Status.Cancelled(d.blockchain.height + 1, Some(leaseCancelTx.id())) ) ) ) @@ -220,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( @@ -322,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) @@ -330,8 +325,8 @@ class StateSnapshotStorageTest extends PropSpec with WithDomain { assetNamesAndDescriptions = Map( dAppAssetId -> AssetInfo("name", "description", Height(height)) ), - leaseStates = Map( - leaseId -> LeaseSnapshot(dAppPk, senderAddress, 123, Active) + newLeases = Map( + leaseId -> LeaseStaticInfo(dAppPk, senderAddress, TxPositiveAmount(123), invokeId, height) ), accountData = Map( dAppPk.toAddress -> Map("key" -> StringDataEntry("key", "abc")) @@ -356,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 eb25b4b1ebc..b17fe3586ab 100644 --- a/node/src/test/scala/com/wavesplatform/state/snapshot/TxStateSnapshotHashSpec.scala +++ b/node/src/test/scala/com/wavesplatform/state/snapshot/TxStateSnapshotHashSpec.scala @@ -1,212 +1,318 @@ package com.wavesplatform.state.snapshot -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.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, Waves} -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 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 - 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 snapshot = StateSnapshot( - balances = VectorMap( - (address1, Waves) -> (addr1PortfolioDiff.balance + addr1Balance), - (address2, assetId1) -> addr2PortfolioDiff.assets.head._2 - ), - leaseBalances = Map(address1 -> addr1PortfolioDiff.lease), - assetStatics = StateSnapshot.assetStatics( - VectorMap( - assetId1 -> assetInfo1, - assetId2 -> assetInfo2, - assetId3 -> assetInfo3, - assetId4 -> assetInfo4 + ) + + private val accountScript = TSS(accountScripts = + Some( + TSS.AccountScript( + bs(signer101.publicKey.arr), + testScript, + 250 ) + ) + ) + + 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) ), - assetVolumes = VectorMap( - assetId1 -> updatedAssetVolumeInfo1, - assetId2 -> assetInfo2.volume, - assetId3 -> updatedAssetVolumeInfo3, - assetId4 -> assetInfo4.volume + newLeases = Seq( + TSS.NewLease(leaseId, bs(signer101.publicKey.arr), bs(address2.bytes), 25.waves) + ) + ) + + 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) + ) + ) + + private val sponsorship = TSS( + sponsorships = Seq(TSS.Sponsorship(assetId2, 5500)) + ) + + 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) + ) + ) + + private val newAsset = TSS( + assetStatics = Seq( + TSS.NewAsset(assetId1, hashInt(0x88aadd55), nft = true), + TSS.NewAsset(assetId2, hashInt(0x88aadd55), decimals = 8) ), - assetNamesAndDescriptions = VectorMap( - assetId1 -> updatedAssetInfo1, - assetId2 -> updatedAssetInfo2, - assetId3 -> assetInfo3.dynamic, - assetId4 -> assetInfo4.dynamic + assetVolumes = Seq( + TSS.AssetVolume(assetId2, true, bs((BigInt(Long.MaxValue) * 10).toByteArray)), + TSS.AssetVolume(assetId1, false, bs(BigInt(1).toByteArray)) ), - aliases = Map(addr1Alias1 -> address1, addr2Alias -> address2, addr1Alias2 -> address1), - orderFills = Map(orderId -> volumeAndFee), - 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)), - sponsorships = Map(assetId1 -> sponsorship) + assetNamesAndDescriptions = Seq() ) - def hash(bs: Seq[Array[Byte]]): ByteStr = ByteStr(com.wavesplatform.crypto.fastHash(bs.reduce(_ ++ _))) + 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 + ) - property("correctly create transaction state snapshot hash from snapshot") { - withDomain(DomainPresets.RideV6, balances = Seq(AddrWithBalance(address1, addr1Balance), AddrWithBalance(address2, addr2Balance))) { _ => - 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 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 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) - ++ 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), - 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 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 compute hash using previous value") { - val txStateHash = TxStateSnapshotHashBuilder.Result(ByteStr.fill(DigestLength)(1)) - val prevHash = ByteStr.fill(DigestLength)(2) + 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 - txStateHash.createHash(prevHash) shouldBe hash(Seq(prevHash.arr ++ txStateHash.txStateSnapshotHash.arr)) + 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/test/SharedDomain.scala b/node/src/test/scala/com/wavesplatform/test/SharedDomain.scala index 568730401e0..cedd61b6dec 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/TxHelpers.scala b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala index 2c91c553678..6d396c8f648 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala @@ -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/utils/EmptyBlockchain.scala b/node/src/test/scala/com/wavesplatform/utils/EmptyBlockchain.scala index 6ee1556f2d1..27f41f53e81 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 ac355cad702..fab2f0fa2dc 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 936cbb22f35..52c8f395c44 100644 --- a/node/src/test/scala/com/wavesplatform/utx/UtxPoolSpecification.scala +++ b/node/src/test/scala/com/wavesplatform/utx/UtxPoolSpecification.scala @@ -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) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index c5ca5a3c525..1c5115023f8 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-84-SNAPSHOT" classifier "protobuf-src" intransitive () + "com.wavesplatform" % "protobuf-schemas" % "1.5.1-85-SNAPSHOT" classifier "protobuf-src" intransitive () private def akkaModule(module: String) = "com.typesafe.akka" %% s"akka-$module" % "2.6.21" @@ -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/ride-runner/src/main/scala/com/wavesplatform/database/rocksdb/package.scala b/ride-runner/src/main/scala/com/wavesplatform/database/rocksdb/package.scala index 815cd6bce93..b05dbdf68e0 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 c9a15ca1a87..5e1c1232408 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 b93a3553217..2c7d1f4d81d 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 74bff800b17..02165cacd1a 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)) } ) }