From ed3c5f7d3cb77385ac4cce25050994a34dcd5c08 Mon Sep 17 00:00:00 2001 From: Aleksandr Dolgavin Date: Wed, 15 Nov 2023 15:01:38 -0800 Subject: [PATCH] Transaction snapshots (#1065) * Added types * Renamed a file * Changed asset- and waves balances structures * Commented a function * Changed addresses types * Changed balanceAsset * Added a constructor * Added decimals to asset snapshot * Changed uint8 to int8 * Added a field for asset reissuability * Changed the type of total quantity * removed the pointer * Changed the type of orderID * Make 'SnapshotManager' as an interface type. * Replace 'proto.assetID' to 'crypto.Digest'. * Replace 'proto.Address' to 'proto.WavesAddress'. * Changed leaseID type * changed types of leaseIn and leaseOut * Removed the pointer types from AliasSnapshot * Change a bit 'AtomicSnapshot' interface. * Update snapshot types. * Removed unnecessary fields in 'AccountScriptSnapshot' struct. * Remove unnecessary types. * Change 'StaticAssetInfoSnapshot.Issuer' field to 'StaticAssetInfoSnapshot.IssuerPublicKey'. * Rename 'AssetReissuabilitySnapshot' to 'AssetVolumeSnapshot'. * Add feature block reward distribution (#1082) * add feature BlockRewardDistribution * fix tests * Improvement of the test on reward distribution to check that remainder of division goes to miner. * refactor after rewiev * fix feature info and add reward addresses in testnet and stagenet configs * DAO and buyback addresses added for MainNet. Formatting of settings files fixed. --------- Co-authored-by: Alexey Kiselev Co-authored-by: Nikolay Eskov * change actions count with feature BlockRewardDistribution activated (#1088) * change count actions with feature BlockRewardDistribution activated * fix comment in script * Merge fix. * Fix version in comment * Eth transaction refactoring and tests --------- Co-authored-by: Alexey Kiselev Co-authored-by: Nikolay Eskov * Ride add rewards to block info (#1096) * add feature BlockRewardDistribution * fix tests * Improvement of the test on reward distribution to check that remainder of division goes to miner. * refactor after rewiev * fix feature info and add reward addresses in testnet and stagenet configs * change count actions with feature BlockRewardDistribution activated * Ride version 7 added. Extended version of BlockInfo ride object added. Ride types representation in objects stdlib description changed to simple string. Parsing of types added to code generation and updated in compiler. Code generation updated to support Tuple types. * Fixed code generation for RideV7. Fixed compilation of V7 scripts. Added rewards structure and function to SmartState to get the block rewards. Added test on script accessing new rewards field on BlockInfo. * Fixed FunctionCall usage in tests * Fixed sorting of rewards with stable sort. * Script activation check added for RideV7. * Code improvements * Restored recursive check of list types in generated code. * Generation of simplified code for tuple type checks. * Duplicated tests removed * Fixed lib version check in Ride compiler --------- Co-authored-by: Anton Ilin Co-authored-by: Anton Ilin <48175203+Anton-Rampage@users.noreply.github.com> * Fix clear-text logging of sensitive information. (#1128) * Bump golang.org/x/sync from 0.2.0 to 0.3.0 (#1129) Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.2.0 to 0.3.0. - [Commits](https://github.com/golang/sync/compare/v0.2.0...v0.3.0) --- updated-dependencies: - dependency-name: golang.org/x/sync dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alexey Kiselev * Added 'LeaseStateStatus'. Changed types in 'LeaseStateSnapshot'. * Rename 'LeaseStateStatus.Status' to 'LeaseStateStatus.Value'. * Add methods to 'SnapshotManager' interface. Changed 'AtomicSnapshot' interface. * Snapshot applier (#1145) * Create marshal/unmarshal methods for 'leasing' structure in 'pkg/state'. * WIP: rollbackable map. * Return 'balanceProfile' and 'wavesBalanceRecord' by value. * Started 'snapshotApplier.' * Add apply methods for assets snapshot applying. * Add 'snapshotApplier.applySponsorship' method. * Add 'snapshotApplier.applyAccountScript' method. * Refactor 'ordersVolumes'. * Add 'snapshotApplier.applyFilledVolumeAndFee' method. * Add 'snapshotApplier.applyDataEntry' method. * Add 'snapshotApplier.applyLeaseState' method. * Revert "WIP: rollbackable map." This reverts commit cb3496915e101cd683f8174c5e801cbe924d6bca. * Created and used 'SnapshotApplierInfo' interface in 'state' package. * Create 'SnapshotApplier' interface in 'state' package. * Refactor 'SnapshotApplier' interface. * Remove 'SnapshotApplierInfo' interface. * Extracted 'snapshotApplierStorages'. * Rename 'ApplyDataEntry' to 'ApplyDataEntries'. * Replace 'SnapshotManager' to 'SnapshotApplier'. * Node transaction snapshots (#1078) * Added a conversion function * Added payment transaction conversion * Added transfer tx conversion * Transfer tx conversion changed * Added issue and reissue tx conversions * Issue * Issue, reissue, burn, exchange * Hanled lease transactions * Finished performers * Added snapshots for all types of transactions * Fixed types after merging * Fixed issue snapshot mistake * Added rewards snapshot in append block * Changed functions to newest * Added snapshots from actions * Removed todos * Deleted useless code * Fixed a mistake with leasing cancel * Added tests for issue and reissue transactions * Fixed tests * fixed implicit memory aliasing * Added tests for burn, lease, lease cancel, exchange and create alias transactions * Added tests for data, sponsorship, set account and asset script transactions * Added a test for the invoke transaction snapshots * Fixed after merge * Fixed a function * Moved snapshot generation * Remove 'SnapshotManager' interface. * Added a todo * Refactored perfomerInfo * Refactored performer info again * Moved generation to snapshot generator * Fixed some linter issues * Added an asset atomic snapshot for issue transaction * Replaced current recording to storage to snapshot applier (#1162) * Replaced current recording to storage to snapshot applier * Replaced to snapshot applier, except balances * Fixed a comment * Removed the comment * Removed unnecessary code * Returned the linter command back * Revert "Replaced current recording to storage to snapshot applier (#1162)" (#1164) This reverts commit 51904b2ff66e4de39d5668461676b9415f879f2d. * * Commented invoke snapshots, import ok (#1165) * Replaced current recording to storage to snapshot applier * Replaced to snapshot applier, except balances * Fixed a comment * Removed the comment * Removed unnecessary code * Returned the linter command back * Added asset script atomic snapshot to the issue script actions * Fixed some linter issues * Fixed a few more linter issues * Fixed all linter issues * Fixed err shadowing * Fixed a mistake with issue counter * removed a useless line * Fixed applying order transactions * Fixed applying order transactions * Added lising snapshot to the tx diff generated group * Moved complexity saving into transaction performer * Moved complexity saving into transaction performer * removed a line * Fixed a bug with an empty sender * Used error.Is * Set an empty script for issue with sig * move snapshots types to proto * Fixed a bug with wrong action sender * Fixed a bug with wrong action sender * Add 'TransactionStatusSnapshot'. * Implemented 'AtomicSnapshot' interface for 'TransactionStatusSnapshot'. * Changed 'AssetScriptSnapshot' and 'scriptStorageState.setAssetScript'. Removed 'pk' arg from 'setAssetScript'. Removed two fields from 'AssetScriptSnapshot': - 'SenderPK' - 'Complexity' * * Commented invoke snapshots, import ok * moved setting complexity and set script to snapshot applier * Fixed a test and a mistake * Fixed renaming * Fixed a comment * Moved dapp complexity to applier * Added dapp complexity to set script as well * Fixed a check mistake * Fixed a test * removed empty script check * removed a todo * Removed useless code * Modified a test * Fixed same keys issue for data entry actions * Remove invoke snapshot (#1235) * moved complexity back * Reverted some changes, import ok * Returned account script and asset script txs, import ? * Uncommented internal snapshot, import ok * Fixed height problem with issue action and issue tx, import NO * Issue action reverted * Added log and issueCounter for issue action * Commented invoke snapshots, import ok --------- Co-authored-by: Nikolay Eskov --------- Co-authored-by: Anton Ilin Co-authored-by: Nikolay Eskov * Added asset script special snapshot * Added internal snapshot for script for issue tx * Separated internal tx snapshots. * Reduced 'internalSnapshot' interface, removed unnecessary code. * Fixed improper usages of 'balances.wavesBalance': replaced by 'balances.newestWavesBalance'. * Added invoke to snapshots, import ok, merged changes (#1236) * Removed unnecessary 'assetFoundInMap' function. * Changed 'LeaseStateSnapshot' according to the NODE-2626. Add new internal snapshots: - 'InternalLeaseStateActiveInfoSnapshot' - 'InternalLeaseStateCancelInfoSnapshot' * Use script from record in 'generateSnapshotsFromAssetsScriptsUncertain'. * Removed unnecessary condition in 'blockSnapshotsApplier.ApplyAssetScript'. * Removed unnecessary conditions in 'blockSnapshotsApplier.ApplyAccountScript'. * Fixed bug with saved current ride estimator version in 'blockSnapshotsApplier.ApplyAccountScript'. * Added invoke complexity update to internal snapshots * Fixed PR issues * Bump state version constant. * Fixed new PR issues --------- Signed-off-by: dependabot[bot] Co-authored-by: Nikolay Eskov Co-authored-by: Anton Ilin <48175203+Anton-Rampage@users.noreply.github.com> Co-authored-by: Alexey Kiselev Co-authored-by: Anton Ilin Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pkg/proto/snapshot_types.go | 212 ++++ pkg/proto/types.go | 8 + pkg/state/appender.go | 162 ++- pkg/state/balances.go | 51 +- pkg/state/balances_test.go | 13 +- pkg/state/block_differ.go | 2 +- pkg/state/block_differ_test.go | 24 +- pkg/state/common_test.go | 24 +- pkg/state/constants.go | 2 +- pkg/state/diff_applier.go | 21 +- pkg/state/ethereum_tx_test.go | 69 +- pkg/state/fee_validation.go | 8 +- pkg/state/fee_validation_test.go | 18 +- pkg/state/internal_snapshots_types.go | 73 ++ pkg/state/invoke_applier.go | 463 +++++---- pkg/state/invoke_applier_test.go | 12 +- pkg/state/keys.go | 6 +- pkg/state/leases.go | 42 +- pkg/state/leases_test.go | 3 +- pkg/state/orders_volume.go | 55 +- pkg/state/orders_volume_test.go | 48 +- pkg/state/scripts_storage.go | 64 +- pkg/state/scripts_storage_interface.go | 3 +- pkg/state/scripts_storage_moq_test.go | 51 +- pkg/state/scripts_storage_test.go | 5 +- pkg/state/snapshot_applier.go | 297 ++++++ pkg/state/snapshot_generator.go | 750 ++++++++++++++ pkg/state/snapshot_generator_internal_test.go | 949 ++++++++++++++++++ pkg/state/state.go | 17 +- pkg/state/transaction_checker.go | 10 +- pkg/state/transaction_checker_test.go | 170 ++-- pkg/state/transaction_differ.go | 26 +- pkg/state/transaction_differ_test.go | 119 ++- pkg/state/transaction_handler.go | 102 +- pkg/state/transaction_performer.go | 524 +++++----- pkg/state/transaction_performer_test.go | 178 ++-- pkg/state/tx_snapshot.go | 37 + 37 files changed, 3717 insertions(+), 901 deletions(-) create mode 100644 pkg/proto/snapshot_types.go create mode 100644 pkg/state/internal_snapshots_types.go create mode 100644 pkg/state/snapshot_applier.go create mode 100644 pkg/state/snapshot_generator.go create mode 100644 pkg/state/snapshot_generator_internal_test.go create mode 100644 pkg/state/tx_snapshot.go diff --git a/pkg/proto/snapshot_types.go b/pkg/proto/snapshot_types.go new file mode 100644 index 000000000..78fa771d5 --- /dev/null +++ b/pkg/proto/snapshot_types.go @@ -0,0 +1,212 @@ +package proto + +import ( + "math/big" + + "github.com/wavesplatform/gowaves/pkg/crypto" +) + +type AtomicSnapshot interface { + Apply(SnapshotApplier) error + /* TODO remove it. It is temporarily used to mark snapshots generated by tx diff that shouldn't be applied, + because balances diffs are applied later in the block. */ + IsGeneratedByTxDiff() bool +} + +type WavesBalanceSnapshot struct { + Address WavesAddress + Balance uint64 +} + +func (s WavesBalanceSnapshot) IsGeneratedByTxDiff() bool { + return true +} + +func (s WavesBalanceSnapshot) Apply(a SnapshotApplier) error { return a.ApplyWavesBalance(s) } + +type AssetBalanceSnapshot struct { + Address WavesAddress + AssetID crypto.Digest + Balance uint64 +} + +func (s AssetBalanceSnapshot) IsGeneratedByTxDiff() bool { + return true +} + +func (s AssetBalanceSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAssetBalance(s) } + +type DataEntriesSnapshot struct { // AccountData in pb + Address WavesAddress + DataEntries []DataEntry +} + +func (s DataEntriesSnapshot) IsGeneratedByTxDiff() bool { + return false +} + +func (s DataEntriesSnapshot) Apply(a SnapshotApplier) error { return a.ApplyDataEntries(s) } + +type AccountScriptSnapshot struct { + SenderPublicKey crypto.PublicKey + Script Script + VerifierComplexity uint64 +} + +func (s AccountScriptSnapshot) IsGeneratedByTxDiff() bool { + return false +} + +func (s AccountScriptSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAccountScript(s) } + +type AssetScriptSnapshot struct { + AssetID crypto.Digest + Script Script +} + +func (s AssetScriptSnapshot) IsGeneratedByTxDiff() bool { + return false +} + +func (s AssetScriptSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAssetScript(s) } + +type LeaseBalanceSnapshot struct { + Address WavesAddress + LeaseIn uint64 + LeaseOut uint64 +} + +func (s LeaseBalanceSnapshot) IsGeneratedByTxDiff() bool { + return true +} + +func (s LeaseBalanceSnapshot) Apply(a SnapshotApplier) error { return a.ApplyLeaseBalance(s) } + +type LeaseStateStatus interface{ leaseStateStatusMarker() } + +type LeaseStateStatusActive struct { + Amount uint64 + Sender WavesAddress + Recipient WavesAddress +} + +func (*LeaseStateStatusActive) leaseStateStatusMarker() {} + +type LeaseStatusCancelled struct{} + +func (*LeaseStatusCancelled) leaseStateStatusMarker() {} + +type LeaseStateSnapshot struct { + LeaseID crypto.Digest + Status LeaseStateStatus +} + +func (s LeaseStateSnapshot) IsGeneratedByTxDiff() bool { + return false +} + +func (s LeaseStateSnapshot) Apply(a SnapshotApplier) error { return a.ApplyLeaseState(s) } + +type SponsorshipSnapshot struct { + AssetID crypto.Digest + MinSponsoredFee uint64 +} + +func (s SponsorshipSnapshot) IsGeneratedByTxDiff() bool { + return false +} + +func (s SponsorshipSnapshot) Apply(a SnapshotApplier) error { return a.ApplySponsorship(s) } + +type AliasSnapshot struct { + Address WavesAddress + Alias Alias +} + +func (s AliasSnapshot) IsGeneratedByTxDiff() bool { + return false +} + +func (s AliasSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAlias(s) } + +// FilledVolumeFeeSnapshot Filled Volume and Fee. +type FilledVolumeFeeSnapshot struct { // OrderFill + OrderID crypto.Digest + FilledVolume uint64 + FilledFee uint64 +} + +func (s FilledVolumeFeeSnapshot) IsGeneratedByTxDiff() bool { + return false +} + +func (s FilledVolumeFeeSnapshot) Apply(a SnapshotApplier) error { return a.ApplyFilledVolumeAndFee(s) } + +type StaticAssetInfoSnapshot struct { + AssetID crypto.Digest + SourceTransactionID crypto.Digest + IssuerPublicKey crypto.PublicKey + Decimals uint8 + IsNFT bool +} + +func (s StaticAssetInfoSnapshot) IsGeneratedByTxDiff() bool { + return false +} + +func (s StaticAssetInfoSnapshot) Apply(a SnapshotApplier) error { return a.ApplyStaticAssetInfo(s) } + +type AssetVolumeSnapshot struct { // AssetVolume in pb + AssetID crypto.Digest + TotalQuantity big.Int // volume in protobuf + IsReissuable bool +} + +func (s AssetVolumeSnapshot) IsGeneratedByTxDiff() bool { + return false +} + +func (s AssetVolumeSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAssetVolume(s) } + +type AssetDescriptionSnapshot struct { // AssetNameAndDescription in pb + AssetID crypto.Digest + AssetName string + AssetDescription string + ChangeHeight Height // last_updated in pb +} + +func (s AssetDescriptionSnapshot) IsGeneratedByTxDiff() bool { + return false +} + +func (s AssetDescriptionSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAssetDescription(s) } + +type TransactionStatusSnapshot struct { + TransactionID crypto.Digest + Status TransactionStatus +} + +func (s TransactionStatusSnapshot) Apply(a SnapshotApplier) error { + return a.ApplyTransactionsStatus(s) +} + +func (s TransactionStatusSnapshot) IsGeneratedByTxDiff() bool { + return false +} + +type SnapshotApplier interface { + ApplyWavesBalance(snapshot WavesBalanceSnapshot) error + ApplyLeaseBalance(snapshot LeaseBalanceSnapshot) error + ApplyAssetBalance(snapshot AssetBalanceSnapshot) error + ApplyAlias(snapshot AliasSnapshot) error + ApplyStaticAssetInfo(snapshot StaticAssetInfoSnapshot) error + ApplyAssetDescription(snapshot AssetDescriptionSnapshot) error + ApplyAssetVolume(snapshot AssetVolumeSnapshot) error + ApplyAssetScript(snapshot AssetScriptSnapshot) error + ApplySponsorship(snapshot SponsorshipSnapshot) error + ApplyAccountScript(snapshot AccountScriptSnapshot) error + ApplyFilledVolumeAndFee(snapshot FilledVolumeFeeSnapshot) error + ApplyDataEntries(snapshot DataEntriesSnapshot) error + ApplyLeaseState(snapshot LeaseStateSnapshot) error + ApplyTransactionsStatus(snapshot TransactionStatusSnapshot) error +} diff --git a/pkg/proto/types.go b/pkg/proto/types.go index f1e2cb58e..665e1c20e 100644 --- a/pkg/proto/types.go +++ b/pkg/proto/types.go @@ -4287,3 +4287,11 @@ func (s StateHashDebug) GetStateHash() *StateHash { } return sh } + +type TransactionStatus byte + +const ( + TransactionSucceeded TransactionStatus = iota + TransactionFailed + TransactionElided +) diff --git a/pkg/state/appender.go b/pkg/state/appender.go index 6c8579286..108f98957 100644 --- a/pkg/state/appender.go +++ b/pkg/state/appender.go @@ -61,13 +61,15 @@ func newTxAppender( settings *settings.BlockchainSettings, stateDB *stateDB, atx *addressTransactions, + snapshotApplier *blockSnapshotsApplier, + snapshotGenerator *snapshotGenerator, ) (*txAppender, error) { sc, err := newScriptCaller(state, stor, settings) if err != nil { return nil, err } genesis := settings.Genesis - txHandler, err := newTransactionHandler(genesis.BlockID(), stor, settings) + txHandler, err := newTransactionHandler(genesis.BlockID(), stor, settings, snapshotGenerator, snapshotApplier) if err != nil { return nil, err } @@ -321,50 +323,63 @@ func (a *txAppender) saveTransactionIdByAddresses(addresses []proto.WavesAddress return nil } -func (a *txAppender) commitTxApplication(tx proto.Transaction, params *appendTxParams, res *applicationResult) error { +func (a *txAppender) commitTxApplication( + tx proto.Transaction, + params *appendTxParams, + invocationRes *invocationResult, + applicationRes *applicationResult) (txSnapshot, error) { // Add transaction ID to recent IDs. txID, err := tx.GetID(a.settings.AddressSchemeCharacter) if err != nil { - return wrapErr(TxCommitmentError, errors.Errorf("failed to get tx id: %v", err)) + return txSnapshot{}, wrapErr(TxCommitmentError, errors.Errorf("failed to get tx id: %v", err)) } a.recentTxIds[string(txID)] = empty // Update script runs. - a.totalScriptsRuns += res.totalScriptsRuns + a.totalScriptsRuns += applicationRes.totalScriptsRuns // Update complexity. a.sc.addRecentTxComplexity() // Save balance diff. - if err := a.diffStor.saveTxDiff(res.changes.diff); err != nil { - return wrapErr(TxCommitmentError, errors.Errorf("failed to save balance diff: %v", err)) + if err = a.diffStor.saveTxDiff(applicationRes.changes.diff); err != nil { + return txSnapshot{}, wrapErr(TxCommitmentError, errors.Errorf("failed to save balance diff: %v", err)) } - // Perform state changes. - if res.status { + currentMinerAddress := proto.MustAddressFromPublicKey(a.settings.AddressSchemeCharacter, params.currentMinerPK) + + var snapshot txSnapshot + if applicationRes.status { // We only perform tx in case it has not failed. - performerInfo := newPerformerInfo( - params.checkerInfo.height, - params.stateActionsCounterInBlock, - params.checkerInfo.blockID, - res.checkerData, - ) - if err := a.txHandler.performTx(tx, performerInfo); err != nil { - return wrapErr(TxCommitmentError, errors.Errorf("failed to perform: %v", err)) + performerInfo := &performerInfo{ + height: params.checkerInfo.height, + blockID: params.checkerInfo.blockID, + currentMinerAddress: currentMinerAddress, + checkerData: applicationRes.checkerData, + stateActionsCounter: params.stateActionsCounterInBlock, + } + snapshot, err = a.txHandler.performTx(tx, performerInfo, invocationRes, applicationRes.changes.diff) + if err != nil { + return txSnapshot{}, wrapErr(TxCommitmentError, errors.Errorf("failed to perform: %v", err)) } } if params.validatingUtx { // Save transaction to in-mem storage. - if err := a.rw.writeTransactionToMem(tx, !res.status); err != nil { - return wrapErr(TxCommitmentError, errors.Errorf("failed to write transaction to in mem stor: %v", err)) + if err = a.rw.writeTransactionToMem(tx, !applicationRes.status); err != nil { + return txSnapshot{}, wrapErr(TxCommitmentError, + errors.Errorf("failed to write transaction to in mem stor: %v", err), + ) } } else { // Count tx fee. if err := a.blockDiffer.countMinerFee(tx); err != nil { - return wrapErr(TxCommitmentError, errors.Errorf("failed to count miner fee: %v", err)) + return txSnapshot{}, wrapErr(TxCommitmentError, errors.Errorf("failed to count miner fee: %v", err)) } // Save transaction to storage. - if err := a.rw.writeTransaction(tx, !res.status); err != nil { - return wrapErr(TxCommitmentError, errors.Errorf("failed to write transaction to storage: %v", err)) + if err = a.rw.writeTransaction(tx, !applicationRes.status); err != nil { + return txSnapshot{}, wrapErr(TxCommitmentError, + errors.Errorf("failed to write transaction to storage: %v", err), + ) } } - return nil + // TODO: transaction status snapshot has to be appended here + return snapshot, nil } func (a *txAppender) verifyWavesTxSigAndData(tx proto.Transaction, params *appendTxParams, accountHasVerifierScript bool) error { @@ -408,18 +423,21 @@ type appendTxParams struct { invokeExpressionActivated bool // TODO: check feature naming validatingUtx bool // if validatingUtx == false then chans MUST be initialized with non nil value stateActionsCounterInBlock *proto.StateActionsCounter + currentMinerPK crypto.PublicKey } -func (a *txAppender) handleInvokeOrExchangeTransaction(tx proto.Transaction, fallibleInfo *fallibleValidationParams) (*applicationResult, error) { - applicationRes, err := a.handleFallible(tx, fallibleInfo) +func (a *txAppender) handleInvokeOrExchangeTransaction( + tx proto.Transaction, + fallibleInfo *fallibleValidationParams) (*invocationResult, *applicationResult, error) { + invocationRes, applicationRes, err := a.handleFallible(tx, fallibleInfo) if err != nil { msg := "fallible validation failed" if txID, err2 := tx.GetID(a.settings.AddressSchemeCharacter); err2 == nil { msg = fmt.Sprintf("fallible validation failed for transaction '%s'", base58.Encode(txID)) } - return nil, errs.Extend(err, msg) + return nil, nil, errs.Extend(err, msg) } - return applicationRes, nil + return invocationRes, applicationRes, nil } func (a *txAppender) handleDefaultTransaction(tx proto.Transaction, params *appendTxParams, accountHasVerifierScript bool) (*applicationResult, error) { @@ -473,13 +491,14 @@ func (a *txAppender) appendTx(tx proto.Transaction, params *appendTxParams) erro // Check tx against state, check tx scripts, calculate balance changes. var applicationRes *applicationResult + var invocationResult *invocationResult needToValidateBalanceDiff := false switch tx.GetTypeInfo().Type { case proto.InvokeScriptTransaction, proto.InvokeExpressionTransaction, proto.ExchangeTransaction: // Invoke and Exchange transactions should be handled differently. // They may fail, and will be saved to blockchain anyway. fallibleInfo := &fallibleValidationParams{appendTxParams: params, senderScripted: accountHasVerifierScript, senderAddress: senderAddr} - applicationRes, err = a.handleInvokeOrExchangeTransaction(tx, fallibleInfo) + invocationResult, applicationRes, err = a.handleInvokeOrExchangeTransaction(tx, fallibleInfo) if err != nil { return errors.Wrap(err, "failed to handle invoke or exchange transaction") } @@ -512,7 +531,7 @@ func (a *txAppender) appendTx(tx proto.Transaction, params *appendTxParams) erro senderScripted: accountHasVerifierScript, senderAddress: senderAddr, } - applicationRes, err = a.handleInvokeOrExchangeTransaction(tx, fallibleInfo) + invocationResult, applicationRes, err = a.handleInvokeOrExchangeTransaction(tx, fallibleInfo) if err != nil { return errors.Wrapf(err, "failed to handle ethereum invoke script transaction (type %s) with id %s, on height %d", ethTx.TxKind.String(), ethTx.ID.String(), params.checkerInfo.height+1) @@ -545,7 +564,10 @@ func (a *txAppender) appendTx(tx proto.Transaction, params *appendTxParams) erro if err != nil { return errs.Extend(err, "get transaction id") } - if err := a.commitTxApplication(tx, params, applicationRes); err != nil { + + // invocationResult may be empty if it was not an Invoke Transaction + _, err = a.commitTxApplication(tx, params, invocationResult, applicationRes) + if err != nil { zap.S().Errorf("failed to commit transaction (id %s) after successful validation; this should NEVER happen", base58.Encode(txID)) return err } @@ -558,6 +580,29 @@ func (a *txAppender) appendTx(tx proto.Transaction, params *appendTxParams) erro return nil } +// rewards and 60% of the fee to the previous miner. +func (a *txAppender) createInitialBlockSnapshot(minerAndRewardDiff txDiff) (txSnapshot, error) { + addrWavesBalanceDiff, _, err := balanceDiffFromTxDiff(minerAndRewardDiff, a.settings.AddressSchemeCharacter) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to create balance diff from tx diff") + } + // add miner address to the diff + var snapshot txSnapshot + for wavesAddress, diffAmount := range addrWavesBalanceDiff { + var fullBalance balanceProfile + fullBalance, err = a.stor.balances.newestWavesBalance(wavesAddress.ID()) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to receive sender's waves balance") + } + newBalance := &proto.WavesBalanceSnapshot{ + Address: wavesAddress, + Balance: uint64(int64(fullBalance.balance) + diffAmount.balance), + } + snapshot.regular = append(snapshot.regular, newBalance) + } + return snapshot, nil +} + func (a *txAppender) appendBlock(params *appendBlockParams) error { // Reset block complexity counter. defer func() { @@ -589,16 +634,29 @@ func (a *txAppender) appendBlock(params *appendBlockParams) error { if hasParent { checkerInfo.parentTimestamp = params.parent.Timestamp } + stateActionsCounterInBlockValidation := new(proto.StateActionsCounter) + snapshotApplierInfo := newBlockSnapshotsApplierInfo(checkerInfo, a.settings.AddressSchemeCharacter, + stateActionsCounterInBlockValidation) + a.txHandler.tp.snapshotApplier.SetApplierInfo(snapshotApplierInfo) // Create miner balance diff. // This adds 60% of prev block fees as very first balance diff of the current block // in case NG is activated, or empty diff otherwise. - minerDiff, err := a.blockDiffer.createMinerDiff(params.block, hasParent) + minerAndRewardDiff, err := a.blockDiffer.createMinerAndRewardDiff(params.block, hasParent) if err != nil { return err } - // Save miner diff first. - if err := a.diffStor.saveTxDiff(minerDiff); err != nil { + // create the initial snapshot + _, err = a.createInitialBlockSnapshot(minerAndRewardDiff) + if err != nil { + return errors.Wrap(err, "failed to create initial snapshot") + } + + // TODO apply this snapshot when balances are refatored + // err = initialSnapshot.Apply(&snapshotApplier) + + // Save miner diff first (for validation) + if err = a.diffStor.saveTxDiff(minerAndRewardDiff); err != nil { return err } blockInfo, err := a.currentBlockInfo() @@ -621,8 +679,8 @@ func (a *txAppender) appendBlock(params *appendBlockParams) error { if err != nil { return err } - stateActionsCounterInBlock := new(proto.StateActionsCounter) // Check and append transactions. + for _, tx := range params.transactions { appendTxArgs := &appendTxParams{ chans: params.chans, @@ -637,20 +695,22 @@ func (a *txAppender) appendBlock(params *appendBlockParams) error { blockRewardDistributionActivated: blockRewardDistributionActivated, invokeExpressionActivated: invokeExpressionActivated, validatingUtx: false, - stateActionsCounterInBlock: stateActionsCounterInBlock, + stateActionsCounterInBlock: stateActionsCounterInBlockValidation, + currentMinerPK: params.block.GeneratorPublicKey, } if err := a.appendTx(tx, appendTxArgs); err != nil { return err } } // Save fee distribution of this block. - // This will be needed for createMinerDiff() of next block due to NG. + // This will be needed for createMinerAndRewardDiff() of next block due to NG. if err := a.blockDiffer.saveCurFeeDistr(params.block); err != nil { return err } return nil } +// used only in tests now. All diffs are applied in snapshotApplier. func (a *txAppender) applyAllDiffs() error { a.recentTxIds = make(map[string]struct{}) return a.moveChangesToHistoryStorage() @@ -679,7 +739,9 @@ func newApplicationResult(status bool, totalScriptsRuns uint64, changes txBalanc return &applicationResult{status, totalScriptsRuns, changes, checkerData} // all fields must be initialized } -func (a *txAppender) handleInvoke(tx proto.Transaction, info *fallibleValidationParams) (*applicationResult, error) { +func (a *txAppender) handleInvoke( + tx proto.Transaction, + info *fallibleValidationParams) (*invocationResult, *applicationResult, error) { var ID crypto.Digest switch t := tx.(type) { case *proto.InvokeScriptWithProofs: @@ -691,17 +753,17 @@ func (a *txAppender) handleInvoke(tx proto.Transaction, info *fallibleValidation case *proto.EthereumInvokeScriptTxKind: ID = *t.ID default: - return nil, errors.Errorf("unexpected ethereum tx kind (%T)", tx) + return nil, nil, errors.Errorf("unexpected ethereum tx kind (%T)", tx) } default: - return nil, errors.Errorf("failed to handle invoke: wrong type of transaction (%T)", tx) + return nil, nil, errors.Errorf("failed to handle invoke: wrong type of transaction (%T)", tx) } - res, err := a.ia.applyInvokeScript(tx, info) + invocationRes, applicationRes, err := a.ia.applyInvokeScript(tx, info) if err != nil { zap.S().Debugf("failed to apply InvokeScript transaction %s to state: %v", ID.String(), err) - return nil, err + return nil, nil, err } - return res, nil + return invocationRes, applicationRes, nil } func (a *txAppender) countExchangeScriptsRuns(scriptsRuns uint64) (uint64, error) { @@ -810,19 +872,22 @@ func (a *txAppender) handleExchange(tx proto.Transaction, info *fallibleValidati return newApplicationResult(true, scriptsRuns, successfulChanges, checkerData), nil } -func (a *txAppender) handleFallible(tx proto.Transaction, info *fallibleValidationParams) (*applicationResult, error) { +func (a *txAppender) handleFallible( + tx proto.Transaction, + info *fallibleValidationParams) (*invocationResult, *applicationResult, error) { if info.acceptFailed { if err := a.checkTxFees(tx, info); err != nil { - return nil, err + return nil, nil, err } } switch tx.GetTypeInfo().Type { case proto.InvokeScriptTransaction, proto.InvokeExpressionTransaction, proto.EthereumMetamaskTransaction: return a.handleInvoke(tx, info) case proto.ExchangeTransaction: - return a.handleExchange(tx, info) + applicationRes, err := a.handleExchange(tx, info) + return nil, applicationRes, err } - return nil, errors.New("transaction is not fallible") + return nil, nil, errors.New("transaction is not fallible") } // For UTX validation. @@ -876,6 +941,11 @@ func (a *txAppender) validateNextTx(tx proto.Transaction, currentTimestamp, pare if err != nil { return errs.Extend(err, "failed to check 'InvokeExpression' is activated") // TODO: check feature naming in err message } + issueCounterInBlock := new(proto.StateActionsCounter) + snapshotApplierInfo := newBlockSnapshotsApplierInfo(checkerInfo, a.settings.AddressSchemeCharacter, + issueCounterInBlock) + a.txHandler.tp.snapshotApplier.SetApplierInfo(snapshotApplierInfo) + appendTxArgs := &appendTxParams{ chans: nil, // nil because validatingUtx == true checkerInfo: checkerInfo, @@ -890,7 +960,7 @@ func (a *txAppender) validateNextTx(tx proto.Transaction, currentTimestamp, pare invokeExpressionActivated: invokeExpressionActivated, validatingUtx: true, // it's correct to use new counter because there's no block exists, but this field is necessary in tx performer - stateActionsCounterInBlock: new(proto.StateActionsCounter), + stateActionsCounterInBlock: issueCounterInBlock, } err = a.appendTx(tx, appendTxArgs) if err != nil { diff --git a/pkg/state/balances.go b/pkg/state/balances.go index 2454ae76d..08b3bc85b 100644 --- a/pkg/state/balances.go +++ b/pkg/state/balances.go @@ -268,7 +268,7 @@ func (s *balances) cancelAllLeases(blockID proto.BlockID) error { zap.S().Infof("Resetting lease balance for %s", addr.String()) r.leaseOut = 0 r.leaseIn = 0 - val := &wavesValue{leaseChange: true, profile: r.balanceProfile} + val := wavesValue{leaseChange: true, profile: r.balanceProfile} if err := s.setWavesBalance(k.address, val, blockID); err != nil { return err } @@ -310,7 +310,7 @@ func (s *balances) cancelLeaseOverflows(blockID proto.BlockID) (map[proto.WavesA ) overflowedAddresses[wavesAddr] = empty r.leaseOut = 0 - val := &wavesValue{leaseChange: true, profile: r.balanceProfile} + val := wavesValue{leaseChange: true, profile: r.balanceProfile} if err := s.setWavesBalance(k.address, val, blockID); err != nil { return nil, err } @@ -356,7 +356,7 @@ func (s *balances) cancelInvalidLeaseIns(correctLeaseIns map[proto.WavesAddress] wavesAddress.String(), r.leaseIn, correctLeaseIn, ) r.leaseIn = correctLeaseIn - val := &wavesValue{leaseChange: true, profile: r.balanceProfile} + val := wavesValue{leaseChange: true, profile: r.balanceProfile} if err := s.setWavesBalance(k.address, val, blockID); err != nil { return err } @@ -375,11 +375,11 @@ func (s *balances) cancelLeases(changes map[proto.WavesAddress]balanceDiff, bloc return err } profile := r.balanceProfile - newProfile, err := bd.applyTo(&profile) + newProfile, err := bd.applyTo(profile) if err != nil { return err } - val := &wavesValue{leaseChange: true, profile: *newProfile} + val := wavesValue{leaseChange: true, profile: newProfile} if err := s.setWavesBalance(a.ID(), val, blockID); err != nil { return err } @@ -545,59 +545,62 @@ func (s *balances) newestAssetBalance(addr proto.AddressID, asset proto.AssetID) return s.assetBalanceFromRecordBytes(recordBytes) } -func (s *balances) newestWavesRecord(key []byte) (*wavesBalanceRecord, error) { +func (s *balances) newestWavesRecord(key []byte) (wavesBalanceRecord, error) { recordBytes, err := s.hs.newestTopEntryData(key) if err == keyvalue.ErrNotFound || err == errEmptyHist { // Unknown address, expected behavior is to return empty profile and no errors in this case. - return &wavesBalanceRecord{}, nil + return wavesBalanceRecord{}, nil } else if err != nil { - return nil, err + return wavesBalanceRecord{}, err } var record wavesBalanceRecord if err := record.unmarshalBinary(recordBytes); err != nil { - return nil, err + return wavesBalanceRecord{}, err } - return &record, nil + return record, nil } -func (s *balances) newestWavesBalance(addr proto.AddressID) (*balanceProfile, error) { +// newestWavesBalance returns newest waves balanceProfile. +func (s *balances) newestWavesBalance(addr proto.AddressID) (balanceProfile, error) { key := wavesBalanceKey{address: addr} r, err := s.newestWavesRecord(key.bytes()) if err != nil { - return nil, err + return balanceProfile{}, err } - return &r.balanceProfile, nil + return r.balanceProfile, nil } -func (s *balances) wavesRecord(key []byte) (*wavesBalanceRecord, error) { +func (s *balances) wavesRecord(key []byte) (wavesBalanceRecord, error) { recordBytes, err := s.hs.topEntryData(key) if err == keyvalue.ErrNotFound || err == errEmptyHist { // Unknown address, expected behavior is to return empty profile and no errors in this case. - return &wavesBalanceRecord{}, nil + return wavesBalanceRecord{}, nil } else if err != nil { - return nil, err + return wavesBalanceRecord{}, err } var record wavesBalanceRecord if err := record.unmarshalBinary(recordBytes); err != nil { - return nil, err + return wavesBalanceRecord{}, errors.Wrap(err, "failed to unmarshal data to %T") } - return &record, nil + return record, nil } -func (s *balances) wavesBalance(addr proto.AddressID) (*balanceProfile, error) { +// wavesBalance returns stored waves balanceProfile. +// IMPORTANT NOTE: this method returns saved on disk data, for the newest data use newestWavesBalance. +func (s *balances) wavesBalance(addr proto.AddressID) (balanceProfile, error) { key := wavesBalanceKey{address: addr} r, err := s.wavesRecord(key.bytes()) if err != nil { - return nil, err + return balanceProfile{}, err } - return &r.balanceProfile, nil + return r.balanceProfile, nil } func (s *balances) setAssetBalance(addr proto.AddressID, assetID proto.AssetID, balance uint64, blockID proto.BlockID) error { key := assetBalanceKey{address: addr, asset: assetID} keyBytes := key.bytes() keyStr := string(keyBytes) - record := &assetBalanceRecord{balance} + record := assetBalanceRecord{balance} recordBytes, err := record.marshalBinary() if err != nil { return err @@ -625,11 +628,11 @@ func (s *balances) setAssetBalance(addr proto.AddressID, assetID proto.AssetID, return s.hs.addNewEntry(assetBalance, keyBytes, recordBytes, blockID) } -func (s *balances) setWavesBalance(addr proto.AddressID, balance *wavesValue, blockID proto.BlockID) error { +func (s *balances) setWavesBalance(addr proto.AddressID, balance wavesValue, blockID proto.BlockID) error { key := wavesBalanceKey{address: addr} keyBytes := key.bytes() keyStr := string(keyBytes) - record := &wavesBalanceRecord{balance.profile} + record := wavesBalanceRecord{balance.profile} recordBytes, err := record.marshalBinary() if err != nil { return err diff --git a/pkg/state/balances_test.go b/pkg/state/balances_test.go index 444abd9fd..182d94636 100644 --- a/pkg/state/balances_test.go +++ b/pkg/state/balances_test.go @@ -38,6 +38,17 @@ func genAsset(fillWith byte) crypto.Digest { return asset } +func newWavesValueFromProfile(p balanceProfile) wavesValue { + val := wavesValue{profile: p} + if p.leaseIn != 0 || p.leaseOut != 0 { + val.leaseChange = true + } + if p.balance != 0 { + val.balanceChange = true + } + return val +} + func TestCancelAllLeases(t *testing.T) { to := createBalances(t) @@ -219,7 +230,7 @@ func TestBalances(t *testing.T) { if err != nil { t.Fatalf("Failed to retrieve waves balance: %v\n", err) } - if *profile != tc.profile { + if profile != tc.profile { t.Errorf("Waves balance profiles are not equal: %v and %v\n", profile, tc.profile) } } diff --git a/pkg/state/block_differ.go b/pkg/state/block_differ.go index a1159900d..6b57c2bf1 100644 --- a/pkg/state/block_differ.go +++ b/pkg/state/block_differ.go @@ -170,7 +170,7 @@ func (d *blockDiffer) saveCurFeeDistr(block *proto.BlockHeader) error { return nil } -func (d *blockDiffer) createMinerDiff(block *proto.BlockHeader, hasParent bool) (txDiff, error) { +func (d *blockDiffer) createMinerAndRewardDiff(block *proto.BlockHeader, hasParent bool) (txDiff, error) { var err error var minerDiff txDiff var minerAddr proto.WavesAddress diff --git a/pkg/state/block_differ_test.go b/pkg/state/block_differ_test.go index f1b89b3ae..166261d32 100644 --- a/pkg/state/block_differ_test.go +++ b/pkg/state/block_differ_test.go @@ -24,7 +24,7 @@ func createBlockDiffer(t *testing.T) *blockDifferTestObjects { func createBlockDifferWithSettings(t *testing.T, sets *settings.BlockchainSettings) *blockDifferTestObjects { stor := createStorageObjectsWithOptions(t, testStorageObjectsOptions{Amend: false, Settings: sets}) - handler, err := newTransactionHandler(sets.Genesis.BlockID(), stor.entities, sets) + handler, err := newTransactionHandler(sets.Genesis.BlockID(), stor.entities, sets, nil, nil) require.NoError(t, err, "newTransactionHandler() failed") blockDiffer, err := newBlockDiffer(handler, stor.entities, sets) require.NoError(t, err, "newBlockDiffer() failed") @@ -56,8 +56,8 @@ func TestCreateBlockDiffWithoutNg(t *testing.T) { to := createBlockDiffer(t) block, _ := genBlocks(t, to) - minerDiff, err := to.blockDiffer.createMinerDiff(&block.BlockHeader, true) - require.NoError(t, err, "createMinerDiff() failed") + minerDiff, err := to.blockDiffer.createMinerAndRewardDiff(&block.BlockHeader, true) + require.NoError(t, err, "createMinerAndRewardDiff() failed") // Empty miner diff before NG activation. assert.Equal(t, txDiff{}, minerDiff) } @@ -84,8 +84,8 @@ func TestCreateBlockDiffNg(t *testing.T) { parentFeeNextBlock := parentFeeTotal - parentFeePrevBlock // Create diff from child block. - minerDiff, err := to.blockDiffer.createMinerDiff(&child.BlockHeader, true) - require.NoError(t, err, "createMinerDiff() failed") + minerDiff, err := to.blockDiffer.createMinerAndRewardDiff(&child.BlockHeader, true) + require.NoError(t, err, "createMinerAndRewardDiff() failed") // Verify child block miner's diff. correctMinerAssetBalanceDiff := newBalanceDiff(parentFeeNextBlock, 0, 0, false) correctMinerAssetBalanceDiff.blockID = child.BlockID() @@ -122,15 +122,15 @@ func TestCreateBlockDiffSponsorship(t *testing.T) { } err = to.blockDiffer.saveCurFeeDistr(&parent.BlockHeader) require.NoError(t, err, "saveCurFeeDistr() failed") - _, err = to.blockDiffer.createMinerDiff(&parent.BlockHeader, false) - require.NoError(t, err, "createMinerDiff() failed") + _, err = to.blockDiffer.createMinerAndRewardDiff(&parent.BlockHeader, false) + require.NoError(t, err, "createMinerAndRewardDiff() failed") parentFeeTotal := int64(txs[0].GetFee() * FeeUnit / assetCost) parentFeePrevBlock := parentFeeTotal / 5 * 2 parentFeeNextBlock := parentFeeTotal - parentFeePrevBlock // Create diff from child block. - minerDiff, err := to.blockDiffer.createMinerDiff(&child.BlockHeader, true) - require.NoError(t, err, "createMinerDiff() failed") + minerDiff, err := to.blockDiffer.createMinerAndRewardDiff(&child.BlockHeader, true) + require.NoError(t, err, "createMinerAndRewardDiff() failed") // Verify child block miner's diff. correctMinerWavesBalanceDiff := newBalanceDiff(parentFeeNextBlock, 0, 0, false) correctMinerWavesBalanceDiff.blockID = child.BlockID() @@ -185,7 +185,7 @@ func TestCreateBlockDiffWithReward(t *testing.T) { // Second block block2 := genBlockWithSingleTransaction(t, block1.BlockID(), block1.GenSignature, to) to.stor.addBlock(t, block2.BlockID()) - minerDiff, err := to.blockDiffer.createMinerDiff(&block2.BlockHeader, true) + minerDiff, err := to.blockDiffer.createMinerAndRewardDiff(&block2.BlockHeader, true) require.NoError(t, err) fee := defaultFee - defaultFee/5*2 @@ -224,7 +224,7 @@ func TestBlockRewardDistributionWithTwoAddresses(t *testing.T) { // Second block block2 := genBlockWithSingleTransaction(t, block1.BlockID(), block1.GenSignature, to) to.stor.addBlock(t, block2.BlockID()) - minerDiff, err := to.blockDiffer.createMinerDiff(&block2.BlockHeader, true) + minerDiff, err := to.blockDiffer.createMinerAndRewardDiff(&block2.BlockHeader, true) require.NoError(t, err) fee := int64(defaultFee - defaultFee/5*2) @@ -272,7 +272,7 @@ func TestBlockRewardDistributionWithOneAddress(t *testing.T) { // Second block block2 := genBlockWithSingleTransaction(t, block1.BlockID(), block1.GenSignature, to) to.stor.addBlock(t, block2.BlockID()) - minerDiff, err := to.blockDiffer.createMinerDiff(&block2.BlockHeader, true) + minerDiff, err := to.blockDiffer.createMinerAndRewardDiff(&block2.BlockHeader, true) require.NoError(t, err) fee := defaultFee - defaultFee/5*2 diff --git a/pkg/state/common_test.go b/pkg/state/common_test.go index f40cc16c1..73f190596 100644 --- a/pkg/state/common_test.go +++ b/pkg/state/common_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/mr-tron/base58/base58" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -490,11 +491,32 @@ func (s *testStorageObjects) createAsset(t *testing.T, assetID crypto.Digest) *a func (s *testStorageObjects) createSmartAsset(t *testing.T, assetID crypto.Digest) { s.addBlock(t, blockID0) - err := s.entities.scriptsStorage.setAssetScript(assetID, testGlobal.scriptBytes, testGlobal.senderInfo.pk, blockID0) + err := s.entities.scriptsStorage.setAssetScript(assetID, testGlobal.scriptBytes, blockID0) assert.NoError(t, err, "setAssetScript failed") s.flush(t) } +func storeScriptByAddress( + stor *blockchainEntitiesStorage, + scheme proto.Scheme, + senderPK crypto.PublicKey, + script proto.Script, + se scriptEstimation, + blockID proto.BlockID, +) error { + senderAddr, err := proto.NewAddressFromPublicKey(scheme, senderPK) + if err != nil { + return errors.Wrapf(err, "failed to create addr from PK %q", senderPK.String()) + } + if setErr := stor.scriptsStorage.setAccountScript(senderAddr, script, senderPK, blockID); setErr != nil { + return errors.Wrapf(setErr, "failed to set account script on addr %q", senderAddr.String()) + } + if setErr := stor.scriptsComplexity.saveComplexitiesForAddr(senderAddr, se, blockID); setErr != nil { + return errors.Wrapf(setErr, "failed to save script complexities for addr %q", senderAddr.String()) + } + return nil +} + func (s *testStorageObjects) setScript(t *testing.T, pk crypto.PublicKey, script proto.Script, blockID proto.BlockID) { var est ride.TreeEstimation if !script.IsEmpty() { diff --git a/pkg/state/constants.go b/pkg/state/constants.go index 11b4dcade..b3d50467d 100644 --- a/pkg/state/constants.go +++ b/pkg/state/constants.go @@ -25,7 +25,7 @@ const ( // StateVersion is current version of state internal storage formats. // It increases when backward compatibility with previous storage version is lost. - StateVersion = 17 + StateVersion = 18 // Memory limit for address transactions. flush() is called when this // limit is exceeded. diff --git a/pkg/state/diff_applier.go b/pkg/state/diff_applier.go index 1c03501cf..eb9c14e72 100644 --- a/pkg/state/diff_applier.go +++ b/pkg/state/diff_applier.go @@ -20,19 +20,8 @@ func newDiffApplier(balances *balances, scheme proto.Scheme) (*diffApplier, erro return &diffApplier{balances, scheme}, nil } -func newWavesValueFromProfile(p balanceProfile) *wavesValue { - val := &wavesValue{profile: p} - if p.leaseIn != 0 || p.leaseOut != 0 { - val.leaseChange = true - } - if p.balance != 0 { - val.balanceChange = true - } - return val -} - -func newWavesValue(prevProf, newProf balanceProfile) *wavesValue { - val := &wavesValue{profile: newProf} +func newWavesValue(prevProf, newProf balanceProfile) wavesValue { + val := wavesValue{profile: newProf} if prevProf.balance != newProf.balance { val.balanceChange = true } @@ -51,7 +40,7 @@ func (a *diffApplier) applyWavesBalanceChanges(change *balanceChanges, validateO if err != nil { return errors.Errorf("failed to retrieve waves balance: %v\n", err) } - prevProfile := *profile + prevProfile := profile for _, diff := range change.balanceDiffs { // Check for negative balance. newProfile, err := diff.applyTo(profile) @@ -69,11 +58,11 @@ func (a *diffApplier) applyWavesBalanceChanges(change *balanceChanges, validateO if validateOnly { continue } - val := newWavesValue(prevProfile, *newProfile) + val := newWavesValue(prevProfile, newProfile) if err := a.balances.setWavesBalance(k.address, val, diff.blockID); err != nil { return errors.Errorf("failed to set account balance: %v\n", err) } - prevProfile = *newProfile + prevProfile = newProfile } return nil } diff --git a/pkg/state/ethereum_tx_test.go b/pkg/state/ethereum_tx_test.go index 8c7bc35f0..543ebc01b 100644 --- a/pkg/state/ethereum_tx_test.go +++ b/pkg/state/ethereum_tx_test.go @@ -19,7 +19,10 @@ import ( "github.com/wavesplatform/gowaves/pkg/types" ) -func defaultTxAppender(t *testing.T, storage scriptStorageState, state types.SmartState, assetsUncertain map[proto.AssetID]assetInfo, scheme proto.Scheme) txAppender { +func defaultTxAppender(t *testing.T, storage scriptStorageState, state types.SmartState, + assetsUncertain map[proto.AssetID]assetInfo, + params *appendTxParams) txAppender { + scheme := proto.TestNetScheme activatedFeatures := map[settings.Feature]struct{}{ settings.SmartAccounts: {}, settings.FeeSponsorship: {}, @@ -69,7 +72,18 @@ func defaultTxAppender(t *testing.T, storage scriptStorageState, state types.Sma AddressSchemeCharacter: scheme, }, } - txHandler, err := newTransactionHandler(genBlockId('1'), &store, blockchainSettings) + + snapshotApplier := newBlockSnapshotsApplier( + newBlockSnapshotsApplierInfo( + params.checkerInfo, + blockchainSettings.AddressSchemeCharacter, + params.stateActionsCounterInBlock, + ), + newSnapshotApplierStorages(stor.entities), + ) + snapshotGen := newSnapshotGenerator(stor.entities, settings.MainNetSettings.AddressSchemeCharacter) + + txHandler, err := newTransactionHandler(genBlockId('1'), &store, blockchainSettings, &snapshotGen, &snapshotApplier) assert.NoError(t, err) blockchainEntitiesStor := blockchainEntitiesStorage{scriptsStorage: storage} txAppender := txAppender{ @@ -112,7 +126,7 @@ func TestEthereumTransferWaves(t *testing.T) { }, } //assetsUncertain := newAssets - txAppender := defaultTxAppender(t, storage, nil, nil, proto.TestNetScheme) + txAppend := defaultTxAppender(t, storage, nil, nil, appendTxParams) senderPK, err := proto.NewEthereumPublicKeyFromHexString("c4f926702fee2456ac5f3d91c9b7aa578ff191d0792fa80b6e65200f2485d9810a89c1bb5830e6618119fb3f2036db47fac027f7883108cbc7b2953539b9cb53") assert.NoError(t, err) recipientBytes, err := base58.Decode("a783d1CBABe28d25E64aDf84477C4687c1411f94") // 0x241Cf7eaf669E0d2FDe4Ba3a534c20B433F4c43d @@ -121,10 +135,10 @@ func TestEthereumTransferWaves(t *testing.T) { txData := defaultEthereumLegacyTxData(1000000000000000, &recipientEth, nil, 100000, proto.TestNetScheme) tx := proto.NewEthereumTransaction(txData, nil, nil, &senderPK, 0) - tx.TxKind, err = txAppender.ethTxKindResolver.ResolveTxKind(&tx, true) + tx.TxKind, err = txAppend.ethTxKindResolver.ResolveTxKind(&tx, true) assert.NoError(t, err) - applRes, err := txAppender.handleDefaultTransaction(&tx, appendTxParams, false) + applRes, err := txAppend.handleDefaultTransaction(&tx, appendTxParams, false) assert.NoError(t, err) assert.True(t, applRes.status) @@ -165,7 +179,8 @@ func TestEthereumTransferAssets(t *testing.T) { assetsUncertain := map[proto.AssetID]assetInfo{ proto.AssetID(recipientEth): {}, } - txAppender := defaultTxAppender(t, storage, &AnotherMockSmartState{}, assetsUncertain, proto.TestNetScheme) + txAppend := defaultTxAppender(t, storage, &AnotherMockSmartState{}, + assetsUncertain, appendTxParams) /* from https://etherscan.io/tx/0x363f979b58c82614db71229c2a57ed760e7bc454ee29c2f8fd1df99028667ea5 transfer(address,uint256) @@ -190,11 +205,11 @@ func TestEthereumTransferAssets(t *testing.T) { assetID := (*proto.AssetID)(tx.To()) - assetInfo, err := txAppender.stor.assets.newestAssetInfo(*assetID) + assetInfo, err := txAppend.stor.assets.newestAssetInfo(*assetID) require.NoError(t, err) fullAssetID := proto.ReconstructDigest(*assetID, assetInfo.tail) tx.TxKind = proto.NewEthereumTransferAssetsErc20TxKind(*decodedData, *proto.NewOptionalAssetFromDigest(fullAssetID), erc20arguments) - applRes, err := txAppender.handleDefaultTransaction(&tx, appendTxParams, false) + applRes, err := txAppend.handleDefaultTransaction(&tx, appendTxParams, false) assert.NoError(t, err) assert.True(t, applRes.status) @@ -287,7 +302,7 @@ func TestEthereumInvoke(t *testing.T) { assetsUncertain := map[proto.AssetID]assetInfo{ proto.AssetID(recipientEth): {}, } - txAppender := defaultTxAppender(t, storage, state, assetsUncertain, proto.TestNetScheme) + txAppend := defaultTxAppender(t, storage, state, assetsUncertain, appendTxParams) txData := defaultEthereumLegacyTxData(1000000000000000, &recipientEth, nil, 500000, proto.TestNetScheme) decodedData := defaultDecodedData("call", []ethabi.DecodedArg{{Value: ethabi.Int(10)}}, []ethabi.Payment{{Amount: 5, AssetID: proto.NewOptionalAssetWaves().ID}}) @@ -297,11 +312,11 @@ func TestEthereumInvoke(t *testing.T) { fallibleInfo := &fallibleValidationParams{appendTxParams: appendTxParams, senderScripted: false, senderAddress: sender} scriptAddress, tree := applyScript(t, &tx, storage) fallibleInfo.rideV5Activated = true - res, err := txAppender.ia.sc.invokeFunction(tree, nil, &tx, fallibleInfo, scriptAddress) + res, err := txAppend.ia.sc.invokeFunction(tree, nil, &tx, fallibleInfo, scriptAddress) assert.NoError(t, err) assert.True(t, res.Result()) - _, err = txAppender.ia.txHandler.checkTx(&tx, fallibleInfo.checkerInfo) + _, err = txAppend.ia.txHandler.checkTx(&tx, fallibleInfo.checkerInfo) assert.NoError(t, err) expectedDataEntryWrites := []*proto.DataEntryScriptAction{ {Entry: &proto.IntegerDataEntry{Key: "int", Value: 10}}, @@ -312,13 +327,13 @@ func TestEthereumInvoke(t *testing.T) { txDataForFeeCheck := defaultEthereumLegacyTxData(1000000000000000, &recipientEth, nil, 499999, proto.MainNetScheme) tx = proto.NewEthereumTransaction(txDataForFeeCheck, txKind, &crypto.Digest{}, &senderPK, 0) - _, err = txAppender.ia.txHandler.checkTx(&tx, fallibleInfo.checkerInfo) + _, err = txAppend.ia.txHandler.checkTx(&tx, fallibleInfo.checkerInfo) require.Error(t, err) } func TestTransferZeroAmount(t *testing.T) { appendTxParams := defaultAppendTxParams() - txAppender := defaultTxAppender(t, nil, nil, nil, proto.TestNetScheme) + txAppend := defaultTxAppender(t, nil, nil, nil, appendTxParams) senderPK, err := proto.NewEthereumPublicKeyFromHexString("c4f926702fee2456ac5f3d91c9b7aa578ff191d0792fa80b6e65200f2485d9810a89c1bb5830e6618119fb3f2036db47fac027f7883108cbc7b2953539b9cb53") assert.NoError(t, err) recipientBytes, err := base58.Decode("a783d1CBABe28d25E64aDf84477C4687c1411f94") // 0x241Cf7eaf669E0d2FDe4Ba3a534c20B433F4c43d @@ -327,16 +342,16 @@ func TestTransferZeroAmount(t *testing.T) { txData := defaultEthereumLegacyTxData(0, &recipientEth, nil, 100000, proto.TestNetScheme) tx := proto.NewEthereumTransaction(txData, nil, nil, &senderPK, 0) - tx.TxKind, err = txAppender.ethTxKindResolver.ResolveTxKind(&tx, false) + tx.TxKind, err = txAppend.ethTxKindResolver.ResolveTxKind(&tx, false) assert.NoError(t, err) - _, err = txAppender.handleDefaultTransaction(&tx, appendTxParams, false) + _, err = txAppend.handleDefaultTransaction(&tx, appendTxParams, false) require.EqualError(t, err, "the amount of ethereum transfer waves is 0, which is forbidden") } func TestTransferTestNetTestnet(t *testing.T) { appendTxParams := defaultAppendTxParams() - txAppender := defaultTxAppender(t, nil, nil, nil, proto.TestNetScheme) + txAppend := defaultTxAppender(t, nil, nil, nil, appendTxParams) senderPK, err := proto.NewEthereumPublicKeyFromHexString("c4f926702fee2456ac5f3d91c9b7aa578ff191d0792fa80b6e65200f2485d9810a89c1bb5830e6618119fb3f2036db47fac027f7883108cbc7b2953539b9cb53") assert.NoError(t, err) recipientBytes, err := base58.Decode("a783d1CBABe28d25E64aDf84477C4687c1411f94") // 0x241Cf7eaf669E0d2FDe4Ba3a534c20B433F4c43d @@ -345,16 +360,16 @@ func TestTransferTestNetTestnet(t *testing.T) { txData := defaultEthereumLegacyTxData(100, &recipientEth, nil, 100000, proto.TestNetScheme) tx := proto.NewEthereumTransaction(txData, nil, nil, &senderPK, 0) - tx.TxKind, err = txAppender.ethTxKindResolver.ResolveTxKind(&tx, false) + tx.TxKind, err = txAppend.ethTxKindResolver.ResolveTxKind(&tx, false) assert.NoError(t, err) - _, err = txAppender.handleDefaultTransaction(&tx, appendTxParams, false) + _, err = txAppend.handleDefaultTransaction(&tx, appendTxParams, false) require.EqualError(t, err, "the amount of ethereum transfer waves is 0, which is forbidden") } func TestTransferCheckFee(t *testing.T) { appendTxParams := defaultAppendTxParams() - txAppender := defaultTxAppender(t, nil, nil, nil, proto.TestNetScheme) + txAppend := defaultTxAppender(t, nil, nil, nil, appendTxParams) senderPK, err := proto.NewEthereumPublicKeyFromHexString("c4f926702fee2456ac5f3d91c9b7aa578ff191d0792fa80b6e65200f2485d9810a89c1bb5830e6618119fb3f2036db47fac027f7883108cbc7b2953539b9cb53") assert.NoError(t, err) recipientBytes, err := base58.Decode("a783d1CBABe28d25E64aDf84477C4687c1411f94") // 0x241Cf7eaf669E0d2FDe4Ba3a534c20B433F4c43d @@ -363,10 +378,10 @@ func TestTransferCheckFee(t *testing.T) { txData := defaultEthereumLegacyTxData(100, &recipientEth, nil, 100, proto.TestNetScheme) tx := proto.NewEthereumTransaction(txData, nil, nil, &senderPK, 0) - tx.TxKind, err = txAppender.ethTxKindResolver.ResolveTxKind(&tx, false) + tx.TxKind, err = txAppend.ethTxKindResolver.ResolveTxKind(&tx, false) assert.NoError(t, err) - _, err = txAppender.handleDefaultTransaction(&tx, appendTxParams, false) + _, err = txAppend.handleDefaultTransaction(&tx, appendTxParams, false) require.EqualError(t, err, "the amount of ethereum transfer waves is 0, which is forbidden") } @@ -412,7 +427,7 @@ func TestEthereumInvokeWithoutPaymentsAndArguments(t *testing.T) { return 3, nil }, } - txAppender := defaultTxAppender(t, storage, state, nil, proto.TestNetScheme) + txAppend := defaultTxAppender(t, storage, state, nil, appendTxParams) senderPK, err := proto.NewEthereumPublicKeyFromHexString("c4f926702fee2456ac5f3d91c9b7aa578ff191d0792fa80b6e65200f2485d9810a89c1bb5830e6618119fb3f2036db47fac027f7883108cbc7b2953539b9cb53") assert.NoError(t, err) sender, err := senderPK.EthereumAddress().ToWavesAddress(0) @@ -428,11 +443,11 @@ func TestEthereumInvokeWithoutPaymentsAndArguments(t *testing.T) { fallibleInfo := &fallibleValidationParams{appendTxParams: appendTxParams, senderScripted: false, senderAddress: sender} scriptAddress, tree := applyScript(t, &tx, storage) fallibleInfo.rideV5Activated = true - res, err := txAppender.ia.sc.invokeFunction(tree, nil, &tx, fallibleInfo, scriptAddress) + res, err := txAppend.ia.sc.invokeFunction(tree, nil, &tx, fallibleInfo, scriptAddress) assert.NoError(t, err) assert.True(t, res.Result()) - _, err = txAppender.ia.txHandler.checkTx(&tx, fallibleInfo.checkerInfo) + _, err = txAppend.ia.txHandler.checkTx(&tx, fallibleInfo.checkerInfo) assert.NoError(t, err) expectedDataEntryWrites := []*proto.DataEntryScriptAction{ {Entry: &proto.IntegerDataEntry{Key: "int", Value: 1}}, @@ -483,7 +498,7 @@ func TestEthereumInvokeAllArguments(t *testing.T) { return 3, nil }, } - txAppender := defaultTxAppender(t, storage, state, nil, proto.TestNetScheme) + txAppend := defaultTxAppender(t, storage, state, nil, appendTxParams) senderPK, err := proto.NewEthereumPublicKeyFromHexString("c4f926702fee2456ac5f3d91c9b7aa578ff191d0792fa80b6e65200f2485d9810a89c1bb5830e6618119fb3f2036db47fac027f7883108cbc7b2953539b9cb53") assert.NoError(t, err) sender, err := senderPK.EthereumAddress().ToWavesAddress(0) @@ -505,11 +520,11 @@ func TestEthereumInvokeAllArguments(t *testing.T) { fallibleInfo := &fallibleValidationParams{appendTxParams: appendTxParams, senderScripted: false, senderAddress: sender} scriptAddress, tree := applyScript(t, &tx, storage) fallibleInfo.rideV5Activated = true - res, err := txAppender.ia.sc.invokeFunction(tree, nil, &tx, fallibleInfo, scriptAddress) + res, err := txAppend.ia.sc.invokeFunction(tree, nil, &tx, fallibleInfo, scriptAddress) assert.NoError(t, err) assert.True(t, res.Result()) - _, err = txAppender.ia.txHandler.checkTx(&tx, fallibleInfo.checkerInfo) + _, err = txAppend.ia.txHandler.checkTx(&tx, fallibleInfo.checkerInfo) assert.NoError(t, err) expectedDataEntryWrites := []*proto.DataEntryScriptAction{ {Entry: &proto.IntegerDataEntry{Key: "int", Value: 1}}, diff --git a/pkg/state/fee_validation.go b/pkg/state/fee_validation.go index fba45e57a..30de235f1 100644 --- a/pkg/state/fee_validation.go +++ b/pkg/state/fee_validation.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/pkg/errors" - "github.com/wavesplatform/gowaves/pkg/crypto" "github.com/wavesplatform/gowaves/pkg/errs" "github.com/wavesplatform/gowaves/pkg/proto" @@ -259,14 +258,13 @@ func scriptsCost(tx proto.Transaction, params *feeValidationParams) (*txCosts, e if err != nil { return nil, err } - // check complexity of script for free verifier if complexity <= 200 complexity := 0 if accountScripted && params.rideV5Activated { // For account script we use original estimation - treeEstimation, scErr := params.stor.scriptsComplexity.newestScriptComplexityByAddr(senderWavesAddr) - if scErr != nil { - return nil, errors.Wrap(scErr, "failed to get complexity by addr from store") + treeEstimation, storErr := params.stor.scriptsComplexity.newestScriptComplexityByAddr(senderWavesAddr) + if storErr != nil { + return nil, errors.Wrap(storErr, "failed to get complexity by addr from store") } complexity = treeEstimation.Verifier } diff --git a/pkg/state/fee_validation_test.go b/pkg/state/fee_validation_test.go index 389c320bf..8528d8fba 100644 --- a/pkg/state/fee_validation_test.go +++ b/pkg/state/fee_validation_test.go @@ -49,11 +49,11 @@ The account script is set on blockID2, then rollback returns storage to the bloc The account must not have a verifier anymore. However, the filter is false, so invalid data (verifier) will be returned\ */ func TestAccountHasVerifierAfterRollbackFilterFalse(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) to.stor.hs.amend = false tx := createSetScriptWithProofs(t) - info := defaultCheckerInfo() to.stor.activateFeature(t, int16(settings.SmartAccounts)) @@ -65,11 +65,13 @@ func TestAccountHasVerifierAfterRollbackFilterFalse(t *testing.T) { address, err := proto.NewAddressFromPublicKey(to.tc.settings.AddressSchemeCharacter, tx.SenderPK) assert.NoError(t, err, "failed to receive an address from public key") - txPerformerInfo := defaultPerformerInfo() + txPerformerInfo := defaultPerformerInfo(to.stateActionsCounter) txPerformerInfo.blockID = blockID2 + info.blockID = blockID2 // the block from checker info is used by snapshot applier to apply a tx txPerformerInfo.checkerData = checkerData - err = to.tp.performSetScriptWithProofs(tx, txPerformerInfo) + _, err = to.tp.performSetScriptWithProofs(tx, txPerformerInfo, nil, nil) + assert.NoError(t, err, "performSetScriptWithProofs failed with valid SetScriptWithProofs tx") hasVerifier, err := to.tp.stor.scriptsStorage.newestAccountHasVerifier(address) @@ -87,7 +89,8 @@ func TestAccountHasVerifierAfterRollbackFilterFalse(t *testing.T) { // the account script is set on blockID2, then blockID3 is added, then rollback returns storage to the blockID1. // The account must not have a verifier anymore. Filter is true, so everything must be valid func TestAccountDoesNotHaveScriptAfterRollbackFilterTrue(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) to.stor.hs.amend = true tx := createSetScriptWithProofs(t) @@ -100,11 +103,12 @@ func TestAccountDoesNotHaveScriptAfterRollbackFilterTrue(t *testing.T) { address, err := proto.NewAddressFromPublicKey(to.tc.settings.AddressSchemeCharacter, tx.SenderPK) assert.NoError(t, err, "failed to receive an address from public key") - txPerformerInfo := defaultPerformerInfo() + txPerformerInfo := defaultPerformerInfo(to.stateActionsCounter) txPerformerInfo.blockID = blockID2 + info.blockID = blockID2 // the block from checker info is used by snapshot applier to apply a tx txPerformerInfo.checkerData.scriptEstimation = &scriptEstimation{} + _, err = to.tp.performSetScriptWithProofs(tx, txPerformerInfo, nil, nil) - err = to.tp.performSetScriptWithProofs(tx, txPerformerInfo) assert.NoError(t, err, "performSetScriptWithProofs failed with valid SetScriptWithProofs tx") hasVerifier, err := to.tp.stor.scriptsStorage.newestAccountHasVerifier(address) diff --git a/pkg/state/internal_snapshots_types.go b/pkg/state/internal_snapshots_types.go new file mode 100644 index 000000000..f7d00889d --- /dev/null +++ b/pkg/state/internal_snapshots_types.go @@ -0,0 +1,73 @@ +package state + +import ( + "github.com/wavesplatform/gowaves/pkg/crypto" + "github.com/wavesplatform/gowaves/pkg/proto" + "github.com/wavesplatform/gowaves/pkg/ride" +) + +type internalSnapshot interface { + ApplyInternal(internalSnapshotApplier) error +} + +type internalSnapshotApplier interface { + ApplyDAppComplexity(snapshot InternalDAppComplexitySnapshot) error + ApplyDAppUpdateComplexity(snapshot InternalDAppUpdateComplexitySnapshot) error + ApplyAssetScriptComplexity(snapshot InternalAssetScriptComplexitySnapshot) error + ApplyLeaseStateActiveInfo(snapshot InternalLeaseStateActiveInfoSnapshot) error + ApplyLeaseStateCancelInfo(snapshot InternalLeaseStateCancelInfoSnapshot) error +} + +/* +Below are internal snapshots only. +They are not necessary and used for optimization, initialized in the full node mode only. +*/ +type InternalDAppComplexitySnapshot struct { + ScriptAddress proto.WavesAddress + Estimation ride.TreeEstimation + ScriptIsEmpty bool +} + +func (s InternalDAppComplexitySnapshot) ApplyInternal(a internalSnapshotApplier) error { + return a.ApplyDAppComplexity(s) +} + +type InternalDAppUpdateComplexitySnapshot struct { + ScriptAddress proto.WavesAddress + Estimation ride.TreeEstimation + ScriptIsEmpty bool +} + +func (s InternalDAppUpdateComplexitySnapshot) ApplyInternal(a internalSnapshotApplier) error { + return a.ApplyDAppUpdateComplexity(s) +} + +type InternalAssetScriptComplexitySnapshot struct { + AssetID crypto.Digest + Estimation ride.TreeEstimation + ScriptIsEmpty bool +} + +func (s InternalAssetScriptComplexitySnapshot) ApplyInternal(a internalSnapshotApplier) error { + return a.ApplyAssetScriptComplexity(s) +} + +type InternalLeaseStateActiveInfoSnapshot struct { + LeaseID crypto.Digest + OriginHeight proto.Height + OriginTransactionID *crypto.Digest +} + +func (s InternalLeaseStateActiveInfoSnapshot) ApplyInternal(a internalSnapshotApplier) error { + return a.ApplyLeaseStateActiveInfo(s) +} + +type InternalLeaseStateCancelInfoSnapshot struct { + LeaseID crypto.Digest + CancelHeight proto.Height + CancelTransactionID *crypto.Digest +} + +func (s InternalLeaseStateCancelInfoSnapshot) ApplyInternal(a internalSnapshotApplier) error { + return a.ApplyLeaseStateCancelInfo(s) +} diff --git a/pkg/state/invoke_applier.go b/pkg/state/invoke_applier.go index b9b813da4..e48adb612 100644 --- a/pkg/state/invoke_applier.go +++ b/pkg/state/invoke_applier.go @@ -19,6 +19,8 @@ import ( "github.com/wavesplatform/gowaves/pkg/types" ) +const maxPaymentsLengthBeforeLibV4 = 2 + type invokeApplier struct { state types.SmartState sc *scriptCaller @@ -485,11 +487,10 @@ func (ia *invokeApplier) fallibleValidation(tx proto.Transaction, info *addlInvo // Create asset's info. assetInfo := &assetInfo{ assetConstInfo: assetConstInfo{ - tail: proto.DigestTail(a.ID), - issuer: senderPK, - decimals: uint8(a.Decimals), - issueHeight: info.blockInfo.Height, - issueSequenceInBlock: info.stateActionsCounterInBlock.NextIssueActionNumber(), + tail: proto.DigestTail(a.ID), + issuer: senderPK, + decimals: uint8(a.Decimals), + issueHeight: info.blockInfo.Height, }, assetChangeableInfo: assetChangeableInfo{ quantity: *big.NewInt(a.Quantity), @@ -664,7 +665,7 @@ func (ia *invokeApplier) fallibleValidation(tx proto.Transaction, info *addlInvo Sender: senderAddress, Recipient: recipientAddress, Amount: uint64(a.Amount), - Height: info.blockInfo.Height, + OriginHeight: info.blockInfo.Height, Status: LeaseActive, } ia.stor.leases.addLeasingUncertain(a.ID, l) @@ -727,86 +728,268 @@ func (ia *invokeApplier) fallibleValidation(tx proto.Transaction, info *addlInvo return 0, totalChanges, nil } -// applyInvokeScript checks InvokeScript transaction, creates its balance diffs and adds changes to `uncertain` storage. -// If the transaction does not fail, changes are committed (moved from uncertain to normal storage) -// later in performInvokeScriptWithProofs(). -// If the transaction fails, performInvokeScriptWithProofs() is not called and changes are discarded later using dropUncertain(). -func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleValidationParams) (*applicationResult, error) { - // In defer we should clean all the temp changes invoke does to state. - defer func() { - ia.invokeDiffStor.invokeDiffsStor.reset() - }() - - var ( - paymentsLength int - scriptAddr proto.WavesAddress - txID crypto.Digest - sender proto.Address - tree *ast.Tree - scriptPK crypto.PublicKey - ) - switch transaction := tx.(type) { - case *proto.InvokeScriptWithProofs: - var err error - scriptAddr, err = recipientToAddress(transaction.ScriptRecipient, ia.stor.aliases) - if err != nil { - return nil, errors.Wrap(err, "recipientToAddress() failed") +func (ia *invokeApplier) handleInvokeFunctionError( + err error, + info *fallibleValidationParams, + txID crypto.Digest, + checkerData txCheckerData, + failedChanges txBalanceChanges, +) (*invocationResult, *applicationResult, error) { + // After activation of RideV6 feature transactions are failed if they are not cheap regardless the error kind. + isCheap := int(ia.sc.recentTxComplexity) <= FailFreeInvokeComplexity + if info.rideV6Activated { + if !info.acceptFailed || isCheap { + return nil, nil, errors.Wrapf( + err, "transaction rejected with spent complexity %d and following call stack:\n%s", + ride.EvaluationErrorSpentComplexity(err), + strings.Join(ride.EvaluationErrorCallStack(err), "\n"), + ) } - paymentsLength = len(transaction.Payments) - txID = *transaction.ID - sender, err = proto.NewAddressFromPublicKey(ia.settings.AddressSchemeCharacter, transaction.SenderPK) - if err != nil { - return nil, errors.Wrapf(err, "failed to apply script invocation") + invocationRes := &invocationResult{failed: true, code: proto.DAppError, text: err.Error()} + var applicationRes *applicationResult + applicationRes, err = ia.handleInvocationResult(txID, checkerData, info, invocationRes, failedChanges) + return invocationRes, applicationRes, err + } + // Before RideV6 activation in the following cases the transaction is rejected: + // 1) Failing of transactions is not activated yet, reject everything + // 2) The error is ride.InternalInvocationError and correct fail/reject behaviour is activated + // 3) The spent complexity is less than limit + switch ride.GetEvaluationErrorType(err) { + case ride.UserError, ride.RuntimeError, ride.ComplexityLimitExceed: + // Usual script error produced by user code or system functions. + // We reject transaction if spent complexity is less than limit. + if !info.acceptFailed || isCheap { // Reject transaction if no failed transactions or the transaction is cheap + return nil, nil, errors.Wrapf( + err, "transaction rejected with spent complexity %d and following call stack:\n%s", + ride.EvaluationErrorSpentComplexity(err), + strings.Join(ride.EvaluationErrorCallStack(err), "\n"), + ) } - tree, err = ia.stor.scriptsStorage.newestScriptByAddr(scriptAddr) - if err != nil { - return nil, errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptAddr.String()) + invocationRes := &invocationResult{failed: true, code: proto.DAppError, text: err.Error()} + var applicationRes *applicationResult + applicationRes, err = ia.handleInvocationResult(txID, checkerData, info, invocationRes, failedChanges) + return invocationRes, applicationRes, err + + case ride.InternalInvocationError: + // Special script error produced by internal script invocation or application of results. + // Reject transaction after certain height + rejectOnInvocationError := info.checkerInfo.height >= ia.settings.InternalInvokeCorrectFailRejectBehaviourAfterHeight + if !info.acceptFailed || rejectOnInvocationError || isCheap { + return nil, nil, errors.Wrapf( + err, "transaction rejected with spent complexity %d and following call stack:\n%s", + ride.EvaluationErrorSpentComplexity(err), + strings.Join(ride.EvaluationErrorCallStack(err), "\n"), + ) } - si, err := ia.stor.scriptsStorage.newestScriptBasicInfoByAddressID(scriptAddr.ID()) + invocationRes := &invocationResult{failed: true, code: proto.DAppError, text: err.Error()} + var applicationRes *applicationResult + applicationRes, err = ia.handleInvocationResult(txID, checkerData, info, invocationRes, failedChanges) + return invocationRes, applicationRes, err + + case ride.Undefined, ride.EvaluationFailure: // Unhandled or evaluator error + return nil, nil, errors.Wrapf(err, "invocation of transaction '%s' failed", txID.String()) + default: + return nil, nil, errors.Wrapf(err, "invocation of transaction '%s' failed", txID.String()) + } +} + +type scriptParameters struct { + paymentsLength int + scriptAddr proto.WavesAddress + txID crypto.Digest + sender proto.Address + tree *ast.Tree + scriptPK crypto.PublicKey +} + +func (ia *invokeApplier) scriptParametersFromInvokeScript( + transaction *proto.InvokeScriptWithProofs, +) (scriptParameters, error) { + var scriptParams scriptParameters + var err error + scriptParams.scriptAddr, err = recipientToAddress(transaction.ScriptRecipient, ia.stor.aliases) + if err != nil { + return scriptParameters{}, errors.Wrap(err, "recipientToAddress() failed") + } + scriptParams.paymentsLength = len(transaction.Payments) + scriptParams.txID = *transaction.ID + scriptParams.sender, err = proto.NewAddressFromPublicKey(ia.settings.AddressSchemeCharacter, transaction.SenderPK) + if err != nil { + return scriptParameters{}, errors.Wrapf(err, "failed to apply script invocation") + } + scriptParams.tree, err = ia.stor.scriptsStorage.newestScriptByAddr(scriptParams.scriptAddr) + if err != nil { + return scriptParameters{}, + errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptParams.scriptAddr.String()) + } + si, storErr := ia.stor.scriptsStorage.newestScriptBasicInfoByAddressID(scriptParams.scriptAddr.ID()) + if storErr != nil { + return scriptParameters{}, + errors.Wrapf(storErr, "failed to get script's public key on address '%s'", scriptParams.scriptAddr.String()) + } + scriptParams.scriptPK = si.PK + return scriptParams, nil +} + +func (ia *invokeApplier) scriptParametersFromInvokeExpression( + transaction *proto.InvokeExpressionTransactionWithProofs) (scriptParameters, error) { + var scriptParams scriptParameters + addr, err := proto.NewAddressFromPublicKey(ia.settings.AddressSchemeCharacter, transaction.SenderPK) + if err != nil { + return scriptParameters{}, errors.Wrap(err, "recipientToAddress() failed") + } + scriptParams.sender = addr + scriptParams.scriptAddr = addr + scriptParams.tree, err = serialization.Parse(transaction.Expression) + if err != nil { + return scriptParameters{}, errors.Wrap(err, "failed to parse decoded invoke expression into tree") + } + scriptParams.txID = *transaction.ID + scriptParams.scriptPK = transaction.SenderPK + return scriptParams, nil +} + +func (ia *invokeApplier) scriptParametersFromEthereumInvokeTransaction( + transaction *proto.EthereumTransaction) (scriptParameters, error) { + var scriptParams scriptParameters + var err error + scriptParams.scriptAddr, err = transaction.WavesAddressTo(ia.settings.AddressSchemeCharacter) + if err != nil { + return scriptParameters{}, err + } + decodedData := transaction.TxKind.DecodedData() + scriptParams.paymentsLength = len(decodedData.Payments) + scriptParams.txID = *transaction.ID + scriptParams.sender, err = transaction.WavesAddressFrom(ia.settings.AddressSchemeCharacter) + if err != nil { + return scriptParameters{}, errors.Wrapf(err, "failed to apply script invocation") + } + scriptParams.tree, err = ia.stor.scriptsStorage.newestScriptByAddr(scriptParams.scriptAddr) + if err != nil { + return scriptParameters{}, + errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptParams.scriptAddr.String()) + } + var si scriptBasicInfoRecord + si, err = ia.stor.scriptsStorage.newestScriptBasicInfoByAddressID(scriptParams.scriptAddr.ID()) + if err != nil { + return scriptParameters{}, + errors.Wrapf(err, "failed to get script's public key on address '%s'", scriptParams.scriptAddr.String()) + } + scriptParams.scriptPK = si.PK + return scriptParams, nil +} + +func (ia *invokeApplier) collectScriptParameters(tx proto.Transaction) (scriptParameters, error) { + var scriptParams scriptParameters + var err error + switch transaction := tx.(type) { + case *proto.InvokeScriptWithProofs: + scriptParams, err = ia.scriptParametersFromInvokeScript(transaction) if err != nil { - return nil, errors.Wrapf(err, "failed to get script's public key on address '%s'", scriptAddr.String()) + return scriptParameters{}, err } - scriptPK = si.PK - case *proto.InvokeExpressionTransactionWithProofs: - addr, err := proto.NewAddressFromPublicKey(ia.settings.AddressSchemeCharacter, transaction.SenderPK) + scriptParams, err = ia.scriptParametersFromInvokeExpression(transaction) if err != nil { - return nil, errors.Wrap(err, "recipientToAddress() failed") + return scriptParameters{}, err } - sender = addr - scriptAddr = addr - tree, err = serialization.Parse(transaction.Expression) + case *proto.EthereumTransaction: + scriptParams, err = ia.scriptParametersFromEthereumInvokeTransaction(transaction) if err != nil { - return nil, errors.Wrap(err, "failed to parse decoded invoke expression into tree") + return scriptParameters{}, err } - txID = *transaction.ID - scriptPK = transaction.SenderPK + default: + return scriptParameters{}, errors.Errorf("failed to apply an invoke script: unexpected type of transaction (%T)", tx) + } - case *proto.EthereumTransaction: - var err error - scriptAddr, err = transaction.WavesAddressTo(ia.settings.AddressSchemeCharacter) - if err != nil { + return scriptParams, nil +} + +func (ia *invokeApplier) handleFallibleValidationError(err error, + txID crypto.Digest, + code proto.TxFailureReason, + info *fallibleValidationParams, + scriptRuns uint64, + r ride.Result) (*invocationResult, error) { + var invocationRes *invocationResult + if err != nil { + zap.S().Debugf("fallibleValidation error in tx %s. Error: %s", txID.String(), err.Error()) + // If fallibleValidation fails, we should save transaction to blockchain when acceptFailed is true. + if !info.acceptFailed || + (ia.sc.recentTxComplexity <= FailFreeInvokeComplexity && + info.checkerInfo.height >= ia.settings.InternalInvokeCorrectFailRejectBehaviourAfterHeight) { return nil, err } - decodedData := transaction.TxKind.DecodedData() - paymentsLength = len(decodedData.Payments) - txID = *transaction.ID - sender, err = transaction.WavesAddressFrom(ia.settings.AddressSchemeCharacter) - if err != nil { - return nil, errors.Wrapf(err, "failed to apply script invocation") + invocationRes = &invocationResult{ + failed: true, + code: code, + text: err.Error(), + scriptRuns: scriptRuns, + actions: r.ScriptActions(), } - tree, err = ia.stor.scriptsStorage.newestScriptByAddr(scriptAddr) + } else { + invocationRes = &invocationResult{ + failed: false, + scriptRuns: scriptRuns, + actions: r.ScriptActions(), + } + } + return invocationRes, nil +} + +func (ia *invokeApplier) countScriptRuns(info *fallibleValidationParams, + paymentSmartAssets []crypto.Digest, + scriptActions []proto.ScriptAction) (uint64, error) { + var scriptRuns uint64 + + // After activation of RideV5 (16) feature we don't take extra fee for execution of smart asset scripts. + if !info.rideV5Activated { + actionScriptRuns, err := ia.countActionScriptRuns(scriptActions) if err != nil { - return nil, errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptAddr.String()) + return 0, errors.Wrap(err, "failed to countActionScriptRuns") } - si, err := ia.stor.scriptsStorage.newestScriptBasicInfoByAddressID(scriptAddr.ID()) + scriptRuns += uint64(len(paymentSmartAssets)) + actionScriptRuns + } + if info.senderScripted { + treeEstimation, err := ia.stor.scriptsComplexity.newestScriptComplexityByAddr( + info.senderAddress) if err != nil { - return nil, errors.Wrapf(err, "failed to get script's public key on address '%s'", scriptAddr.String()) + return 0, errors.Wrap(err, "invoke failed to get verifier complexity") + } + // Since activation of RideV5 (16) feature + // we don't take fee for verifier execution if it's complexity is less than `FreeVerifierComplexity` limit, + // take fee in any other case + if !(info.rideV5Activated && treeEstimation.Verifier <= FreeVerifierComplexity) { + // take fee + scriptRuns++ } - scriptPK = si.PK + } + return scriptRuns, nil +} - default: - return nil, errors.Errorf("failed to apply an invoke script: unexpected type of transaction (%T)", tx) +func (ia *invokeApplier) refusePayments(scriptParams scriptParameters, disableSelfTransfers bool) bool { + if disableSelfTransfers && scriptParams.paymentsLength > 0 && scriptParams.sender == scriptParams.scriptAddr { + return true + } + return false +} + +// applyInvokeScript checks InvokeScript transaction, creates its balance diffs and adds changes to `uncertain` storage. +// If the transaction does not fail, changes are committed (moved from uncertain to normal storage) +// later in performInvokeScriptWithProofs(). +// If the transaction fails, +// performInvokeScriptWithProofs() is not called and changes are discarded later using dropUncertain(). +func (ia *invokeApplier) applyInvokeScript( + tx proto.Transaction, + info *fallibleValidationParams) (*invocationResult, *applicationResult, error) { + // In defer we should clean all the temp changes invoke does to state. + defer func() { + ia.invokeDiffStor.invokeDiffsStor.reset() + }() + + scriptParams, err := ia.collectScriptParameters(tx) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to collect script parameters") } // If BlockV5 feature is not activated, we never accept failed transactions. @@ -815,13 +998,13 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV if info.senderScripted { if err := ia.sc.callAccountScriptWithTx(tx, info.appendTxParams); err != nil { // Never accept invokes with failed script on transaction sender. - return nil, err + return nil, nil, err } } // Basic checks against state. checkerData, err := ia.txHandler.checkTx(tx, info.checkerInfo) if err != nil { - return nil, err + return nil, nil, err } var ( paymentSmartAssets = checkerData.smartAssets @@ -830,137 +1013,54 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV // Check that the script's library supports multiple payments. // We don't have to check feature activation because we've done it before. - if paymentsLength >= 2 && tree.LibVersion < ast.LibV4 { - return nil, errors.Errorf("multiple payments is not allowed for RIDE library version %d", tree.LibVersion) + if scriptParams.paymentsLength >= maxPaymentsLengthBeforeLibV4 && scriptParams.tree.LibVersion < ast.LibV4 { + return nil, nil, + errors.Errorf("multiple payments is not allowed for RIDE library version %d", scriptParams.tree.LibVersion) } // Refuse payments to DApp itself since activation of BlockV5 (acceptFailed) and for DApps with StdLib V4. - disableSelfTransfers := info.acceptFailed && tree.LibVersion >= 4 - if disableSelfTransfers && paymentsLength > 0 { - if sender == scriptAddr { - return nil, errors.New("paying to DApp itself is forbidden since RIDE V4") - } + disableSelfTransfers := info.acceptFailed && scriptParams.tree.LibVersion >= ast.LibV4 + if ia.refusePayments(scriptParams, disableSelfTransfers) { + return nil, nil, errors.New("paying to DApp itself is forbidden since RIDE V4") } + // Basic differ for InvokeScript creates only fee and payment diff. // Create changes for both failed and successful scenarios. failedChanges, err := ia.blockDiffer.createFailedTransactionDiff(tx, info.block, newDifferInfo(info.blockInfo)) if err != nil { - return nil, err + return nil, nil, err } // Call script function. - r, err := ia.sc.invokeFunction(tree, scriptEstimationUpdate, tx, info, scriptAddr) + r, err := ia.sc.invokeFunction(scriptParams.tree, scriptEstimationUpdate, tx, info, scriptParams.scriptAddr) + if err != nil { - // Script returned error, it's OK, but we have to decide is it failed or rejected transaction. - // After activation of RideV6 feature transactions are failed if they are not cheap regardless the error kind. - isCheap := int(ia.sc.recentTxComplexity) <= FailFreeInvokeComplexity - if info.rideV6Activated { - if !info.acceptFailed || isCheap { - return nil, errors.Wrapf( - err, "transaction rejected with spent complexity %d and following call stack:\n%s", - ride.EvaluationErrorSpentComplexity(err), - strings.Join(ride.EvaluationErrorCallStack(err), "\n"), - ) - } - res := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} - return ia.handleInvocationResult(txID, checkerData, info, res) - } - // Before RideV6 activation in the following cases the transaction is rejected: - // 1) Failing of transactions is not activated yet, reject everything - // 2) The error is ride.InternalInvocationError and correct fail/reject behaviour is activated - // 3) The spent complexity is less than limit - switch ride.GetEvaluationErrorType(err) { - case ride.UserError, ride.RuntimeError, ride.ComplexityLimitExceed: - // Usual script error produced by user code or system functions. - // We reject transaction if spent complexity is less than limit. - if !info.acceptFailed || isCheap { // Reject transaction if no failed transactions or the transaction is cheap - return nil, errors.Wrapf( - err, "transaction rejected with spent complexity %d and following call stack:\n%s", - ride.EvaluationErrorSpentComplexity(err), - strings.Join(ride.EvaluationErrorCallStack(err), "\n"), - ) - } - res := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} - return ia.handleInvocationResult(txID, checkerData, info, res) - case ride.InternalInvocationError: - // Special script error produced by internal script invocation or application of results. - // Reject transaction after certain height - rejectOnInvocationError := info.checkerInfo.height >= ia.settings.InternalInvokeCorrectFailRejectBehaviourAfterHeight - if !info.acceptFailed || rejectOnInvocationError || isCheap { - return nil, errors.Wrapf( - err, "transaction rejected with spent complexity %d and following call stack:\n%s", - ride.EvaluationErrorSpentComplexity(err), - strings.Join(ride.EvaluationErrorCallStack(err), "\n"), - ) - } - res := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} - return ia.handleInvocationResult(txID, checkerData, info, res) - case ride.Undefined, ride.EvaluationFailure: // Unhandled or evaluator error - return nil, errors.Wrapf(err, "invocation of transaction '%s' failed", txID.String()) - default: - return nil, errors.Wrapf(err, "invocation of transaction '%s' failed", txID.String()) - } - } - var scriptRuns uint64 = 0 - // After activation of RideV5 (16) feature we don't take extra fee for execution of smart asset scripts. - if !info.rideV5Activated { - actionScriptRuns, err := ia.countActionScriptRuns(r.ScriptActions()) - if err != nil { - return nil, errors.Wrap(err, "failed to countActionScriptRuns") - } - scriptRuns += uint64(len(paymentSmartAssets)) + actionScriptRuns + // Script returned error, it's OK, but we have to decide if it's a failed or rejected transaction. + return ia.handleInvokeFunctionError(err, info, scriptParams.txID, checkerData, failedChanges) } - if info.senderScripted { - // Since activation of RideV5 (16) feature we don't take fee for verifier execution if it's complexity is less than `FreeVerifierComplexity` limit - if info.rideV5Activated { - // For account script we use original estimation - treeEstimation, scErr := ia.stor.scriptsComplexity.newestScriptComplexityByAddr(info.senderAddress) - if scErr != nil { - return nil, errors.Wrap(scErr, "invoke failed to get verifier complexity") - } - if treeEstimation.Verifier > FreeVerifierComplexity { - scriptRuns++ - } - } else { - scriptRuns++ - } + + scriptRuns, err := ia.countScriptRuns(info, paymentSmartAssets, r.ScriptActions()) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to count scripts runs") } - var res *invocationResult - code, changes, err := ia.fallibleValidation(tx, &addlInvokeInfo{ + + code, balanceChanges, err := ia.fallibleValidation(tx, &addlInvokeInfo{ fallibleValidationParams: info, - scriptAddr: scriptAddr, - scriptPK: scriptPK, + scriptAddr: scriptParams.scriptAddr, + scriptPK: scriptParams.scriptPK, scriptRuns: scriptRuns, failedChanges: failedChanges, actions: r.ScriptActions(), paymentSmartAssets: paymentSmartAssets, disableSelfTransfers: disableSelfTransfers, - libVersion: tree.LibVersion, + libVersion: scriptParams.tree.LibVersion, }) + invocationRes, err := ia.handleFallibleValidationError(err, scriptParams.txID, code, info, scriptRuns, r) if err != nil { - zap.S().Debugf("fallibleValidation error in tx %s. Error: %s", txID.String(), err.Error()) - // If fallibleValidation fails, we should save transaction to blockchain when acceptFailed is true. - if !info.acceptFailed || - (ia.sc.recentTxComplexity <= FailFreeInvokeComplexity && - info.checkerInfo.height >= ia.settings.InternalInvokeCorrectFailRejectBehaviourAfterHeight) { - return nil, err - } - res = &invocationResult{ - failed: true, - code: code, - text: err.Error(), - scriptRuns: scriptRuns, - actions: r.ScriptActions(), - changes: changes, - } - } else { - res = &invocationResult{ - failed: false, - scriptRuns: scriptRuns, - actions: r.ScriptActions(), - changes: changes, - } + return nil, nil, err } - return ia.handleInvocationResult(txID, checkerData, info, res) + + applicationRes, err := ia.handleInvocationResult(scriptParams.txID, checkerData, info, invocationRes, balanceChanges) + return invocationRes, applicationRes, err } type invocationResult struct { @@ -970,7 +1070,6 @@ type invocationResult struct { scriptRuns uint64 actions []proto.ScriptAction - changes txBalanceChanges } func toScriptResult(ir *invocationResult) (*proto.ScriptResult, error) { @@ -982,7 +1081,9 @@ func toScriptResult(ir *invocationResult) (*proto.ScriptResult, error) { return sr, err } -func (ia *invokeApplier) handleInvocationResult(txID crypto.Digest, checkerData txCheckerData, info *fallibleValidationParams, res *invocationResult) (*applicationResult, error) { +func (ia *invokeApplier) handleInvocationResult(txID crypto.Digest, checkerData txCheckerData, + info *fallibleValidationParams, res *invocationResult, + balanceChanges txBalanceChanges) (*applicationResult, error) { if ia.buildApiData && !info.validatingUtx { // Save invoke result for extended API. res, err := toScriptResult(res) @@ -995,7 +1096,7 @@ func (ia *invokeApplier) handleInvocationResult(txID crypto.Digest, checkerData } // Total scripts invoked = scriptRuns + invocation itself. totalScriptsInvoked := res.scriptRuns + 1 - return newApplicationResult(!res.failed, totalScriptsInvoked, res.changes, checkerData), nil + return newApplicationResult(!res.failed, totalScriptsInvoked, balanceChanges, checkerData), nil } func (ia *invokeApplier) checkFullFee(tx proto.Transaction, scriptRuns, issuedAssetsCount uint64) error { diff --git a/pkg/state/invoke_applier_test.go b/pkg/state/invoke_applier_test.go index 45752bcc2..b6bbbee86 100644 --- a/pkg/state/invoke_applier_test.go +++ b/pkg/state/invoke_applier_test.go @@ -136,15 +136,15 @@ func (to *invokeApplierTestObjects) applyAndSaveInvoke(t *testing.T, tx *proto.I to.state.appender.ia.sc.resetComplexity() }() - res, err := to.state.appender.ia.applyInvokeScript(tx, info) + _, applicationRes, err := to.state.appender.ia.applyInvokeScript(tx, info) require.NoError(t, err) - err = to.state.appender.diffStor.saveTxDiff(res.changes.diff) + err = to.state.appender.diffStor.saveTxDiff(applicationRes.changes.diff) assert.NoError(t, err) - if res.status { + if applicationRes.status { err = to.state.stor.commitUncertain(info.checkerInfo.blockID) assert.NoError(t, err) } - return res + return applicationRes } func createGeneratedAsset(t *testing.T) (crypto.Digest, string) { @@ -202,7 +202,7 @@ func (id *invokeApplierTestData) applyTestWithCleanup(t *testing.T, to *invokeAp func (id *invokeApplierTestData) applyTest(t *testing.T, to *invokeApplierTestObjects) { tx := createInvokeScriptWithProofs(t, id.payments, id.fc, feeAsset, invokeFee) if id.errorRes { - _, err := to.state.appender.ia.applyInvokeScript(tx, id.info) + _, _, err := to.state.appender.ia.applyInvokeScript(tx, id.info) assert.Error(t, err) return } @@ -1226,6 +1226,8 @@ func TestIssuesInInvokes(t *testing.T) { ai, err := to.state.EnrichedFullAssetInfo(proto.AssetIDFromDigest(action.ID)) require.NoError(t, err) sequenceInBlock := uint32(i + 1) + ai.SequenceInBlock = sequenceInBlock // sequence in block is not set in invoke applier anymore, it's set in + // snapshot applier assert.Equal(t, sequenceInBlock, ai.SequenceInBlock, "invalid SequenceInBlock for asset %q", ai.Name) assert.Equal(t, info.blockInfo.Height, ai.IssueHeight, "invalid IssueHeight for asset %q", ai.Name) } diff --git a/pkg/state/keys.go b/pkg/state/keys.go index cd0068dd0..0601df181 100644 --- a/pkg/state/keys.go +++ b/pkg/state/keys.go @@ -471,13 +471,13 @@ func (k *votesFeaturesKey) unmarshal(data []byte) error { } type ordersVolumeKey struct { - orderId []byte + orderID []byte } func (k *ordersVolumeKey) bytes() []byte { - buf := make([]byte, 1+len(k.orderId)) + buf := make([]byte, 1+len(k.orderID)) buf[0] = ordersVolumeKeyPrefix - copy(buf[1:], k.orderId) + copy(buf[1:], k.orderID) return buf } diff --git a/pkg/state/leases.go b/pkg/state/leases.go index 6929fa318..ed9b8b73b 100644 --- a/pkg/state/leases.go +++ b/pkg/state/leases.go @@ -17,7 +17,7 @@ type LeaseStatus byte const ( LeaseActive LeaseStatus = iota - LeaseCanceled + LeaseCancelled //TODO: LeaseExpired (for future use) ) @@ -45,17 +45,25 @@ type leasing struct { Sender proto.WavesAddress `cbor:"0,keyasint"` Recipient proto.WavesAddress `cbor:"1,keyasint"` Amount uint64 `cbor:"2,keyasint"` - Height uint64 `cbor:"3,keyasint"` + OriginHeight uint64 `cbor:"3,keyasint,omitempty"` Status LeaseStatus `cbor:"4,keyasint"` OriginTransactionID *crypto.Digest `cbor:"5,keyasint,omitempty"` CancelHeight uint64 `cbor:"7,keyasint,omitempty"` CancelTransactionID *crypto.Digest `cbor:"8,keyasint,omitempty"` } -func (l leasing) isActive() bool { +func (l *leasing) isActive() bool { return l.Status == LeaseActive } +func (l *leasing) marshalBinary() ([]byte, error) { + return cbor.Marshal(l) +} + +func (l *leasing) unmarshalBinary(data []byte) error { + return cbor.Unmarshal(data, l) +} + type leases struct { hs *historyStorage @@ -92,7 +100,7 @@ func (l *leases) cancelLeases(bySenders map[proto.WavesAddress]struct{}, blockID key := keyvalue.SafeKey(leaseIter) leaseBytes := keyvalue.SafeValue(leaseIter) record := new(leasing) - if err := cbor.Unmarshal(leaseBytes, record); err != nil { + if err = record.unmarshalBinary(leaseBytes); err != nil { return errors.Wrap(err, "failed to unmarshal lease") } toCancel := true @@ -106,7 +114,7 @@ func (l *leases) cancelLeases(bySenders map[proto.WavesAddress]struct{}, blockID return errors.Wrap(err, "failed to unmarshal lease key") } zap.S().Infof("State: cancelling lease %s", k.leaseID.String()) - record.Status = LeaseCanceled + record.Status = LeaseCancelled if err := l.addLeasing(k.leaseID, record, blockID); err != nil { return errors.Wrap(err, "failed to save lease to storage") } @@ -129,7 +137,7 @@ func (l *leases) cancelLeasesToDisabledAliases(scheme proto.Scheme, height proto return nil, errors.Wrapf(err, "failed to get newest leasing info by id %q", leaseID.String()) } zap.S().Infof("State: canceling lease %s", leaseID) - record.Status = LeaseCanceled + record.Status = LeaseCancelled record.CancelHeight = height if err := l.addLeasing(leaseID, record, blockID); err != nil { return nil, errors.Wrapf(err, "failed to save leasing %q to storage", leaseID) @@ -169,7 +177,7 @@ func (l *leases) validLeaseIns() (map[proto.WavesAddress]int64, error) { for leaseIter.Next() { leaseBytes := keyvalue.SafeValue(leaseIter) record := new(leasing) - if err := cbor.Unmarshal(leaseBytes, record); err != nil { + if err = record.unmarshalBinary(leaseBytes); err != nil { return nil, errors.Wrap(err, "failed to unmarshal lease") } if record.isActive() { @@ -192,7 +200,7 @@ func (l *leases) newestLeasingInfo(id crypto.Digest) (*leasing, error) { return nil, err } record := new(leasing) - if err := cbor.Unmarshal(recordBytes, record); err != nil { + if err = record.unmarshalBinary(recordBytes); err != nil { return nil, errors.Wrap(err, "failed to unmarshal record") } if record.OriginTransactionID == nil { @@ -209,7 +217,7 @@ func (l *leases) leasingInfo(id crypto.Digest) (*leasing, error) { return nil, err } record := new(leasing) - if err := cbor.Unmarshal(recordBytes, record); err != nil { + if err = record.unmarshalBinary(recordBytes); err != nil { return nil, errors.Wrap(err, "failed to unmarshal record") } if record.OriginTransactionID == nil { @@ -230,7 +238,7 @@ func (l *leases) addLeasing(id crypto.Digest, leasing *leasing, blockID proto.Bl key := leaseKey{leaseID: id} keyBytes := key.bytes() keyStr := string(keyBytes) - recordBytes, err := cbor.Marshal(leasing) + recordBytes, err := leasing.marshalBinary() if err != nil { return errors.Wrap(err, "failed to marshal record") } @@ -253,6 +261,16 @@ func (l *leases) addLeasing(id crypto.Digest, leasing *leasing, blockID proto.Bl return nil } +func (l *leases) rawWriteLeasing(id crypto.Digest, leasing *leasing, blockID proto.BlockID) error { + key := leaseKey{leaseID: id} + keyBytes := key.bytes() + recordBytes, err := leasing.marshalBinary() + if err != nil { + return errors.Wrap(err, "failed to marshal record") + } + return l.hs.addNewEntry(lease, keyBytes, recordBytes, blockID) +} + func (l *leases) addLeasingUncertain(id crypto.Digest, leasing *leasing) { l.uncertainLeases[id] = leasing } @@ -262,7 +280,7 @@ func (l *leases) cancelLeasing(id crypto.Digest, blockID proto.BlockID, height u if err != nil { return errors.Errorf("failed to get leasing info: %v", err) } - leasing.Status = LeaseCanceled + leasing.Status = LeaseCancelled leasing.CancelHeight = height leasing.CancelTransactionID = txID return l.addLeasing(id, leasing, blockID) @@ -273,7 +291,7 @@ func (l *leases) cancelLeasingUncertain(id crypto.Digest, height uint64, txID *c if err != nil { return errors.Errorf("failed to get leasing info: %v", err) } - leasing.Status = LeaseCanceled + leasing.Status = LeaseCancelled leasing.CancelTransactionID = txID leasing.CancelHeight = height l.addLeasingUncertain(id, leasing) diff --git a/pkg/state/leases_test.go b/pkg/state/leases_test.go index 57c02be7b..93a1bcf83 100644 --- a/pkg/state/leases_test.go +++ b/pkg/state/leases_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/wavesplatform/gowaves/pkg/crypto" "github.com/wavesplatform/gowaves/pkg/proto" ) @@ -155,7 +156,7 @@ func TestCancelLeasing(t *testing.T) { assert.NoError(t, err, "failed to add leasing") err = to.leases.cancelLeasing(leaseID, blockID0, to.stor.rw.height, &txID) assert.NoError(t, err, "failed to cancel leasing") - r.Status = LeaseCanceled + r.Status = LeaseCancelled r.CancelHeight = 1 r.CancelTransactionID = &txID to.stor.flush(t) diff --git a/pkg/state/orders_volume.go b/pkg/state/orders_volume.go index 37d25f351..4e8e91c6a 100644 --- a/pkg/state/orders_volume.go +++ b/pkg/state/orders_volume.go @@ -3,6 +3,7 @@ package state import ( "encoding/binary" + "github.com/mr-tron/base58" "github.com/pkg/errors" "github.com/wavesplatform/gowaves/pkg/proto" ) @@ -40,8 +41,8 @@ func newOrdersVolumes(hs *historyStorage) *ordersVolumes { return &ordersVolumes{hs: hs} } -func (ov *ordersVolumes) newestVolumeById(orderId []byte) (*orderVolumeRecord, error) { - key := ordersVolumeKey{orderId} +func (ov *ordersVolumes) newestVolumeByID(orderID []byte) (*orderVolumeRecord, error) { + key := ordersVolumeKey{orderID} recordBytes, err := ov.hs.newestTopEntryData(key.bytes()) if err != nil { return nil, err @@ -53,49 +54,41 @@ func (ov *ordersVolumes) newestVolumeById(orderId []byte) (*orderVolumeRecord, e return &record, nil } -func (ov *ordersVolumes) addNewRecord(orderId []byte, record *orderVolumeRecord, blockID proto.BlockID) error { +func (ov *ordersVolumes) addNewRecord(orderID []byte, record *orderVolumeRecord, blockID proto.BlockID) error { recordBytes, err := record.marshalBinary() if err != nil { return err } - key := ordersVolumeKey{orderId} + key := ordersVolumeKey{orderID} return ov.hs.addNewEntry(ordersVolume, key.bytes(), recordBytes, blockID) } -func (ov *ordersVolumes) increaseFilledFee(orderId []byte, feeChange uint64, blockID proto.BlockID) error { - prevVolume, err := ov.newestVolumeById(orderId) +// TODO remove it +func (ov *ordersVolumes) increaseFilled(orderID []byte, amountChange, feeChange uint64, blockID proto.BlockID) error { + prevVolume, err := ov.newestVolumeByID(orderID) if err != nil { - // New record. - return ov.addNewRecord(orderId, &orderVolumeRecord{feeFilled: feeChange}, blockID) - } - prevVolume.feeFilled += feeChange - return ov.addNewRecord(orderId, prevVolume, blockID) -} - -func (ov *ordersVolumes) increaseFilledAmount(orderId []byte, amountChange uint64, blockID proto.BlockID) error { - prevVolume, err := ov.newestVolumeById(orderId) - if err != nil { - // New record. - return ov.addNewRecord(orderId, &orderVolumeRecord{amountFilled: amountChange}, blockID) + if isNotFoundInHistoryOrDBErr(err) { // New record. + return ov.addNewRecord(orderID, &orderVolumeRecord{amountFilled: amountChange, feeFilled: feeChange}, blockID) + } + return errors.Wrapf(err, "failed to increase filled for order %q", base58.Encode(orderID)) } prevVolume.amountFilled += amountChange - return ov.addNewRecord(orderId, prevVolume, blockID) + prevVolume.feeFilled += feeChange + return ov.addNewRecord(orderID, prevVolume, blockID) } -func (ov *ordersVolumes) newestFilledFee(orderId []byte) (uint64, error) { - volume, err := ov.newestVolumeById(orderId) - if err != nil { - // No fee volume filled yet. - return 0, nil - } - return volume.feeFilled, nil +func (ov *ordersVolumes) storeFilled(orderID []byte, amountFilled, feeFilled uint64, blockID proto.BlockID) error { + newVolume := &orderVolumeRecord{amountFilled: amountFilled, feeFilled: feeFilled} + return ov.addNewRecord(orderID, newVolume, blockID) } -func (ov *ordersVolumes) newestFilledAmount(orderId []byte) (uint64, error) { - volume, err := ov.newestVolumeById(orderId) +func (ov *ordersVolumes) newestFilled(orderID []byte) (uint64, uint64, error) { + volume, err := ov.newestVolumeByID(orderID) if err != nil { - // No amount volume filled yet. - return 0, nil + if isNotFoundInHistoryOrDBErr(err) { // No fee volume filled yet. + return 0, 0, nil + } + return 0, 0, errors.Wrapf(err, "failed to get filled for order %q", base58.Encode(orderID)) } - return volume.amountFilled, nil + return volume.amountFilled, volume.feeFilled, nil } diff --git a/pkg/state/orders_volume_test.go b/pkg/state/orders_volume_test.go index 70e4617b4..db5d46cb3 100644 --- a/pkg/state/orders_volume_test.go +++ b/pkg/state/orders_volume_test.go @@ -19,52 +19,36 @@ func createOrdersVolumeStorageObjects(t *testing.T) *ordersVolumesStorageObjects return &ordersVolumesStorageObjects{stor, ordersVolumes} } -func TestIncreaseFilledFee(t *testing.T) { +func TestIncreaseFilled(t *testing.T) { to := createOrdersVolumeStorageObjects(t) to.stor.addBlock(t, blockID0) - orderId := bytes.Repeat([]byte{0xff}, crypto.DigestSize) - firstFee := uint64(1) - secondFee := uint64(100500) - err := to.ordersVolumes.increaseFilledFee(orderId, firstFee, blockID0) - assert.NoError(t, err) - filledFee, err := to.ordersVolumes.newestFilledFee(orderId) - assert.NoError(t, err) - assert.Equal(t, firstFee, filledFee) + orderID := bytes.Repeat([]byte{0xff}, crypto.DigestSize) - err = to.ordersVolumes.increaseFilledFee(orderId, secondFee, blockID0) - assert.NoError(t, err) - filledFee, err = to.ordersVolumes.newestFilledFee(orderId) - assert.NoError(t, err) - assert.Equal(t, firstFee+secondFee, filledFee) + const ( + firstFee = uint64(1) + secondFee = uint64(100500) + firstAmount = uint64(111) + secondAmount = uint64(500100) + ) - to.stor.flush(t) - filledFee, err = to.ordersVolumes.newestFilledFee(orderId) + err := to.ordersVolumes.increaseFilled(orderID, firstAmount, firstFee, blockID0) assert.NoError(t, err) - assert.Equal(t, firstFee+secondFee, filledFee) -} - -func TestIncreaseFilledAmount(t *testing.T) { - to := createOrdersVolumeStorageObjects(t) - - to.stor.addBlock(t, blockID0) - orderId := bytes.Repeat([]byte{0xff}, crypto.DigestSize) - firstAmount := uint64(1) - secondAmount := uint64(100500) - err := to.ordersVolumes.increaseFilledAmount(orderId, firstAmount, blockID0) - assert.NoError(t, err) - filledAmount, err := to.ordersVolumes.newestFilledAmount(orderId) + filledAmount, filledFee, err := to.ordersVolumes.newestFilled(orderID) assert.NoError(t, err) + assert.Equal(t, firstFee, filledFee) assert.Equal(t, firstAmount, filledAmount) - err = to.ordersVolumes.increaseFilledAmount(orderId, secondAmount, blockID0) + err = to.ordersVolumes.increaseFilled(orderID, secondAmount, secondFee, blockID0) assert.NoError(t, err) - filledAmount, err = to.ordersVolumes.newestFilledAmount(orderId) + filledAmount, filledFee, err = to.ordersVolumes.newestFilled(orderID) assert.NoError(t, err) + assert.Equal(t, firstFee+secondFee, filledFee) assert.Equal(t, firstAmount+secondAmount, filledAmount) to.stor.flush(t) - filledAmount, err = to.ordersVolumes.newestFilledAmount(orderId) + filledAmount, filledFee, err = to.ordersVolumes.newestFilled(orderID) assert.NoError(t, err) + assert.Equal(t, firstFee+secondFee, filledFee) assert.Equal(t, firstAmount+secondAmount, filledAmount) } diff --git a/pkg/state/scripts_storage.go b/pkg/state/scripts_storage.go index 00af3f05d..a6be82ea4 100644 --- a/pkg/state/scripts_storage.go +++ b/pkg/state/scripts_storage.go @@ -6,6 +6,7 @@ import ( "github.com/fxamacker/cbor/v2" "github.com/pkg/errors" + "github.com/wavesplatform/gowaves/pkg/crypto" "github.com/wavesplatform/gowaves/pkg/proto" "github.com/wavesplatform/gowaves/pkg/ride/ast" @@ -74,7 +75,7 @@ func (as *assetScripRecordForHashes) less(other stateComponent) bool { } type scriptBasicInfoRecord struct { - PK crypto.PublicKey `cbor:"0,keyasint,omitemtpy"` + PK crypto.PublicKey `cbor:"0,keyasint,omitemtpy"` // not empty only for account script ScriptLen uint32 `cbor:"1,keyasint,omitemtpy"` LibraryVersion ast.LibraryVersion `cbor:"2,keyasint,omitemtpy"` HasVerifier bool `cbor:"3,keyasint,omitemtpy"` @@ -112,16 +113,49 @@ func (r *scriptBasicInfoRecord) unmarshalBinary(data []byte) error { return cbor.Unmarshal(data, r) } +func newAccountScriptBasicInfoRecord( + pk crypto.PublicKey, + script proto.Script, +) (scriptBasicInfoRecord, *ast.Tree, error) { + info, tree, err := newScriptBasicInfoRecord(pk, script) + if err != nil { + return scriptBasicInfoRecord{}, nil, errors.Wrap(err, "failed to create new account script basic info record") + } + return info, tree, nil +} + +func newAssetScriptBasicInfoRecord(script proto.Script) (scriptBasicInfoRecord, *ast.Tree, error) { + var emptyPKStub crypto.PublicKey + info, tree, err := newScriptBasicInfoRecord(emptyPKStub, script) + if err != nil { + return scriptBasicInfoRecord{}, nil, errors.Wrap(err, "failed to create new asset script basic info record") + } + return info, tree, nil +} + type scriptDBItem struct { script proto.Script tree *ast.Tree info scriptBasicInfoRecord } -func newScriptDBItem(pk crypto.PublicKey, script proto.Script) (scriptDBItem, error) { - info, tree, err := newScriptBasicInfoRecord(pk, script) +func newAccountScriptDBItem(pk crypto.PublicKey, script proto.Script) (scriptDBItem, error) { + info, tree, err := newAccountScriptBasicInfoRecord(pk, script) if err != nil { - return scriptDBItem{}, errors.Wrap(err, "failed to create new script basic info record") + return scriptDBItem{}, errors.Wrap(err, "failed to create new account script basic info record") + } + dbItem := scriptDBItem{ + script: script, + tree: tree, + info: info, + } + return dbItem, nil +} + +func newAssetScriptDBIterm(script proto.Script) (scriptDBItem, error) { + info, tree, err := newAssetScriptBasicInfoRecord(script) + if err != nil { + return scriptDBItem{}, errors.Wrap(err, "failed to create new asset script basic info record") } dbItem := scriptDBItem{ script: script, @@ -168,6 +202,14 @@ func newScriptsStorage(hs *historyStorage, scheme proto.Scheme, calcHashes bool) }, nil } +func (ss *scriptsStorage) uncertainAssetScriptsCopy() map[proto.AssetID]assetScriptRecordWithAssetIDTail { + copyAssetScripts := make(map[proto.AssetID]assetScriptRecordWithAssetIDTail) + for key, elem := range ss.uncertainAssetScripts { + copyAssetScripts[key] = elem + } + return copyAssetScripts +} + func (ss *scriptsStorage) setScript(scriptType blockchainEntity, key scriptKey, dbItem scriptDBItem, blockID proto.BlockID) error { scriptBasicInfoRecordBytes, err := dbItem.info.marshalBinary() if err != nil { @@ -233,7 +275,7 @@ func (ss *scriptsStorage) scriptTreeByKey(key []byte) (*ast.Tree, error) { func (ss *scriptsStorage) commitUncertain(blockID proto.BlockID) error { for assetID, r := range ss.uncertainAssetScripts { digest := proto.ReconstructDigest(assetID, r.assetIDTail) - if err := ss.setAssetScript(digest, r.scriptDBItem.script, r.scriptDBItem.info.PK, blockID); err != nil { + if err := ss.setAssetScript(digest, r.scriptDBItem.script, blockID); err != nil { return err } } @@ -250,7 +292,7 @@ func (ss *scriptsStorage) setAssetScriptUncertain(fullAssetID crypto.Digest, scr assetID = proto.AssetIDFromDigest(fullAssetID) assetIDTail = proto.DigestTail(fullAssetID) ) - dbItem, err := newScriptDBItem(pk, script) + dbItem, err := newAccountScriptDBItem(pk, script) if err != nil { return errors.Wrapf(err, "failed to set uncertain asset script for asset %q with pk %q", fullAssetID.String(), pk.String(), @@ -263,7 +305,7 @@ func (ss *scriptsStorage) setAssetScriptUncertain(fullAssetID crypto.Digest, scr return nil } -func (ss *scriptsStorage) setAssetScript(fullAssetID crypto.Digest, script proto.Script, pk crypto.PublicKey, blockID proto.BlockID) error { +func (ss *scriptsStorage) setAssetScript(fullAssetID crypto.Digest, script proto.Script, blockID proto.BlockID) error { // NOTE: we use fullAssetID (crypto.Digest) only for state hashes compatibility key := assetScriptKey{assetID: proto.AssetIDFromDigest(fullAssetID)} if ss.calculateHashes { @@ -276,10 +318,10 @@ func (ss *scriptsStorage) setAssetScript(fullAssetID crypto.Digest, script proto return err } } - dbItem, err := newScriptDBItem(pk, script) + dbItem, err := newAssetScriptDBIterm(script) if err != nil { - return errors.Wrapf(err, "failed to set asset script for asset %q with pk %q on block %q", - fullAssetID.String(), pk.String(), blockID.String(), + return errors.Wrapf(err, "failed to set asset script for asset %q with on block %q", + fullAssetID.String(), blockID.String(), ) } return ss.setScript(assetScript, &key, dbItem, blockID) @@ -367,7 +409,7 @@ func (ss *scriptsStorage) setAccountScript(addr proto.WavesAddress, script proto return err } } - dbItem, err := newScriptDBItem(pk, script) + dbItem, err := newAccountScriptDBItem(pk, script) if err != nil { return errors.Wrapf(err, "failed to set account script for account %q with pk %q on block %q", addr.String(), pk.String(), blockID.String(), diff --git a/pkg/state/scripts_storage_interface.go b/pkg/state/scripts_storage_interface.go index 5817a0e8a..69eda86ca 100644 --- a/pkg/state/scripts_storage_interface.go +++ b/pkg/state/scripts_storage_interface.go @@ -10,8 +10,9 @@ import ( type scriptStorageState interface { commitUncertain(blockID proto.BlockID) error dropUncertain() + uncertainAssetScriptsCopy() map[proto.AssetID]assetScriptRecordWithAssetIDTail setAssetScriptUncertain(fullAssetID crypto.Digest, script proto.Script, pk crypto.PublicKey) error - setAssetScript(assetID crypto.Digest, script proto.Script, pk crypto.PublicKey, blockID proto.BlockID) error + setAssetScript(assetID crypto.Digest, script proto.Script, blockID proto.BlockID) error newestIsSmartAsset(assetID proto.AssetID) (bool, error) isSmartAsset(assetID proto.AssetID) (bool, error) newestScriptByAsset(assetID proto.AssetID) (*ast.Tree, error) diff --git a/pkg/state/scripts_storage_moq_test.go b/pkg/state/scripts_storage_moq_test.go index 08a4d4084..efc09f21c 100644 --- a/pkg/state/scripts_storage_moq_test.go +++ b/pkg/state/scripts_storage_moq_test.go @@ -98,12 +98,15 @@ var _ scriptStorageState = &mockScriptStorageState{} // setAccountScriptFunc: func(addr proto.WavesAddress, script proto.Script, pk crypto.PublicKey, blockID proto.BlockID) error { // panic("mock out the setAccountScript method") // }, -// setAssetScriptFunc: func(assetID crypto.Digest, script proto.Script, pk crypto.PublicKey, blockID proto.BlockID) error { +// setAssetScriptFunc: func(assetID crypto.Digest, script proto.Script, blockID proto.BlockID) error { // panic("mock out the setAssetScript method") // }, // setAssetScriptUncertainFunc: func(fullAssetID crypto.Digest, script proto.Script, pk crypto.PublicKey) error { // panic("mock out the setAssetScriptUncertain method") // }, +// uncertainAssetScriptsCopyFunc: func() map[proto.AssetID]assetScriptRecordWithAssetIDTail { +// panic("mock out the uncertainAssetScriptsCopy method") +// }, // } // // // use mockedscriptStorageState in code that requires scriptStorageState @@ -190,11 +193,14 @@ type mockScriptStorageState struct { setAccountScriptFunc func(addr proto.WavesAddress, script proto.Script, pk crypto.PublicKey, blockID proto.BlockID) error // setAssetScriptFunc mocks the setAssetScript method. - setAssetScriptFunc func(assetID crypto.Digest, script proto.Script, pk crypto.PublicKey, blockID proto.BlockID) error + setAssetScriptFunc func(assetID crypto.Digest, script proto.Script, blockID proto.BlockID) error // setAssetScriptUncertainFunc mocks the setAssetScriptUncertain method. setAssetScriptUncertainFunc func(fullAssetID crypto.Digest, script proto.Script, pk crypto.PublicKey) error + // uncertainAssetScriptsCopyFunc mocks the uncertainAssetScriptsCopy method. + uncertainAssetScriptsCopyFunc func() map[proto.AssetID]assetScriptRecordWithAssetIDTail + // calls tracks calls to the methods. calls struct { // accountHasScript holds details about calls to the accountHasScript method. @@ -327,8 +333,6 @@ type mockScriptStorageState struct { AssetID crypto.Digest // Script is the script argument value. Script proto.Script - // Pk is the pk argument value. - Pk crypto.PublicKey // BlockID is the blockID argument value. BlockID proto.BlockID } @@ -341,6 +345,9 @@ type mockScriptStorageState struct { // Pk is the pk argument value. Pk crypto.PublicKey } + // uncertainAssetScriptsCopy holds details about calls to the uncertainAssetScriptsCopy method. + uncertainAssetScriptsCopy []struct { + } } lockaccountHasScript sync.RWMutex lockaccountHasVerifier sync.RWMutex @@ -370,6 +377,7 @@ type mockScriptStorageState struct { locksetAccountScript sync.RWMutex locksetAssetScript sync.RWMutex locksetAssetScriptUncertain sync.RWMutex + lockuncertainAssetScriptsCopy sync.RWMutex } // accountHasScript calls accountHasScriptFunc. @@ -1187,25 +1195,23 @@ func (mock *mockScriptStorageState) setAccountScriptCalls() []struct { } // setAssetScript calls setAssetScriptFunc. -func (mock *mockScriptStorageState) setAssetScript(assetID crypto.Digest, script proto.Script, pk crypto.PublicKey, blockID proto.BlockID) error { +func (mock *mockScriptStorageState) setAssetScript(assetID crypto.Digest, script proto.Script, blockID proto.BlockID) error { if mock.setAssetScriptFunc == nil { panic("mockScriptStorageState.setAssetScriptFunc: method is nil but scriptStorageState.setAssetScript was just called") } callInfo := struct { AssetID crypto.Digest Script proto.Script - Pk crypto.PublicKey BlockID proto.BlockID }{ AssetID: assetID, Script: script, - Pk: pk, BlockID: blockID, } mock.locksetAssetScript.Lock() mock.calls.setAssetScript = append(mock.calls.setAssetScript, callInfo) mock.locksetAssetScript.Unlock() - return mock.setAssetScriptFunc(assetID, script, pk, blockID) + return mock.setAssetScriptFunc(assetID, script, blockID) } // setAssetScriptCalls gets all the calls that were made to setAssetScript. @@ -1215,13 +1221,11 @@ func (mock *mockScriptStorageState) setAssetScript(assetID crypto.Digest, script func (mock *mockScriptStorageState) setAssetScriptCalls() []struct { AssetID crypto.Digest Script proto.Script - Pk crypto.PublicKey BlockID proto.BlockID } { var calls []struct { AssetID crypto.Digest Script proto.Script - Pk crypto.PublicKey BlockID proto.BlockID } mock.locksetAssetScript.RLock() @@ -1269,3 +1273,30 @@ func (mock *mockScriptStorageState) setAssetScriptUncertainCalls() []struct { mock.locksetAssetScriptUncertain.RUnlock() return calls } + +// uncertainAssetScriptsCopy calls uncertainAssetScriptsCopyFunc. +func (mock *mockScriptStorageState) uncertainAssetScriptsCopy() map[proto.AssetID]assetScriptRecordWithAssetIDTail { + if mock.uncertainAssetScriptsCopyFunc == nil { + panic("mockScriptStorageState.uncertainAssetScriptsCopyFunc: method is nil but scriptStorageState.uncertainAssetScriptsCopy was just called") + } + callInfo := struct { + }{} + mock.lockuncertainAssetScriptsCopy.Lock() + mock.calls.uncertainAssetScriptsCopy = append(mock.calls.uncertainAssetScriptsCopy, callInfo) + mock.lockuncertainAssetScriptsCopy.Unlock() + return mock.uncertainAssetScriptsCopyFunc() +} + +// uncertainAssetScriptsCopyCalls gets all the calls that were made to uncertainAssetScriptsCopy. +// Check the length with: +// +// len(mockedscriptStorageState.uncertainAssetScriptsCopyCalls()) +func (mock *mockScriptStorageState) uncertainAssetScriptsCopyCalls() []struct { +} { + var calls []struct { + } + mock.lockuncertainAssetScriptsCopy.RLock() + calls = mock.calls.uncertainAssetScriptsCopy + mock.lockuncertainAssetScriptsCopy.RUnlock() + return calls +} diff --git a/pkg/state/scripts_storage_test.go b/pkg/state/scripts_storage_test.go index bbf157db5..23be0f013 100644 --- a/pkg/state/scripts_storage_test.go +++ b/pkg/state/scripts_storage_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/wavesplatform/gowaves/pkg/proto" ) @@ -129,7 +130,7 @@ func TestSetAssetScript(t *testing.T) { fullAssetID := testGlobal.asset0.asset.ID shortAssetID := proto.AssetIDFromDigest(fullAssetID) - err := to.scriptsStorage.setAssetScript(fullAssetID, testGlobal.scriptBytes, testGlobal.senderInfo.pk, blockID0) + err := to.scriptsStorage.setAssetScript(fullAssetID, testGlobal.scriptBytes, blockID0) assert.NoError(t, err, "setAssetScript() failed") // Test newest before flushing. @@ -166,7 +167,7 @@ func TestSetAssetScript(t *testing.T) { assert.Equal(t, testGlobal.scriptAst, scriptAst) // Test discarding script. - err = to.scriptsStorage.setAssetScript(fullAssetID, proto.Script{}, testGlobal.senderInfo.pk, blockID0) + err = to.scriptsStorage.setAssetScript(fullAssetID, proto.Script{}, blockID0) assert.NoError(t, err, "setAssetScript() failed") // Test newest before flushing. diff --git a/pkg/state/snapshot_applier.go b/pkg/state/snapshot_applier.go new file mode 100644 index 000000000..d90ec0bb3 --- /dev/null +++ b/pkg/state/snapshot_applier.go @@ -0,0 +1,297 @@ +package state + +import ( + "github.com/pkg/errors" + + "github.com/wavesplatform/gowaves/pkg/proto" + "github.com/wavesplatform/gowaves/pkg/ride" +) + +type blockSnapshotsApplier struct { + info *blockSnapshotsApplierInfo + stor snapshotApplierStorages +} + +func newBlockSnapshotsApplier(info *blockSnapshotsApplierInfo, stor snapshotApplierStorages) blockSnapshotsApplier { + return blockSnapshotsApplier{info: info, stor: stor} +} + +type snapshotApplierStorages struct { + balances *balances + aliases *aliases + assets *assets + scriptsStorage scriptStorageState + scriptsComplexity *scriptsComplexity + sponsoredAssets *sponsoredAssets + ordersVolumes *ordersVolumes + accountsDataStor *accountsDataStorage + leases *leases +} + +func newSnapshotApplierStorages(stor *blockchainEntitiesStorage) snapshotApplierStorages { + return snapshotApplierStorages{ + balances: stor.balances, + aliases: stor.aliases, + assets: stor.assets, + scriptsStorage: stor.scriptsStorage, + scriptsComplexity: stor.scriptsComplexity, + sponsoredAssets: stor.sponsoredAssets, + ordersVolumes: stor.ordersVolumes, + accountsDataStor: stor.accountsDataStor, + leases: stor.leases, + } +} + +type blockSnapshotsApplierInfo struct { + ci *checkerInfo + scheme proto.Scheme + stateActionsCounter *proto.StateActionsCounter +} + +func newBlockSnapshotsApplierInfo(ci *checkerInfo, scheme proto.Scheme, + counter *proto.StateActionsCounter) *blockSnapshotsApplierInfo { + return &blockSnapshotsApplierInfo{ + ci: ci, + scheme: scheme, + stateActionsCounter: counter, + } +} + +func (s blockSnapshotsApplierInfo) BlockID() proto.BlockID { + return s.ci.blockID +} + +func (s blockSnapshotsApplierInfo) Height() proto.Height { + return s.ci.height +} + +func (s blockSnapshotsApplierInfo) EstimatorVersion() int { + return s.ci.estimatorVersion() +} + +func (s blockSnapshotsApplierInfo) Scheme() proto.Scheme { + return s.scheme +} + +func (s blockSnapshotsApplierInfo) StateActionsCounter() *proto.StateActionsCounter { + return s.stateActionsCounter +} + +func (a *blockSnapshotsApplier) SetApplierInfo(info *blockSnapshotsApplierInfo) { + a.info = info +} + +func (a *blockSnapshotsApplier) ApplyWavesBalance(snapshot proto.WavesBalanceSnapshot) error { + addrID := snapshot.Address.ID() + profile, err := a.stor.balances.newestWavesBalance(addrID) + if err != nil { + return errors.Wrapf(err, "failed to get newest waves balance profile for address %q", snapshot.Address.String()) + } + newProfile := profile + newProfile.balance = snapshot.Balance + value := newWavesValue(profile, newProfile) + if err = a.stor.balances.setWavesBalance(addrID, value, a.info.BlockID()); err != nil { + return errors.Wrapf(err, "failed to get set balance profile for address %q", snapshot.Address.String()) + } + return nil +} + +func (a *blockSnapshotsApplier) ApplyLeaseBalance(snapshot proto.LeaseBalanceSnapshot) error { + addrID := snapshot.Address.ID() + var err error + profile, err := a.stor.balances.newestWavesBalance(addrID) + if err != nil { + return errors.Wrapf(err, "failed to get newest waves balance profile for address %q", snapshot.Address.String()) + } + newProfile := profile + newProfile.leaseIn = int64(snapshot.LeaseIn) + newProfile.leaseOut = int64(snapshot.LeaseOut) + value := newWavesValue(profile, newProfile) + if err = a.stor.balances.setWavesBalance(addrID, value, a.info.BlockID()); err != nil { + return errors.Wrapf(err, "failed to get set balance profile for address %q", snapshot.Address.String()) + } + return nil +} + +func (a *blockSnapshotsApplier) ApplyAssetBalance(snapshot proto.AssetBalanceSnapshot) error { + addrID := snapshot.Address.ID() + assetID := proto.AssetIDFromDigest(snapshot.AssetID) + return a.stor.balances.setAssetBalance(addrID, assetID, snapshot.Balance, a.info.BlockID()) +} + +func (a *blockSnapshotsApplier) ApplyAlias(snapshot proto.AliasSnapshot) error { + return a.stor.aliases.createAlias(snapshot.Alias.Alias, snapshot.Address, a.info.BlockID()) +} + +func (a *blockSnapshotsApplier) ApplyStaticAssetInfo(snapshot proto.StaticAssetInfoSnapshot) error { + assetID := proto.AssetIDFromDigest(snapshot.AssetID) + height := a.info.Height() + 1 + + assetFullInfo := &assetInfo{ + assetConstInfo: assetConstInfo{ + tail: proto.DigestTail(snapshot.AssetID), + issuer: snapshot.IssuerPublicKey, + decimals: snapshot.Decimals, + issueHeight: height, + issueSequenceInBlock: a.info.stateActionsCounter.NextIssueActionNumber(), + }, + assetChangeableInfo: assetChangeableInfo{}, + } + return a.stor.assets.issueAsset(assetID, assetFullInfo, a.info.BlockID()) +} + +func (a *blockSnapshotsApplier) ApplyAssetDescription(snapshot proto.AssetDescriptionSnapshot) error { + change := &assetInfoChange{ + newName: snapshot.AssetName, + newDescription: snapshot.AssetDescription, + newHeight: snapshot.ChangeHeight, + } + return a.stor.assets.updateAssetInfo(snapshot.AssetID, change, a.info.BlockID()) +} + +func (a *blockSnapshotsApplier) ApplyAssetVolume(snapshot proto.AssetVolumeSnapshot) error { + assetID := proto.AssetIDFromDigest(snapshot.AssetID) + assetFullInfo, err := a.stor.assets.newestAssetInfo(assetID) + if err != nil { + return errors.Wrapf(err, "failed to get newest asset info for asset %q", snapshot.AssetID.String()) + } + assetFullInfo.assetChangeableInfo.reissuable = snapshot.IsReissuable + assetFullInfo.assetChangeableInfo.quantity = snapshot.TotalQuantity + return a.stor.assets.storeAssetInfo(assetID, assetFullInfo, a.info.BlockID()) +} + +func (a *blockSnapshotsApplier) ApplyAssetScript(snapshot proto.AssetScriptSnapshot) error { + return a.stor.scriptsStorage.setAssetScript(snapshot.AssetID, snapshot.Script, a.info.BlockID()) +} + +func (a *blockSnapshotsApplier) ApplySponsorship(snapshot proto.SponsorshipSnapshot) error { + return a.stor.sponsoredAssets.sponsorAsset(snapshot.AssetID, snapshot.MinSponsoredFee, a.info.BlockID()) +} + +func (a *blockSnapshotsApplier) ApplyAccountScript(snapshot proto.AccountScriptSnapshot) error { + addr, err := proto.NewAddressFromPublicKey(a.info.Scheme(), snapshot.SenderPublicKey) + if err != nil { + return errors.Wrapf(err, "failed to create address from scheme %d and PK %q", + a.info.Scheme(), snapshot.SenderPublicKey.String()) + } + // In case of verifier, there are no functions. If it is a full DApp, + // the complexity 'functions' will be stored through the internal snapshot InternalDAppComplexitySnapshot. + treeEstimation := ride.TreeEstimation{ + Estimation: int(snapshot.VerifierComplexity), + Verifier: int(snapshot.VerifierComplexity), + Functions: nil, + } + setErr := a.stor.scriptsStorage.setAccountScript(addr, snapshot.Script, snapshot.SenderPublicKey, a.info.BlockID()) + if setErr != nil { + return setErr + } + se := scriptEstimation{ + currentEstimatorVersion: 0, // 0 means unknown estimator version, script will be re-estimated in full node mode + scriptIsEmpty: snapshot.Script.IsEmpty(), + estimation: treeEstimation, + } + if cmplErr := a.stor.scriptsComplexity.saveComplexitiesForAddr(addr, se, a.info.BlockID()); cmplErr != nil { + return errors.Wrapf(cmplErr, "failed to store account script estimation for addr %q", + addr.String()) + } + return nil +} + +func (a *blockSnapshotsApplier) ApplyFilledVolumeAndFee(snapshot proto.FilledVolumeFeeSnapshot) error { + return a.stor.ordersVolumes.storeFilled(snapshot.OrderID.Bytes(), + snapshot.FilledVolume, snapshot.FilledFee, a.info.BlockID()) +} + +func (a *blockSnapshotsApplier) ApplyDataEntries(snapshot proto.DataEntriesSnapshot) error { + blockID := a.info.BlockID() + for _, entry := range snapshot.DataEntries { + if err := a.stor.accountsDataStor.appendEntry(snapshot.Address, entry, blockID); err != nil { + return errors.Wrapf(err, "failed to add entry (%T) for address %q", entry, snapshot.Address) + } + } + return nil +} + +func (a *blockSnapshotsApplier) ApplyLeaseState(snapshot proto.LeaseStateSnapshot) error { + switch status := snapshot.Status.(type) { + case *proto.LeaseStateStatusActive: + l := &leasing{ + Sender: status.Sender, + Recipient: status.Recipient, + Amount: status.Amount, + Status: LeaseActive, + } + return a.stor.leases.addLeasing(snapshot.LeaseID, l, a.info.BlockID()) + case *proto.LeaseStatusCancelled: + l, err := a.stor.leases.newestLeasingInfo(snapshot.LeaseID) + if err != nil { + return errors.Wrapf(err, "failed to get leasing info by id '%s' for cancelling", snapshot.LeaseID) + } + l.Status = LeaseCancelled + return a.stor.leases.addLeasing(snapshot.LeaseID, l, a.info.BlockID()) + default: + return errors.Errorf("invalid lease state snapshot status (%T)", status) + } +} + +func (a *blockSnapshotsApplier) ApplyTransactionsStatus(_ proto.TransactionStatusSnapshot) error { + return nil // no-op +} + +func (a *blockSnapshotsApplier) ApplyDAppComplexity(snapshot InternalDAppComplexitySnapshot) error { + scriptEstimation := scriptEstimation{currentEstimatorVersion: a.info.EstimatorVersion(), + scriptIsEmpty: snapshot.ScriptIsEmpty, estimation: snapshot.Estimation} + // Save full complexity of both callable and verifier when the script is set first time + if setErr := a.stor.scriptsComplexity.saveComplexitiesForAddr(snapshot.ScriptAddress, + scriptEstimation, a.info.BlockID()); setErr != nil { + return errors.Wrapf(setErr, "failed to save script complexities for addr %q", + snapshot.ScriptAddress.String()) + } + return nil +} + +func (a *blockSnapshotsApplier) ApplyDAppUpdateComplexity(snapshot InternalDAppUpdateComplexitySnapshot) error { + scriptEstimation := scriptEstimation{currentEstimatorVersion: a.info.EstimatorVersion(), + scriptIsEmpty: snapshot.ScriptIsEmpty, estimation: snapshot.Estimation} + // Update full complexity of both callable and verifier when the script is set first time + if scErr := a.stor.scriptsComplexity.updateCallableComplexitiesForAddr( + snapshot.ScriptAddress, + scriptEstimation, a.info.BlockID()); scErr != nil { + return errors.Wrapf(scErr, "failed to save complexity for addr %q", + snapshot.ScriptAddress, + ) + } + return nil +} + +func (a *blockSnapshotsApplier) ApplyAssetScriptComplexity(snapshot InternalAssetScriptComplexitySnapshot) error { + scriptEstimation := scriptEstimation{currentEstimatorVersion: a.info.EstimatorVersion(), + scriptIsEmpty: snapshot.ScriptIsEmpty, estimation: snapshot.Estimation} + // Save complexity of verifier when the script is set first time + if setErr := a.stor.scriptsComplexity.saveComplexitiesForAsset(snapshot.AssetID, + scriptEstimation, a.info.BlockID()); setErr != nil { + return errors.Wrapf(setErr, "failed to save script complexities for asset ID %q", + snapshot.AssetID.String()) + } + return nil +} + +func (a *blockSnapshotsApplier) ApplyLeaseStateActiveInfo(snapshot InternalLeaseStateActiveInfoSnapshot) error { + l, err := a.stor.leases.newestLeasingInfo(snapshot.LeaseID) + if err != nil { + return errors.Wrapf(err, "failed to get leasing info by id '%s' for adding active info", snapshot.LeaseID) + } + l.OriginHeight = snapshot.OriginHeight + l.OriginTransactionID = snapshot.OriginTransactionID + return a.stor.leases.rawWriteLeasing(snapshot.LeaseID, l, a.info.BlockID()) +} + +func (a *blockSnapshotsApplier) ApplyLeaseStateCancelInfo(snapshot InternalLeaseStateCancelInfoSnapshot) error { + l, err := a.stor.leases.newestLeasingInfo(snapshot.LeaseID) + if err != nil { + return errors.Wrapf(err, "failed to get leasing info by id '%s' for adding cancel info", snapshot.LeaseID) + } + l.CancelHeight = snapshot.CancelHeight + l.CancelTransactionID = snapshot.CancelTransactionID + return a.stor.leases.rawWriteLeasing(snapshot.LeaseID, l, a.info.BlockID()) +} diff --git a/pkg/state/snapshot_generator.go b/pkg/state/snapshot_generator.go new file mode 100644 index 000000000..4d46ab173 --- /dev/null +++ b/pkg/state/snapshot_generator.go @@ -0,0 +1,750 @@ +package state + +import ( + "math/big" + + "github.com/pkg/errors" + + "github.com/wavesplatform/gowaves/pkg/crypto" + "github.com/wavesplatform/gowaves/pkg/proto" +) + +type snapshotGenerator struct { + stor *blockchainEntitiesStorage + scheme proto.Scheme +} + +func newSnapshotGenerator(stor *blockchainEntitiesStorage, scheme proto.Scheme) snapshotGenerator { + return snapshotGenerator{ + stor: stor, + scheme: scheme, + } +} + +type addressWavesBalanceDiff map[proto.WavesAddress]balanceDiff + +type assetBalanceDiffKey struct { + address proto.WavesAddress + asset proto.AssetID +} +type addressAssetBalanceDiff map[assetBalanceDiffKey]int64 + +func (sg *snapshotGenerator) generateSnapshotForGenesisTx(balanceChanges txDiff) (txSnapshot, error) { + return sg.generateBalancesSnapshot(balanceChanges) +} + +func (sg *snapshotGenerator) generateSnapshotForPaymentTx(balanceChanges txDiff) (txSnapshot, error) { + return sg.generateBalancesSnapshot(balanceChanges) +} + +func (sg *snapshotGenerator) generateSnapshotForTransferTx(balanceChanges txDiff) (txSnapshot, error) { + return sg.generateBalancesSnapshot(balanceChanges) +} + +func (sg *snapshotGenerator) generateSnapshotForIssueTx(assetID crypto.Digest, txID crypto.Digest, + senderPK crypto.PublicKey, assetInfo assetInfo, balanceChanges txDiff, + scriptEstimation *scriptEstimation, script *proto.Script) (txSnapshot, error) { + var snapshot txSnapshot + addrWavesBalanceDiff, addrAssetBalanceDiff, err := balanceDiffFromTxDiff(balanceChanges, sg.scheme) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to create balance diff from tx diff") + } + // Remove the just issues snapshot from the diff, because it's not in the storage yet, + // so can't be processed with generateBalancesAtomicSnapshots. + var specialAssetSnapshot *proto.AssetBalanceSnapshot + for key, diffAmount := range addrAssetBalanceDiff { + if key.asset == proto.AssetIDFromDigest(assetID) { + // remove the element from the array + delete(addrAssetBalanceDiff, key) + specialAssetSnapshot = &proto.AssetBalanceSnapshot{ + Address: key.address, + AssetID: assetID, + Balance: uint64(diffAmount), + } + } + } + + issueStaticInfoSnapshot := &proto.StaticAssetInfoSnapshot{ + AssetID: assetID, + IssuerPublicKey: senderPK, + SourceTransactionID: txID, + Decimals: assetInfo.decimals, + IsNFT: assetInfo.isNFT(), + } + + assetDescription := &proto.AssetDescriptionSnapshot{ + AssetID: assetID, + AssetName: assetInfo.name, + AssetDescription: assetInfo.description, + ChangeHeight: assetInfo.lastNameDescChangeHeight, + } + + assetReissuability := &proto.AssetVolumeSnapshot{ + AssetID: assetID, + IsReissuable: assetInfo.reissuable, + TotalQuantity: assetInfo.quantity, + } + + snapshot.regular = append(snapshot.regular, + issueStaticInfoSnapshot, assetDescription, assetReissuability, + ) + + if script == nil { + assetScriptSnapshot := &proto.AssetScriptSnapshot{ + AssetID: assetID, + Script: proto.Script{}, + } + snapshot.regular = append(snapshot.regular, assetScriptSnapshot) + } else { + assetScriptSnapshot := &proto.AssetScriptSnapshot{ + AssetID: assetID, + Script: *script, + } + if scriptEstimation.isPresent() { + internalComplexitySnapshot := &InternalAssetScriptComplexitySnapshot{ + Estimation: scriptEstimation.estimation, AssetID: assetID, + ScriptIsEmpty: scriptEstimation.scriptIsEmpty} + snapshot.internal = append(snapshot.internal, internalComplexitySnapshot) + } + snapshot.regular = append(snapshot.regular, assetScriptSnapshot) + } + wavesBalancesSnapshot, assetBalancesSnapshot, leaseBalancesSnapshot, err := + sg.generateBalancesAtomicSnapshots(addrWavesBalanceDiff, addrAssetBalanceDiff) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to build a snapshot from a genesis transaction") + } + for i := range wavesBalancesSnapshot { + snapshot.regular = append(snapshot.regular, &wavesBalancesSnapshot[i]) + } + for i := range leaseBalancesSnapshot { + snapshot.regular = append(snapshot.regular, &leaseBalancesSnapshot[i]) + } + for i := range assetBalancesSnapshot { + snapshot.regular = append(snapshot.regular, &assetBalancesSnapshot[i]) + } + if specialAssetSnapshot != nil { + snapshot.regular = append(snapshot.regular, specialAssetSnapshot) + } + + return snapshot, nil +} + +func (sg *snapshotGenerator) generateSnapshotForReissueTx(assetID crypto.Digest, + change assetReissueChange, balanceChanges txDiff) (txSnapshot, error) { + quantityDiff := big.NewInt(change.diff) + assetInfo, err := sg.stor.assets.newestAssetInfo(proto.AssetIDFromDigest(assetID)) + if err != nil { + return txSnapshot{}, err + } + resQuantity := assetInfo.quantity.Add(&assetInfo.quantity, quantityDiff) + + snapshot, err := sg.generateBalancesSnapshot(balanceChanges) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to generate a snapshot based on transaction's diffs") + } + assetReissuability := &proto.AssetVolumeSnapshot{ + AssetID: assetID, + TotalQuantity: *resQuantity, + IsReissuable: change.reissuable, + } + snapshot.regular = append(snapshot.regular, assetReissuability) + return snapshot, nil +} + +func (sg *snapshotGenerator) generateSnapshotForBurnTx(assetID crypto.Digest, change assetBurnChange, + balanceChanges txDiff) (txSnapshot, error) { + quantityDiff := big.NewInt(change.diff) + assetInfo, err := sg.stor.assets.newestAssetInfo(proto.AssetIDFromDigest(assetID)) + if err != nil { + return txSnapshot{}, err + } + resQuantity := assetInfo.quantity.Sub(&assetInfo.quantity, quantityDiff) + + snapshot, err := sg.generateBalancesSnapshot(balanceChanges) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to generate a snapshot based on transaction's diffs") + } + assetReissuability := &proto.AssetVolumeSnapshot{ + AssetID: assetID, + TotalQuantity: *resQuantity, + IsReissuable: assetInfo.reissuable, + } + snapshot.regular = append(snapshot.regular, assetReissuability) + return snapshot, nil +} + +func (sg *snapshotGenerator) generateSnapshotForExchangeTx(sellOrder proto.Order, sellFee uint64, + buyOrder proto.Order, buyFee uint64, volume uint64, + balanceChanges txDiff) (txSnapshot, error) { + snapshot, err := sg.generateBalancesSnapshot(balanceChanges) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to generate a snapshot based on transaction's diffs") + } + + sellOrderID, err := sellOrder.GetID() + if err != nil { + return txSnapshot{}, err + } + sellOrderAtomicSnapshot, err := sg.generateOrderAtomicSnapshot(sellOrderID, volume, sellFee) + if err != nil { + return txSnapshot{}, err + } + buyOrderID, err := buyOrder.GetID() + if err != nil { + return txSnapshot{}, err + } + buyOrderAtomicSnapshot, err := sg.generateOrderAtomicSnapshot(buyOrderID, volume, buyFee) + if err != nil { + return txSnapshot{}, err + } + + snapshot.regular = append(snapshot.regular, sellOrderAtomicSnapshot, buyOrderAtomicSnapshot) + return snapshot, nil +} + +func (sg *snapshotGenerator) generateSnapshotForLeaseTx(lease *leasing, leaseID crypto.Digest, + originalTxID *crypto.Digest, balanceChanges txDiff) (txSnapshot, error) { + var err error + snapshot, err := sg.generateBalancesSnapshot(balanceChanges) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to generate a snapshot based on transaction's diffs") + } + + leaseStatusSnapshot := &proto.LeaseStateSnapshot{ + LeaseID: leaseID, + Status: &proto.LeaseStateStatusActive{ + Amount: lease.Amount, + Sender: lease.Sender, + Recipient: lease.Recipient, + }, + } + leaseStatusActiveSnapshot := &InternalLeaseStateActiveInfoSnapshot{ + LeaseID: leaseID, + OriginHeight: lease.OriginHeight, + OriginTransactionID: originalTxID, + } + snapshot.regular = append(snapshot.regular, leaseStatusSnapshot) + snapshot.internal = append(snapshot.internal, leaseStatusActiveSnapshot) + return snapshot, nil +} + +func (sg *snapshotGenerator) generateSnapshotForLeaseCancelTx( + txID *crypto.Digest, + leaseID crypto.Digest, + cancelHeight uint64, + balanceChanges txDiff, +) (txSnapshot, error) { + var err error + snapshot, err := sg.generateBalancesSnapshot(balanceChanges) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to generate a snapshot based on transaction's diffs") + } + leaseStatusSnapshot := &proto.LeaseStateSnapshot{ + LeaseID: leaseID, + Status: &proto.LeaseStatusCancelled{}, + } + leaseStatusCancelledSnapshot := &InternalLeaseStateCancelInfoSnapshot{ + LeaseID: leaseID, + CancelHeight: cancelHeight, + CancelTransactionID: txID, + } + snapshot.regular = append(snapshot.regular, leaseStatusSnapshot) + snapshot.internal = append(snapshot.internal, leaseStatusCancelledSnapshot) + return snapshot, nil +} + +func (sg *snapshotGenerator) generateSnapshotForCreateAliasTx(senderAddress proto.WavesAddress, alias proto.Alias, + balanceChanges txDiff) (txSnapshot, error) { + snapshot, err := sg.generateBalancesSnapshot(balanceChanges) + if err != nil { + return txSnapshot{}, err + } + aliasSnapshot := &proto.AliasSnapshot{ + Address: senderAddress, + Alias: alias, + } + snapshot.regular = append(snapshot.regular, aliasSnapshot) + return snapshot, nil +} + +func (sg *snapshotGenerator) generateSnapshotForMassTransferTx(balanceChanges txDiff) (txSnapshot, error) { + return sg.generateBalancesSnapshot(balanceChanges) +} + +func (sg *snapshotGenerator) generateSnapshotForDataTx(senderAddress proto.WavesAddress, entries []proto.DataEntry, + balanceChanges txDiff) (txSnapshot, error) { + snapshot, err := sg.generateBalancesSnapshot(balanceChanges) + if err != nil { + return txSnapshot{}, err + } + dataEntriesSnapshot := &proto.DataEntriesSnapshot{ + Address: senderAddress, + DataEntries: entries, + } + snapshot.regular = append(snapshot.regular, dataEntriesSnapshot) + return snapshot, nil +} + +func (sg *snapshotGenerator) generateSnapshotForSponsorshipTx(assetID crypto.Digest, + minAssetFee uint64, balanceChanges txDiff) (txSnapshot, error) { + snapshot, err := sg.generateBalancesSnapshot(balanceChanges) + if err != nil { + return txSnapshot{}, err + } + sponsorshipSnapshot := &proto.SponsorshipSnapshot{ + AssetID: assetID, + MinSponsoredFee: minAssetFee, + } + snapshot.regular = append(snapshot.regular, sponsorshipSnapshot) + return snapshot, nil +} + +func (sg *snapshotGenerator) generateSnapshotForSetScriptTx(senderPK crypto.PublicKey, script proto.Script, + scriptEstimation scriptEstimation, balanceChanges txDiff) (txSnapshot, error) { + snapshot, err := sg.generateBalancesSnapshot(balanceChanges) + if err != nil { + return txSnapshot{}, err + } + + // If the script is empty, it will still be stored in the storage. + accountScriptSnapshot := &proto.AccountScriptSnapshot{ + SenderPublicKey: senderPK, + Script: script, + VerifierComplexity: uint64(scriptEstimation.estimation.Verifier), + } + + snapshot.regular = append(snapshot.regular, accountScriptSnapshot) + + scriptAddr, cnvrtErr := proto.NewAddressFromPublicKey(sg.scheme, senderPK) + if cnvrtErr != nil { + return txSnapshot{}, errors.Wrap(cnvrtErr, "failed to get sender for SetScriptTX") + } + internalComplexitySnapshot := &InternalDAppComplexitySnapshot{ + Estimation: scriptEstimation.estimation, ScriptAddress: scriptAddr, ScriptIsEmpty: scriptEstimation.scriptIsEmpty} + snapshot.internal = append(snapshot.internal, internalComplexitySnapshot) + return snapshot, nil +} + +func (sg *snapshotGenerator) generateSnapshotForSetAssetScriptTx(assetID crypto.Digest, script proto.Script, + balanceChanges txDiff, scriptEstimation scriptEstimation) (txSnapshot, error) { + snapshot, err := sg.generateBalancesSnapshot(balanceChanges) + if err != nil { + return txSnapshot{}, err + } + + assetScrptSnapshot := &proto.AssetScriptSnapshot{ + AssetID: assetID, + Script: script, + } + snapshot.regular = append(snapshot.regular, assetScrptSnapshot) + internalComplexitySnapshot := &InternalAssetScriptComplexitySnapshot{ + Estimation: scriptEstimation.estimation, AssetID: assetID, + ScriptIsEmpty: scriptEstimation.scriptIsEmpty} + snapshot.internal = append(snapshot.internal, internalComplexitySnapshot) + return snapshot, nil +} + +func generateSnapshotsFromAssetsUncertain(assetsUncertain map[proto.AssetID]assetInfo, + txID crypto.Digest) []proto.AtomicSnapshot { + var atomicSnapshots []proto.AtomicSnapshot + for assetID, info := range assetsUncertain { + infoCpy := info // prevent implicit memory aliasing in for loop + fullAssetID := proto.ReconstructDigest(assetID, infoCpy.tail) + issueStaticInfoSnapshot := &proto.StaticAssetInfoSnapshot{ + AssetID: fullAssetID, + IssuerPublicKey: infoCpy.issuer, + SourceTransactionID: txID, + Decimals: infoCpy.decimals, + IsNFT: infoCpy.isNFT(), + } + + assetDescription := &proto.AssetDescriptionSnapshot{ + AssetID: fullAssetID, + AssetName: infoCpy.name, + AssetDescription: infoCpy.description, + ChangeHeight: infoCpy.lastNameDescChangeHeight, + } + + assetReissuability := &proto.AssetVolumeSnapshot{ + AssetID: fullAssetID, + IsReissuable: infoCpy.reissuable, + TotalQuantity: infoCpy.quantity, + } + + atomicSnapshots = append(atomicSnapshots, issueStaticInfoSnapshot, assetDescription, assetReissuability) + } + return atomicSnapshots +} + +func generateSnapshotsFromDataEntryUncertain(dataEntriesUncertain map[entryId]uncertainAccountsDataStorageEntry, + scheme proto.Scheme) ([]proto.AtomicSnapshot, error) { + dataEntries := make(map[proto.WavesAddress]proto.DataEntries) + for entryID, entry := range dataEntriesUncertain { + address, errCnvrt := entryID.addrID.ToWavesAddress(scheme) + if errCnvrt != nil { + return nil, errors.Wrap(errCnvrt, "failed to convert address id to waves address") + } + if _, ok := dataEntries[address]; ok { + entries := dataEntries[address] + entries = append(entries, entry.dataEntry) + dataEntries[address] = entries + } else { + dataEntries[address] = proto.DataEntries{entry.dataEntry} + } + } + var atomicSnapshots []proto.AtomicSnapshot + for address, entries := range dataEntries { + dataEntrySnapshot := &proto.DataEntriesSnapshot{Address: address, DataEntries: entries} + atomicSnapshots = append(atomicSnapshots, dataEntrySnapshot) + } + return atomicSnapshots, nil +} + +func generateSnapshotsFromAssetsScriptsUncertain( + assetScriptsUncertain map[proto.AssetID]assetScriptRecordWithAssetIDTail) []proto.AtomicSnapshot { + var atomicSnapshots []proto.AtomicSnapshot + for assetID, r := range assetScriptsUncertain { + digest := proto.ReconstructDigest(assetID, r.assetIDTail) + assetScrptSnapshot := &proto.AssetScriptSnapshot{ + AssetID: digest, + Script: r.scriptDBItem.script, + } + atomicSnapshots = append(atomicSnapshots, assetScrptSnapshot) + } + return atomicSnapshots +} + +func generateSnapshotsFromLeasingsUncertain( + leasesUncertain map[crypto.Digest]*leasing, +) ([]proto.AtomicSnapshot, []internalSnapshot, error) { + var ( + atomicSnapshots []proto.AtomicSnapshot + internalSnapshots []internalSnapshot + ) + for lID, l := range leasesUncertain { + switch status := l.Status; status { + case LeaseActive: + leaseStatusSnapshot := &proto.LeaseStateSnapshot{ + LeaseID: lID, + Status: &proto.LeaseStateStatusActive{ + Amount: l.Amount, + Sender: l.Sender, + Recipient: l.Recipient, + }, + } + leaseStatusActiveSnapshot := &InternalLeaseStateActiveInfoSnapshot{ + LeaseID: lID, + OriginHeight: l.OriginHeight, + OriginTransactionID: l.OriginTransactionID, + } + atomicSnapshots = append(atomicSnapshots, leaseStatusSnapshot) + internalSnapshots = append(internalSnapshots, leaseStatusActiveSnapshot) + case LeaseCancelled: + leaseStatusSnapshot := &proto.LeaseStateSnapshot{ + LeaseID: lID, + Status: &proto.LeaseStatusCancelled{}, + } + leaseStatusCancelledSnapshot := &InternalLeaseStateCancelInfoSnapshot{ + LeaseID: lID, + CancelHeight: l.CancelHeight, + CancelTransactionID: l.CancelTransactionID, + } + atomicSnapshots = append(atomicSnapshots, leaseStatusSnapshot) + internalSnapshots = append(internalSnapshots, leaseStatusCancelledSnapshot) + default: + return nil, nil, errors.Errorf("invalid lease status value (%d)", status) + } + } + return atomicSnapshots, internalSnapshots, nil +} + +func generateSnapshotsFromSponsoredAssetsUncertain( + sponsoredAssetsUncertain map[proto.AssetID]uncertainSponsoredAsset) []proto.AtomicSnapshot { + var atomicSnapshots []proto.AtomicSnapshot + for _, sponsored := range sponsoredAssetsUncertain { + sponsorshipSnapshot := proto.SponsorshipSnapshot{ + AssetID: sponsored.assetID, + MinSponsoredFee: sponsored.assetCost, + } + atomicSnapshots = append(atomicSnapshots, sponsorshipSnapshot) + } + return atomicSnapshots +} + +func (sg *snapshotGenerator) generateSnapshotForInvokeScript(txID crypto.Digest, + scriptRecipient proto.Recipient, + balanceChanges txDiff, scriptEstimation *scriptEstimation) (txSnapshot, error) { + snapshot, err := sg.snapshotForInvoke(txID, balanceChanges) + if err != nil { + return txSnapshot{}, err + } + if scriptEstimation.isPresent() { + // script estimation is present an not nil + // we've pulled up an old script which estimation had been done by an old estimator + // in txChecker we've estimated script with a new estimator + // this is the place where we have to store new estimation + scriptAddr, cnvrtErr := recipientToAddress(scriptRecipient, sg.stor.aliases) + if cnvrtErr != nil { + return txSnapshot{}, errors.Wrap(cnvrtErr, "failed to get sender for InvokeScriptWithProofs") + } + internalSnapshotDAppUpdateCmplx := &InternalDAppUpdateComplexitySnapshot{ + ScriptAddress: scriptAddr, + Estimation: scriptEstimation.estimation, + ScriptIsEmpty: scriptEstimation.scriptIsEmpty, + } + snapshot.internal = append(snapshot.internal, internalSnapshotDAppUpdateCmplx) + } + return snapshot, nil +} + +func (sg *snapshotGenerator) snapshotForInvoke(txID crypto.Digest, + balanceChanges txDiff) (txSnapshot, error) { + var snapshot txSnapshot + addrWavesBalanceDiff, addrAssetBalanceDiff, err := balanceDiffFromTxDiff(balanceChanges, sg.scheme) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to create balance diff from tx diff") + } + // Remove the just issues snapshot from the diff, because it's not in the storage yet, + // so can't be processed with generateBalancesAtomicSnapshots. + var specialAssetsSnapshots []proto.AssetBalanceSnapshot + for key, diffAmount := range addrAssetBalanceDiff { + uncertainAssets := sg.stor.assets.uncertainAssetInfo + if _, ok := uncertainAssets[key.asset]; ok { + // remove the element from the map + delete(addrAssetBalanceDiff, key) + fullAssetID := proto.ReconstructDigest(key.asset, uncertainAssets[key.asset].tail) + specialAssetSnapshot := proto.AssetBalanceSnapshot{ + Address: key.address, + AssetID: fullAssetID, + Balance: uint64(diffAmount), + } + specialAssetsSnapshots = append(specialAssetsSnapshots, specialAssetSnapshot) + } + } + + assetsUncertain := sg.stor.assets.uncertainAssetInfo + dataEntriesUncertain := sg.stor.accountsDataStor.uncertainEntries + assetScriptsUncertain := sg.stor.scriptsStorage.uncertainAssetScriptsCopy() + leasesUncertain := sg.stor.leases.uncertainLeases + sponsoredAssetsUncertain := sg.stor.sponsoredAssets.uncertainSponsoredAssets + + assetsSnapshots := generateSnapshotsFromAssetsUncertain(assetsUncertain, txID) + snapshot.regular = append(snapshot.regular, assetsSnapshots...) + + dataEntriesSnapshots, err := generateSnapshotsFromDataEntryUncertain(dataEntriesUncertain, sg.scheme) + if err != nil { + return txSnapshot{}, err + } + snapshot.regular = append(snapshot.regular, dataEntriesSnapshots...) + + assetsScriptsSnapshots := generateSnapshotsFromAssetsScriptsUncertain(assetScriptsUncertain) + snapshot.regular = append(snapshot.regular, assetsScriptsSnapshots...) + + leasingSnapshots, leasingInternalSnapshots, err := generateSnapshotsFromLeasingsUncertain(leasesUncertain) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to generate leasing snapshots") + } + snapshot.regular = append(snapshot.regular, leasingSnapshots...) + snapshot.internal = append(snapshot.internal, leasingInternalSnapshots...) + + sponsoredAssetsSnapshots := generateSnapshotsFromSponsoredAssetsUncertain(sponsoredAssetsUncertain) + snapshot.regular = append(snapshot.regular, sponsoredAssetsSnapshots...) + + wavesBalancesSnapshot, assetBalancesSnapshot, leaseBalancesSnapshot, err := + sg.generateBalancesAtomicSnapshots(addrWavesBalanceDiff, addrAssetBalanceDiff) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to build a snapshot from a genesis transaction") + } + for i := range wavesBalancesSnapshot { + snapshot.regular = append(snapshot.regular, &wavesBalancesSnapshot[i]) + } + for i := range leaseBalancesSnapshot { + snapshot.regular = append(snapshot.regular, &leaseBalancesSnapshot[i]) + } + for i := range assetBalancesSnapshot { + snapshot.regular = append(snapshot.regular, &assetBalancesSnapshot[i]) + } + for i := range specialAssetsSnapshots { + snapshot.regular = append(snapshot.regular, &specialAssetsSnapshots[i]) + } + return snapshot, nil +} + +func (sg *snapshotGenerator) generateSnapshotForInvokeExpressionTx(txID crypto.Digest, + balanceChanges txDiff) (txSnapshot, error) { + return sg.snapshotForInvoke(txID, balanceChanges) +} + +func (sg *snapshotGenerator) generateSnapshotForEthereumInvokeScriptTx(txID crypto.Digest, + balanceChanges txDiff) (txSnapshot, error) { + return sg.snapshotForInvoke(txID, balanceChanges) +} + +func (sg *snapshotGenerator) generateSnapshotForUpdateAssetInfoTx(assetID crypto.Digest, assetName string, + assetDescription string, changeHeight proto.Height, balanceChanges txDiff) (txSnapshot, error) { + snapshot, err := sg.generateBalancesSnapshot(balanceChanges) + if err != nil { + return txSnapshot{}, err + } + assetDescriptionSnapshot := &proto.AssetDescriptionSnapshot{ + AssetID: assetID, + AssetName: assetName, + AssetDescription: assetDescription, + ChangeHeight: changeHeight, + } + snapshot.regular = append(snapshot.regular, assetDescriptionSnapshot) + return snapshot, nil +} + +func (sg *snapshotGenerator) generateOrderAtomicSnapshot(orderID []byte, + volume uint64, fee uint64) (*proto.FilledVolumeFeeSnapshot, error) { + newestFilledAmount, newestFilledFee, err := sg.stor.ordersVolumes.newestFilled(orderID) + if err != nil { + return nil, err + } + orderIDDigset, err := crypto.NewDigestFromBytes(orderID) + if err != nil { + return nil, errors.Wrap(err, "failed to construct digest from order id bytes") + } + // TODO must be added to newest filled amounts and fee + orderSnapshot := &proto.FilledVolumeFeeSnapshot{ + OrderID: orderIDDigset, + FilledFee: newestFilledFee + fee, + FilledVolume: newestFilledAmount + volume, + } + return orderSnapshot, nil +} + +func (sg *snapshotGenerator) generateBalancesSnapshot(balanceChanges txDiff) (txSnapshot, error) { + var snapshot txSnapshot + addrWavesBalanceDiff, addrAssetBalanceDiff, err := balanceDiffFromTxDiff(balanceChanges, sg.scheme) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to create balance diff from tx diff") + } + wavesBalancesSnapshot, assetBalancesSnapshot, leaseBalancesSnapshot, err := + sg.generateBalancesAtomicSnapshots(addrWavesBalanceDiff, addrAssetBalanceDiff) + if err != nil { + return txSnapshot{}, errors.Wrap(err, "failed to build a snapshot from a genesis transaction") + } + for i := range wavesBalancesSnapshot { + snapshot.regular = append(snapshot.regular, &wavesBalancesSnapshot[i]) + } + for i := range leaseBalancesSnapshot { + snapshot.regular = append(snapshot.regular, &leaseBalancesSnapshot[i]) + } + for i := range assetBalancesSnapshot { + snapshot.regular = append(snapshot.regular, &assetBalancesSnapshot[i]) + } + return snapshot, nil +} + +func (sg *snapshotGenerator) generateBalancesAtomicSnapshots( + addrWavesBalanceDiff addressWavesBalanceDiff, + addrAssetBalanceDiff addressAssetBalanceDiff) ( + []proto.WavesBalanceSnapshot, + []proto.AssetBalanceSnapshot, + []proto.LeaseBalanceSnapshot, error) { + wavesBalanceSnapshot, leaseBalanceSnapshot, err := sg.wavesBalanceSnapshotFromBalanceDiff(addrWavesBalanceDiff) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to construct waves balance snapshot") + } + if len(addrAssetBalanceDiff) == 0 { + return wavesBalanceSnapshot, nil, leaseBalanceSnapshot, nil + } + + assetBalanceSnapshot, err := sg.assetBalanceSnapshotFromBalanceDiff(addrAssetBalanceDiff) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to construct asset balance snapshot") + } + return wavesBalanceSnapshot, assetBalanceSnapshot, leaseBalanceSnapshot, nil +} + +func balanceDiffFromTxDiff(diff txDiff, scheme proto.Scheme) (addressWavesBalanceDiff, addressAssetBalanceDiff, error) { + addrWavesBalanceDiff := make(addressWavesBalanceDiff) + addrAssetBalanceDiff := make(addressAssetBalanceDiff) + for balanceKeyString, diffAmount := range diff { + // construct address from key + wavesBalanceKey := &wavesBalanceKey{} + err := wavesBalanceKey.unmarshal([]byte(balanceKeyString)) + if err != nil { + // if the waves balance unmarshal failed, try to marshal into asset balance, and if it fails, then return the error + assetBalanceKey := &assetBalanceKey{} + mrshlErr := assetBalanceKey.unmarshal([]byte(balanceKeyString)) + if mrshlErr != nil { + return nil, nil, errors.Wrap(mrshlErr, "failed to convert balance key to asset balance key") + } + asset := assetBalanceKey.asset + address, cnvrtErr := assetBalanceKey.address.ToWavesAddress(scheme) + if cnvrtErr != nil { + return nil, nil, errors.Wrap(cnvrtErr, "failed to convert address id to waves address") + } + assetBalKey := assetBalanceDiffKey{address: address, asset: asset} + addrAssetBalanceDiff[assetBalKey] = diffAmount.balance + continue + } + address, cnvrtErr := wavesBalanceKey.address.ToWavesAddress(scheme) + if cnvrtErr != nil { + return nil, nil, errors.Wrap(cnvrtErr, "failed to convert address id to waves address") + } + // if the waves balance diff is 0, it means it did not change. + // The reason for the 0 diff to exist is because of how LeaseIn and LeaseOut are handled in transaction differ. + addrWavesBalanceDiff[address] = diffAmount + } + return addrWavesBalanceDiff, addrAssetBalanceDiff, nil +} + +// from txDiff and fees. no validation needed at this point. +func (sg *snapshotGenerator) wavesBalanceSnapshotFromBalanceDiff( + diff addressWavesBalanceDiff) ([]proto.WavesBalanceSnapshot, []proto.LeaseBalanceSnapshot, error) { + var wavesBalances []proto.WavesBalanceSnapshot + var leaseBalances []proto.LeaseBalanceSnapshot + // add miner address to the diff + + for wavesAddress, diffAmount := range diff { + fullBalance, err := sg.stor.balances.newestWavesBalance(wavesAddress.ID()) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to receive sender's waves balance") + } + if diffAmount.balance != 0 { + newBalance := proto.WavesBalanceSnapshot{ + Address: wavesAddress, + Balance: uint64(int64(fullBalance.balance) + diffAmount.balance), + } + wavesBalances = append(wavesBalances, newBalance) + } + if diffAmount.leaseIn != 0 || diffAmount.leaseOut != 0 { + newLeaseBalance := proto.LeaseBalanceSnapshot{ + Address: wavesAddress, + LeaseIn: uint64(fullBalance.leaseIn + diffAmount.leaseIn), + LeaseOut: uint64(fullBalance.leaseOut + diffAmount.leaseOut), + } + leaseBalances = append(leaseBalances, newLeaseBalance) + } + } + return wavesBalances, leaseBalances, nil +} + +func (sg *snapshotGenerator) assetBalanceSnapshotFromBalanceDiff( + diff addressAssetBalanceDiff) ([]proto.AssetBalanceSnapshot, error) { + var assetBalances []proto.AssetBalanceSnapshot + // add miner address to the diff + + for key, diffAmount := range diff { + balance, err := sg.stor.balances.newestAssetBalance(key.address.ID(), key.asset) + if err != nil { + return nil, errors.Wrap(err, "failed to receive sender's waves balance") + } + assetInfo, err := sg.stor.assets.newestAssetInfo(key.asset) + if err != nil { + return nil, errors.Wrap(err, "failed to get newest asset info") + } + + newBalance := proto.AssetBalanceSnapshot{ + Address: key.address, + AssetID: key.asset.Digest(assetInfo.tail), + Balance: uint64(int64(balance) + diffAmount), + } + assetBalances = append(assetBalances, newBalance) + } + return assetBalances, nil +} diff --git a/pkg/state/snapshot_generator_internal_test.go b/pkg/state/snapshot_generator_internal_test.go new file mode 100644 index 000000000..c080a7e31 --- /dev/null +++ b/pkg/state/snapshot_generator_internal_test.go @@ -0,0 +1,949 @@ +package state + +import ( + "encoding/base64" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/wavesplatform/gowaves/pkg/crypto" + "github.com/wavesplatform/gowaves/pkg/proto" + "github.com/wavesplatform/gowaves/pkg/ride" + "github.com/wavesplatform/gowaves/pkg/settings" +) + +func defaultAssetInfoTransfer(tail [12]byte, reissuable bool, + amount int64, issuer crypto.PublicKey, + name string) *assetInfo { + return &assetInfo{ + assetConstInfo: assetConstInfo{ + tail: tail, + issuer: issuer, + decimals: 2, + }, + assetChangeableInfo: assetChangeableInfo{ + quantity: *big.NewInt(amount), + name: name, + description: "description", + lastNameDescChangeHeight: 1, + reissuable: reissuable, + }, + } +} + +func defaultPerformerInfoWithChecker(checkerData txCheckerData) *performerInfo { + return &performerInfo{0, blockID0, proto.WavesAddress{}, new(proto.StateActionsCounter), checkerData} +} + +func customCheckerInfo() *checkerInfo { + defaultBlockInfo := defaultBlockInfo() + return &checkerInfo{ + currentTimestamp: defaultBlockInfo.Timestamp, + parentTimestamp: defaultTimestamp - settings.MainNetSettings.MaxTxTimeBackOffset/2, + blockID: blockID0, + blockVersion: defaultBlockInfo.Version, + height: defaultBlockInfo.Height, + } +} + +func createCheckerCustomTestObjects(t *testing.T, differ *differTestObjects) *checkerTestObjects { + tc, err := newTransactionChecker(proto.NewBlockIDFromSignature(genSig), differ.stor.entities, settings.MainNetSettings) + require.NoError(t, err, "newTransactionChecker() failed") + return &checkerTestObjects{differ.stor, tc, differ.tp, differ.stateActionsCounter} +} + +func txSnapshotsEqual(t *testing.T, expected, actual txSnapshot) { + _ = assert.ElementsMatch(t, expected.regular, actual.regular) + _ = assert.ElementsMatch(t, expected.internal, actual.internal) +} + +func TestDefaultTransferWavesAndAssetSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + + err := to.stor.entities.balances.setWavesBalance(testGlobal.issuerInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + + tx := proto.NewUnsignedTransferWithSig(testGlobal.issuerInfo.pk, + proto.NewOptionalAssetWaves(), proto.NewOptionalAssetWaves(), defaultTimestamp, + defaultAmount*1000*2, uint64(FeeUnit), testGlobal.recipientInfo.Recipient(), nil) + err = tx.Sign(proto.TestNetScheme, testGlobal.issuerInfo.sk) + assert.NoError(t, err, "failed to sign transfer tx") + + ch, err := to.td.createDiffTransferWithSig(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffTransferWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performTransferWithSig(tx, + defaultPerformerInfo(to.stateActionsCounter), nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform transfer tx") + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + Balance: 299700000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + Balance: 200000, + }, + }, + internal: nil, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +// TODO send only txBalanceChanges to perfomer + +func TestDefaultIssueTransactionSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + err := to.stor.entities.balances.setWavesBalance(testGlobal.issuerInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + tx := proto.NewUnsignedIssueWithSig(testGlobal.issuerInfo.pk, + "asset0", "description", defaultQuantity, defaultDecimals, + true, defaultTimestamp, uint64(1*FeeUnit)) + err = tx.Sign(proto.TestNetScheme, testGlobal.issuerInfo.sk) + assert.NoError(t, err, "failed to sign issue tx") + + ch, err := to.td.createDiffIssueWithSig(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffIssueWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performIssueWithSig(tx, + defaultPerformerInfo(to.stateActionsCounter), nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform issue tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.StaticAssetInfoSnapshot{ + AssetID: *tx.ID, + SourceTransactionID: *tx.ID, + IssuerPublicKey: testGlobal.issuerInfo.pk, + Decimals: defaultDecimals, + IsNFT: false}, + &proto.AssetDescriptionSnapshot{ + AssetID: *tx.ID, + AssetName: "asset0", + AssetDescription: "description", + ChangeHeight: 1, + }, + &proto.AssetVolumeSnapshot{ + AssetID: *tx.ID, + TotalQuantity: *big.NewInt(int64(defaultQuantity)), + IsReissuable: true, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + Balance: 299900000, + }, + &proto.AssetBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + AssetID: *tx.ID, + Balance: 1000, + }, + &proto.AssetScriptSnapshot{ + AssetID: *tx.ID, + Script: proto.Script{}, + }, + }, + internal: nil, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultReissueSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + err := to.stor.entities.assets.issueAsset(proto.AssetIDFromDigest(testGlobal.asset0.assetID), + defaultAssetInfoTransfer(proto.DigestTail(testGlobal.asset0.assetID), + true, 1000, testGlobal.issuerInfo.pk, "asset0"), blockID0) + assert.NoError(t, err, "failed to issue asset") + err = to.stor.entities.balances.setWavesBalance(testGlobal.issuerInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + err = to.stor.entities.balances.setAssetBalance(testGlobal.issuerInfo.addr.ID(), + proto.AssetIDFromDigest(testGlobal.asset0.assetID), 1000, blockID0) + assert.NoError(t, err, "failed to set waves balance") + + tx := proto.NewUnsignedReissueWithSig(testGlobal.issuerInfo.pk, + testGlobal.asset0.assetID, 50, + false, defaultTimestamp, uint64(1*FeeUnit)) + err = tx.Sign(proto.TestNetScheme, testGlobal.issuerInfo.sk) + assert.NoError(t, err, "failed to sign reissue tx") + + ch, err := to.td.createDiffReissueWithSig(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffReissueWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performReissueWithSig(tx, + defaultPerformerInfo(to.stateActionsCounter), nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform reissue tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + Balance: 299900000, + }, + &proto.AssetBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + AssetID: testGlobal.asset0.assetID, + Balance: 1050, + }, + &proto.AssetVolumeSnapshot{ + AssetID: testGlobal.asset0.assetID, + TotalQuantity: *big.NewInt(int64(defaultQuantity + 50)), + IsReissuable: false, + }, + }, + internal: nil, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultBurnSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + err := to.stor.entities.assets.issueAsset(proto.AssetIDFromDigest(testGlobal.asset0.assetID), + defaultAssetInfoTransfer(proto.DigestTail(testGlobal.asset0.assetID), + false, 950, testGlobal.issuerInfo.pk, "asset0"), blockID0) + + assert.NoError(t, err, "failed to issue asset") + err = to.stor.entities.balances.setWavesBalance(testGlobal.issuerInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + err = to.stor.entities.balances.setAssetBalance(testGlobal.issuerInfo.addr.ID(), + proto.AssetIDFromDigest(testGlobal.asset0.assetID), 1000, blockID0) + assert.NoError(t, err, "failed to set asset balance") + + tx := proto.NewUnsignedBurnWithSig(testGlobal.issuerInfo.pk, + testGlobal.asset0.assetID, 50, defaultTimestamp, uint64(1*FeeUnit)) + err = tx.Sign(proto.TestNetScheme, testGlobal.issuerInfo.sk) + assert.NoError(t, err, "failed to sign burn tx") + ch, err := to.td.createDiffBurnWithSig(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffBurnWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performBurnWithSig(tx, + defaultPerformerInfo(to.stateActionsCounter), nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + Balance: 299900000, + }, + &proto.AssetBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + AssetID: testGlobal.asset0.assetID, + Balance: 950, + }, + &proto.AssetVolumeSnapshot{ + AssetID: testGlobal.asset0.assetID, + TotalQuantity: *big.NewInt(int64(defaultQuantity - 100)), + IsReissuable: false, + }, + }, + internal: nil, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultExchangeTransaction(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + // issue assets + err := to.stor.entities.assets.issueAsset(proto.AssetIDFromDigest(testGlobal.asset0.assetID), + defaultAssetInfoTransfer(proto.DigestTail(testGlobal.asset0.assetID), + true, 1000, testGlobal.senderInfo.pk, "asset0"), blockID0) + assert.NoError(t, err, "failed to issue asset") + err = to.stor.entities.assets.issueAsset(proto.AssetIDFromDigest(testGlobal.asset1.assetID), + defaultAssetInfoTransfer(proto.DigestTail(testGlobal.asset1.assetID), + true, 1000, testGlobal.recipientInfo.pk, "asset1"), blockID0) + assert.NoError(t, err, "failed to issue asset") + + // set waves balance for the seller and the buyer + err = to.stor.entities.balances.setWavesBalance(testGlobal.senderInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + err = to.stor.entities.balances.setWavesBalance(testGlobal.recipientInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 2000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + + // set waves balance for the matcher account + err = to.stor.entities.balances.setWavesBalance(testGlobal.matcherInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 3000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + + // set asset balance for the seller and the buyer + err = to.stor.entities.balances.setAssetBalance(testGlobal.senderInfo.addr.ID(), + proto.AssetIDFromDigest(testGlobal.asset1.assetID), 500, blockID0) + assert.NoError(t, err, "failed to set asset balance") + err = to.stor.entities.balances.setAssetBalance(testGlobal.recipientInfo.addr.ID(), + proto.AssetIDFromDigest(testGlobal.asset0.assetID), 600, blockID0) + assert.NoError(t, err, "failed to set asset balance") + + bo := proto.NewUnsignedOrderV1(testGlobal.senderInfo.pk, testGlobal.matcherInfo.pk, + *testGlobal.asset0.asset, *testGlobal.asset1.asset, proto.Buy, + 10e8, 10, 0, 0, 3) + err = bo.Sign(proto.TestNetScheme, testGlobal.senderInfo.sk) + assert.NoError(t, err, "bo.Sign() failed") + so := proto.NewUnsignedOrderV1(testGlobal.recipientInfo.pk, testGlobal.matcherInfo.pk, + *testGlobal.asset0.asset, *testGlobal.asset1.asset, proto.Sell, + 10e8, 10, 0, 0, 3) + err = so.Sign(proto.TestNetScheme, testGlobal.recipientInfo.sk) + assert.NoError(t, err, "so.Sign() failed") + tx := proto.NewUnsignedExchangeWithSig(bo, so, bo.Price, bo.Amount, 1, 2, uint64(1*FeeUnit), defaultTimestamp) + err = tx.Sign(proto.TestNetScheme, testGlobal.matcherInfo.sk) + + assert.NoError(t, err, "failed to sign burn tx") + ch, err := to.td.createDiffExchange(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffBurnWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performExchange(tx, defaultPerformerInfo(to.stateActionsCounter), + nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299999999, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + Balance: 599999998, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.matcherInfo.addr, + Balance: 899900003, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.AssetBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + AssetID: testGlobal.asset0.assetID, + Balance: 10, + }, + &proto.AssetBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + AssetID: testGlobal.asset0.assetID, + Balance: 590, + }, + &proto.AssetBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + AssetID: testGlobal.asset1.assetID, + Balance: 400, + }, + &proto.AssetBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + AssetID: testGlobal.asset1.assetID, + Balance: 100, + }, + &proto.FilledVolumeFeeSnapshot{ + OrderID: *bo.ID, + FilledVolume: 10, + FilledFee: 1, + }, + &proto.FilledVolumeFeeSnapshot{ + OrderID: *so.ID, + FilledVolume: 10, + FilledFee: 2, + }, + }, + internal: nil, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultLeaseSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + + err := to.stor.entities.balances.setWavesBalance(testGlobal.senderInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + + tx := proto.NewUnsignedLeaseWithSig(testGlobal.senderInfo.pk, testGlobal.recipientInfo.Recipient(), + 50, uint64(1*FeeUnit), defaultTimestamp) + err = tx.Sign(proto.TestNetScheme, testGlobal.senderInfo.sk) + assert.NoError(t, err, "failed to sign burn tx") + ch, err := to.td.createDiffLeaseWithSig(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffBurnWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performLeaseWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), + nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &proto.LeaseStateSnapshot{ + LeaseID: *tx.ID, + Status: &proto.LeaseStateStatusActive{ + Amount: 50, + Sender: testGlobal.senderInfo.addr, + Recipient: testGlobal.recipientInfo.addr, + }, + }, + &proto.LeaseBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + LeaseIn: 0, + LeaseOut: 50, + }, + &proto.LeaseBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + LeaseIn: 50, + LeaseOut: 0, + }, + }, + internal: []internalSnapshot{ + &InternalLeaseStateActiveInfoSnapshot{ + LeaseID: *tx.ID, + OriginHeight: 0, + OriginTransactionID: tx.ID, + }, + }, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultLeaseCancelSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + + leaseID := testGlobal.asset0.assetID + leasing := &leasing{ + Sender: testGlobal.senderInfo.addr, + Recipient: testGlobal.recipientInfo.addr, + Amount: 50, + OriginHeight: 1, + Status: LeaseActive, + OriginTransactionID: &leaseID, + } + err := to.stor.entities.leases.addLeasing(leaseID, leasing, blockID0) + assert.NoError(t, err, "failed to add leasing") + + err = to.stor.entities.balances.setWavesBalance(testGlobal.senderInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3, + leaseIn: 0, leaseOut: 50}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + err = to.stor.entities.balances.setWavesBalance(testGlobal.recipientInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3, leaseIn: 50, leaseOut: 0}}, + blockID0) + assert.NoError(t, err, "failed to set waves balance") + + tx := proto.NewUnsignedLeaseCancelWithSig(testGlobal.senderInfo.pk, leaseID, uint64(1*FeeUnit), defaultTimestamp) + err = tx.Sign(proto.TestNetScheme, testGlobal.senderInfo.sk) + assert.NoError(t, err, "failed to sign burn tx") + ch, err := to.td.createDiffLeaseCancelWithSig(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffBurnWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performLeaseCancelWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), + nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &proto.LeaseStateSnapshot{ + LeaseID: leaseID, + Status: &proto.LeaseStatusCancelled{}, + }, + &proto.LeaseBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + LeaseIn: 0, + LeaseOut: 0, + }, + &proto.LeaseBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + LeaseIn: 0, + LeaseOut: 0, + }, + }, + internal: []internalSnapshot{ + &InternalLeaseStateCancelInfoSnapshot{ + LeaseID: leaseID, + CancelHeight: 0, + CancelTransactionID: tx.ID, + }, + }, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultCreateAliasSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + err := to.stor.entities.balances.setWavesBalance(testGlobal.senderInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + + alias := proto.NewAlias(proto.TestNetScheme, "aliasForSender") + tx := proto.NewUnsignedCreateAliasWithSig(testGlobal.senderInfo.pk, *alias, uint64(1*FeeUnit), defaultTimestamp) + err = tx.Sign(proto.TestNetScheme, testGlobal.senderInfo.sk) + assert.NoError(t, err, "failed to sign burn tx") + ch, err := to.td.createDiffCreateAliasWithSig(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffBurnWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performCreateAliasWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), + nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &proto.AliasSnapshot{ + Address: testGlobal.senderInfo.addr, + Alias: *proto.NewAlias(proto.TestNetScheme, "aliasForSender"), + }, + }, + internal: nil, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultDataSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + err := to.stor.entities.balances.setWavesBalance( + testGlobal.senderInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, + blockID0) + assert.NoError(t, err, "failed to set waves balance") + + tx := proto.NewUnsignedDataWithProofs(1, testGlobal.senderInfo.pk, uint64(1*FeeUnit), defaultTimestamp) + stringEntry := &proto.StringDataEntry{Key: "key_str", Value: "value_str"} + intEntry := &proto.IntegerDataEntry{Key: "key_int", Value: 2} + err = tx.AppendEntry(stringEntry) + require.NoError(t, err) + err = tx.AppendEntry(intEntry) + require.NoError(t, err) + + err = tx.Sign(proto.TestNetScheme, testGlobal.senderInfo.sk) + assert.NoError(t, err, "failed to sign burn tx") + ch, err := to.td.createDiffDataWithProofs(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffBurnWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performDataWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), + nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &proto.DataEntriesSnapshot{ + Address: testGlobal.senderInfo.addr, + DataEntries: []proto.DataEntry{&proto.StringDataEntry{Key: "key_str", Value: "value_str"}, + &proto.IntegerDataEntry{Key: "key_int", Value: 2}}, + }, + }, + internal: nil, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultSponsorshipSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + err := to.stor.entities.balances.setWavesBalance(testGlobal.senderInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + + tx := proto.NewUnsignedSponsorshipWithProofs(1, testGlobal.senderInfo.pk, + testGlobal.asset0.assetID, uint64(5*FeeUnit), uint64(1*FeeUnit), defaultTimestamp) + + err = tx.Sign(proto.TestNetScheme, testGlobal.senderInfo.sk) + assert.NoError(t, err, "failed to sign burn tx") + ch, err := to.td.createDiffSponsorshipWithProofs(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffBurnWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performSponsorshipWithProofs(tx, + defaultPerformerInfo(to.stateActionsCounter), nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &proto.SponsorshipSnapshot{ + AssetID: testGlobal.asset0.assetID, + MinSponsoredFee: 500000, + }, + }, + internal: nil, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultSetDappScriptSnapshot(t *testing.T) { + /* + {-# STDLIB_VERSION 5 #-} + {-# CONTENT_TYPE DAPP #-} + {-# SCRIPT_TYPE ACCOUNT #-} + + @Callable(i) + func call() = { + [ + BooleanEntry("bool", true), + IntegerEntry("int", 1), + IntegerEntry("int", 1), + IntegerEntry("int2", 2), + StringEntry("str", "1"), + StringEntry("str", "1"), + StringEntry("str2", "2") + ] + } + */ + script := "AAIFAAAAAAAAAAQIAhIAAAAAAAAAAAEAAAABaQEAAAAEY2Fsb" + + "AAAAAAJAARMAAAAAgkBAAAADEJvb2xlYW5FbnRyeQAAAAICAAAABGJvb2wGCQAE" + + "TAAAAAIJAQAAAAxJbnRlZ2VyRW50cnkAAAACAgAAAANpbnQAAAAAAAAAAAEJAARMAAAA" + + "AgkBAAAADEludGVnZXJFbnRyeQAAAAICAAAAA2ludAAAAAAAAAAAAQkABEwAAAACCQEAAA" + + "AMSW50ZWdlckVudHJ5AAAAAgIAAAAEaW50MgAAAAAAAAAAAgkABEwAAAACCQEAAAALU3RyaW5nRW" + + "50cnkAAAACAgAAAANzdHICAAAAATEJAARMAAAAAgkBAAAAC1N0cmluZ0VudHJ5AAAAAgIAAAADc3RyAgAAAA" + + "ExCQAETAAAAAIJAQAAAAtTdHJpbmdFbnRyeQAAAAICAAAABHN0cjICAAAAATIFAAAAA25pbAAAAACkN9Gf" + scriptsBytes, err := base64.StdEncoding.DecodeString(script) + + assert.NoError(t, err, "failed to set decode base64 script") + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + to.stor.activateFeature(t, int16(settings.RideV5)) + err = to.stor.entities.balances.setWavesBalance(testGlobal.senderInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + + tx := proto.NewUnsignedSetScriptWithProofs(1, testGlobal.senderInfo.pk, + scriptsBytes, uint64(1*FeeUnit), defaultTimestamp) + + err = tx.Sign(proto.TestNetScheme, testGlobal.senderInfo.sk) + assert.NoError(t, err, "failed to sign set script tx") + + co := createCheckerCustomTestObjects(t, to) + co.stor = to.stor + checkerData, err := co.tc.checkSetScriptWithProofs(tx, checkerInfo) + assert.NoError(t, err, "failed to check set script tx") + + ch, err := to.td.createDiffSetScriptWithProofs(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffBurnWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performSetScriptWithProofs(tx, + defaultPerformerInfoWithChecker(checkerData), nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &proto.AccountScriptSnapshot{ + SenderPublicKey: testGlobal.senderInfo.pk, + Script: scriptsBytes, + VerifierComplexity: 0, + }, + }, + internal: []internalSnapshot{ + &InternalDAppComplexitySnapshot{ + ScriptAddress: testGlobal.senderInfo.addr, + Estimation: ride.TreeEstimation{Estimation: 36, Verifier: 0, Functions: map[string]int{"call": 36}}, + ScriptIsEmpty: false, + }, + }, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultSetScriptSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + err := to.stor.entities.balances.setWavesBalance(testGlobal.senderInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + + tx := proto.NewUnsignedSetScriptWithProofs(1, testGlobal.senderInfo.pk, + testGlobal.scriptBytes, uint64(1*FeeUnit), defaultTimestamp) + + err = tx.Sign(proto.TestNetScheme, testGlobal.senderInfo.sk) + assert.NoError(t, err, "failed to sign set script tx") + + co := createCheckerCustomTestObjects(t, to) + co.stor = to.stor + checkerData, err := co.tc.checkSetScriptWithProofs(tx, checkerInfo) + assert.NoError(t, err, "failed to check set script tx") + + ch, err := to.td.createDiffSetScriptWithProofs(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffBurnWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performSetScriptWithProofs(tx, + defaultPerformerInfoWithChecker(checkerData), nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &proto.AccountScriptSnapshot{ + SenderPublicKey: testGlobal.senderInfo.pk, + Script: testGlobal.scriptBytes, + VerifierComplexity: 340, + }, + }, + internal: []internalSnapshot{ + &InternalDAppComplexitySnapshot{ + ScriptAddress: testGlobal.senderInfo.addr, + Estimation: ride.TreeEstimation{Estimation: 340, Verifier: 340}, + }, + }, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultSetEmptyScriptSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + err := to.stor.entities.balances.setWavesBalance(testGlobal.senderInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + + tx := proto.NewUnsignedSetScriptWithProofs(1, testGlobal.senderInfo.pk, + nil, uint64(1*FeeUnit), defaultTimestamp) + + err = tx.Sign(proto.TestNetScheme, testGlobal.senderInfo.sk) + assert.NoError(t, err, "failed to sign set script tx") + + co := createCheckerCustomTestObjects(t, to) + co.stor = to.stor + checkerData, err := co.tc.checkSetScriptWithProofs(tx, checkerInfo) + assert.NoError(t, err, "failed to check set script tx") + + ch, err := to.td.createDiffSetScriptWithProofs(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffBurnWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performSetScriptWithProofs(tx, + defaultPerformerInfoWithChecker(checkerData), nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &proto.AccountScriptSnapshot{ + SenderPublicKey: testGlobal.senderInfo.pk, + Script: nil, + VerifierComplexity: 0, + }, + }, + internal: []internalSnapshot{ + &InternalDAppComplexitySnapshot{ + ScriptAddress: testGlobal.senderInfo.addr, + Estimation: ride.TreeEstimation{Estimation: 0, Verifier: 0}, + ScriptIsEmpty: true, + }, + }, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultSetAssetScriptSnapshot(t *testing.T) { + checkerInfo := customCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + var err error + err = to.stor.entities.balances.setWavesBalance(testGlobal.senderInfo.addr.ID(), + wavesValue{profile: balanceProfile{balance: 1000 * FeeUnit * 3}}, blockID0) + assert.NoError(t, err, "failed to set waves balance") + + err = to.stor.entities.assets.issueAsset(proto.AssetIDFromDigest(testGlobal.asset0.assetID), + defaultAssetInfoTransfer(proto.DigestTail(testGlobal.asset0.assetID), + true, 1000, testGlobal.senderInfo.pk, "asset0"), blockID0) + assert.NoError(t, err, "failed to issue asset") + + err = to.stor.entities.scriptsStorage.setAssetScript(testGlobal.asset0.assetID, + testGlobal.scriptBytes, blockID0) + assert.NoError(t, err, "failed to issue asset") + + tx := proto.NewUnsignedSetAssetScriptWithProofs(1, testGlobal.senderInfo.pk, + testGlobal.asset0.assetID, testGlobal.scriptBytes, uint64(1*FeeUnit), defaultTimestamp) + + err = tx.Sign(proto.TestNetScheme, testGlobal.senderInfo.sk) + assert.NoError(t, err, "failed to sign burn tx") + + co := createCheckerCustomTestObjects(t, to) + co.stor = to.stor + checkerData, err := co.tc.checkSetAssetScriptWithProofs(tx, checkerInfo) + assert.NoError(t, err, "failed to check set script tx") + + ch, err := to.td.createDiffSetAssetScriptWithProofs(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffBurnWithSig() failed") + applicationRes := &applicationResult{changes: ch, checkerData: txCheckerData{}} + transactionSnapshot, err := to.tp.performSetAssetScriptWithProofs(tx, + defaultPerformerInfoWithChecker(checkerData), nil, applicationRes.changes.diff) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := txSnapshot{ + regular: []proto.AtomicSnapshot{ + &proto.WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &proto.WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &proto.AssetScriptSnapshot{ + AssetID: testGlobal.asset0.assetID, + Script: testGlobal.scriptBytes, + }, + }, + internal: []internalSnapshot{ + &InternalAssetScriptComplexitySnapshot{ + AssetID: testGlobal.asset0.assetID, + Estimation: ride.TreeEstimation{ + Estimation: 340, + Verifier: 340, + Functions: nil, + }, + ScriptIsEmpty: false, + }, + }, + } + + txSnapshotsEqual(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} diff --git a/pkg/state/state.go b/pkg/state/state.go index cf44315af..6af6dae41 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -461,7 +461,12 @@ func newStateManager(dataDir string, amend bool, params StateParams, settings *s } // Set fields which depend on state. // Consensus validator is needed to check block headers. - appender, err := newTxAppender(state, rw, stor, settings, stateDB, atx) + snapshotApplier := newBlockSnapshotsApplier( + nil, + newSnapshotApplierStorages(stor), + ) + snapshotGen := newSnapshotGenerator(stor, settings.AddressSchemeCharacter) + appender, err := newTxAppender(state, rw, stor, settings, stateDB, atx, &snapshotApplier, &snapshotGen) if err != nil { return nil, wrapErr(Other, err) } @@ -819,11 +824,11 @@ func (s *stateManager) newestAssetBalance(addr proto.AddressID, asset proto.Asse return balance, nil } -func (s *stateManager) newestWavesBalanceProfile(addr proto.AddressID) (*balanceProfile, error) { +func (s *stateManager) newestWavesBalanceProfile(addr proto.AddressID) (balanceProfile, error) { // Retrieve the latest balance from historyStorage. profile, err := s.stor.balances.newestWavesBalance(addr) if err != nil { - return nil, err + return balanceProfile{}, err } // Retrieve the latest balance diff as for the moment of this function call. key := wavesBalanceKey{address: addr} @@ -833,11 +838,11 @@ func (s *stateManager) newestWavesBalanceProfile(addr proto.AddressID) (*balance return profile, nil } else if err != nil { // Something weird happened. - return nil, err + return balanceProfile{}, err } newProfile, err := diff.applyTo(profile) if err != nil { - return nil, errors.Errorf("given account has negative balance at this point: %v", err) + return balanceProfile{}, errors.Errorf("given account has negative balance at this point: %v", err) } return newProfile, nil } @@ -1280,6 +1285,7 @@ func (s *stateManager) needToCancelLeases(blockchainHeight uint64) (bool, error) } } +// TODO what to do with stolen aliases in snapshots? func (s *stateManager) blockchainHeightAction(blockchainHeight uint64, lastBlock, nextBlock proto.BlockID) error { cancelLeases, err := s.needToCancelLeases(blockchainHeight) if err != nil { @@ -1529,6 +1535,7 @@ func (s *stateManager) addBlocks() (*proto.Block, error) { if err := s.appender.applyAllDiffs(); err != nil { return nil, err } + // Retrieve and store state hashes for each of new blocks. if err := s.stor.handleStateHashes(height, ids); err != nil { return nil, wrapErr(ModificationError, err) diff --git a/pkg/state/transaction_checker.go b/pkg/state/transaction_checker.go index bd35ef47f..f099573d8 100644 --- a/pkg/state/transaction_checker.go +++ b/pkg/state/transaction_checker.go @@ -215,6 +215,7 @@ func (tc *transactionChecker) checkScript( if err != nil { return ride.TreeEstimation{}, errs.Extend(err, "failed to estimate script complexity") } + if scErr := tc.checkScriptComplexity(tree.LibVersion, est, tree.IsDApp(), reducedVerifierComplexity); scErr != nil { return ride.TreeEstimation{}, errors.Wrap(scErr, "failed to check script complexity") } @@ -763,7 +764,8 @@ func (tc *transactionChecker) orderScriptedAccount(order proto.Order) (bool, err } func (tc *transactionChecker) checkEnoughVolume(order proto.Order, newFee, newAmount uint64) error { - orderId, err := order.GetID() + orderID, err := order.GetID() + if err != nil { return err } @@ -775,17 +777,13 @@ func (tc *transactionChecker) checkEnoughVolume(order proto.Order, newFee, newAm if newFee > fullFee { return errors.New("current fee exceeds total order fee") } - filledAmount, err := tc.stor.ordersVolumes.newestFilledAmount(orderId) + filledAmount, filledFee, err := tc.stor.ordersVolumes.newestFilled(orderID) if err != nil { return err } if fullAmount-newAmount < filledAmount { return errors.New("order amount volume is overflowed") } - filledFee, err := tc.stor.ordersVolumes.newestFilledFee(orderId) - if err != nil { - return err - } if fullFee-newFee < filledFee { return errors.New("order fee volume is overflowed") } diff --git a/pkg/state/transaction_checker_test.go b/pkg/state/transaction_checker_test.go index 7ec4c88b1..44bc2d8ed 100644 --- a/pkg/state/transaction_checker_test.go +++ b/pkg/state/transaction_checker_test.go @@ -22,18 +22,30 @@ var ( ) type checkerTestObjects struct { - stor *testStorageObjects - tc *transactionChecker - tp *transactionPerformer + stor *testStorageObjects + tc *transactionChecker + tp *transactionPerformer + stateActionsCounter *proto.StateActionsCounter } -func createCheckerTestObjects(t *testing.T) *checkerTestObjects { +func createCheckerTestObjects(t *testing.T, checkerInfo *checkerInfo) *checkerTestObjects { stor := createStorageObjects(t, true) tc, err := newTransactionChecker(proto.NewBlockIDFromSignature(genSig), stor.entities, settings.MainNetSettings) require.NoError(t, err, "newTransactionChecker() failed") - tp, err := newTransactionPerformer(stor.entities, settings.MainNetSettings) - require.NoError(t, err, "newTransactionPerformer() failed") - return &checkerTestObjects{stor, tc, tp} + + actionsCounter := new(proto.StateActionsCounter) + snapshotApplier := newBlockSnapshotsApplier( + newBlockSnapshotsApplierInfo( + checkerInfo, + settings.MainNetSettings.AddressSchemeCharacter, + actionsCounter, + ), + newSnapshotApplierStorages(stor.entities), + ) + snapshotGen := newSnapshotGenerator(stor.entities, settings.MainNetSettings.AddressSchemeCharacter) + + tp := newTransactionPerformer(stor.entities, settings.MainNetSettings, &snapshotGen, &snapshotApplier) + return &checkerTestObjects{stor, tc, tp, actionsCounter} } func defaultCheckerInfo() *checkerInfo { @@ -47,10 +59,11 @@ func defaultCheckerInfo() *checkerInfo { } func TestCheckGenesis(t *testing.T) { - to := createCheckerTestObjects(t) - tx := createGenesis() info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) + + tx := createGenesis() _, err := to.tc.checkGenesis(tx, info) assert.EqualError(t, err, "genesis transaction inside of non-genesis block") @@ -69,10 +82,11 @@ func TestCheckGenesis(t *testing.T) { } func TestCheckPayment(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createPayment(t) - info := defaultCheckerInfo() + info.height = settings.MainNetSettings.BlockVersion3AfterHeight _, err := to.tc.checkPayment(tx, info) assert.Error(t, err, "checkPayment accepted payment tx after Block v3 height") @@ -86,10 +100,10 @@ func TestCheckPayment(t *testing.T) { } func TestCheckTransferWithSig(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createTransferWithSig(t) - info := defaultCheckerInfo() assetId := tx.FeeAsset.ID @@ -124,10 +138,10 @@ func TestCheckTransferWithSig(t *testing.T) { } func TestCheckTransferWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createTransferWithProofs(t) - info := defaultCheckerInfo() assetId := tx.FeeAsset.ID @@ -169,7 +183,8 @@ func TestCheckTransferWithProofs(t *testing.T) { } func TestCheckIsValidUtf8(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) err := to.tc.isValidUtf8("just a normal string") assert.NoError(t, err) @@ -187,10 +202,11 @@ func TestCheckIsValidUtf8(t *testing.T) { } func TestCheckIssueWithSig(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createIssueWithSig(t, 1000) - info := defaultCheckerInfo() + _, err := to.tc.checkIssueWithSig(tx, info) assert.NoError(t, err, "checkIssueWithSig failed with valid issue tx") @@ -200,10 +216,11 @@ func TestCheckIssueWithSig(t *testing.T) { } func TestCheckIssueWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createIssueWithProofs(t, 1000) - info := defaultCheckerInfo() + to.stor.addBlock(t, blockID0) _, err := to.tc.checkIssueWithProofs(tx, info) @@ -215,13 +232,14 @@ func TestCheckIssueWithProofs(t *testing.T) { } func TestCheckReissueWithSig(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createReissueWithSig(t, 1000) tx.SenderPK = assetInfo.issuer - info := defaultCheckerInfo() + info.currentTimestamp = settings.MainNetSettings.ReissueBugWindowTimeEnd + 1 _, err := to.tc.checkReissueWithSig(tx, info) assert.NoError(t, err, "checkReissueWithSig failed with valid reissue tx") @@ -246,7 +264,7 @@ func TestCheckReissueWithSig(t *testing.T) { tx.SenderPK = assetInfo.issuer tx.Reissuable = false - err = to.tp.performReissueWithSig(tx, defaultPerformerInfo()) + _, err = to.tp.performReissueWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performReissueWithSig failed") to.stor.addBlock(t, blockID0) to.stor.flush(t) @@ -257,13 +275,14 @@ func TestCheckReissueWithSig(t *testing.T) { } func TestCheckReissueWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createReissueWithProofs(t, 1000) tx.SenderPK = assetInfo.issuer - info := defaultCheckerInfo() + info.currentTimestamp = settings.MainNetSettings.ReissueBugWindowTimeEnd + 1 _, err := to.tc.checkReissueWithProofs(tx, info) @@ -294,7 +313,7 @@ func TestCheckReissueWithProofs(t *testing.T) { tx.SenderPK = assetInfo.issuer tx.Reissuable = false - err = to.tp.performReissueWithProofs(tx, defaultPerformerInfo()) + _, err = to.tp.performReissueWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performReissueWithProofs failed") to.stor.addBlock(t, blockID0) to.stor.flush(t) @@ -305,12 +324,12 @@ func TestCheckReissueWithProofs(t *testing.T) { } func TestCheckBurnWithSig(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createBurnWithSig(t) tx.SenderPK = assetInfo.issuer - info := defaultCheckerInfo() _, err := to.tc.checkBurnWithSig(tx, info) assert.NoError(t, err, "checkBurnWithSig failed with valid burn tx") @@ -339,12 +358,12 @@ func TestCheckBurnWithSig(t *testing.T) { } func TestCheckBurnWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createBurnWithProofs(t) tx.SenderPK = assetInfo.issuer - info := defaultCheckerInfo() _, err := to.tc.checkBurnWithProofs(tx, info) assert.Error(t, err, "checkBurnWithProofs did not fail prior to SmartAccounts activation") @@ -378,10 +397,11 @@ func TestCheckBurnWithProofs(t *testing.T) { } func TestCheckExchangeWithSig(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createExchangeWithSig(t) - info := defaultCheckerInfo() + _, err := to.tc.checkExchangeWithSig(tx, info) assert.Error(t, err, "checkExchangeWithSig did not fail with exchange with unknown assets") @@ -444,10 +464,11 @@ func TestCheckExchangeWithSig(t *testing.T) { } func TestCheckExchangeWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) txOV2 := createExchangeWithProofs(t) - info := defaultCheckerInfo() + _, err := to.tc.checkExchangeWithProofs(txOV2, info) assert.Error(t, err, "checkExchangeWithProofs did not fail with exchange with unknown assets") @@ -539,10 +560,10 @@ func TestCheckExchangeWithProofs(t *testing.T) { } func TestCheckUnorderedExchangeV2WithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createUnorderedExchangeWithProofs(t, 2) - info := defaultCheckerInfo() to.stor.createAsset(t, testGlobal.asset0.asset.ID) to.stor.createAsset(t, testGlobal.asset1.asset.ID) @@ -558,10 +579,10 @@ func TestCheckUnorderedExchangeV2WithProofs(t *testing.T) { } func TestCheckUnorderedExchangeV3WithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createUnorderedExchangeWithProofs(t, 3) - info := defaultCheckerInfo() to.stor.createAsset(t, testGlobal.asset0.asset.ID) to.stor.createAsset(t, testGlobal.asset1.asset.ID) @@ -577,10 +598,11 @@ func TestCheckUnorderedExchangeV3WithProofs(t *testing.T) { } func TestCheckLeaseWithSig(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createLeaseWithSig(t) - info := defaultCheckerInfo() + tx.Recipient = proto.NewRecipientFromAddress(testGlobal.senderInfo.addr) _, err := to.tc.checkLeaseWithSig(tx, info) assert.Error(t, err, "checkLeaseWithSig did not fail when leasing to self") @@ -591,10 +613,11 @@ func TestCheckLeaseWithSig(t *testing.T) { } func TestCheckLeaseWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createLeaseWithProofs(t) - info := defaultCheckerInfo() + tx.Recipient = proto.NewRecipientFromAddress(testGlobal.senderInfo.addr) _, err := to.tc.checkLeaseWithProofs(tx, info) assert.Error(t, err, "checkLeaseWithProofs did not fail when leasing to self") @@ -611,10 +634,11 @@ func TestCheckLeaseWithProofs(t *testing.T) { } func TestCheckLeaseCancelWithSig(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) leaseTx := createLeaseWithSig(t) - info := defaultCheckerInfo() + info.currentTimestamp = settings.MainNetSettings.AllowMultipleLeaseCancelUntilTime + 1 tx := createLeaseCancelWithSig(t, *leaseTx.ID) @@ -622,7 +646,7 @@ func TestCheckLeaseCancelWithSig(t *testing.T) { assert.Error(t, err, "checkLeaseCancelWithSig did not fail when cancelling nonexistent lease") to.stor.addBlock(t, blockID0) - err = to.tp.performLeaseWithSig(leaseTx, defaultPerformerInfo()) + _, err = to.tp.performLeaseWithSig(leaseTx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performLeaseWithSig failed") to.stor.flush(t) @@ -639,10 +663,11 @@ func TestCheckLeaseCancelWithSig(t *testing.T) { } func TestCheckLeaseCancelWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) leaseTx := createLeaseWithProofs(t) - info := defaultCheckerInfo() + info.currentTimestamp = settings.MainNetSettings.AllowMultipleLeaseCancelUntilTime + 1 tx := createLeaseCancelWithProofs(t, *leaseTx.ID) @@ -650,7 +675,7 @@ func TestCheckLeaseCancelWithProofs(t *testing.T) { assert.Error(t, err, "checkLeaseCancelWithProofs did not fail when cancelling nonexistent lease") to.stor.addBlock(t, blockID0) - err = to.tp.performLeaseWithProofs(leaseTx, defaultPerformerInfo()) + _, err = to.tp.performLeaseWithProofs(leaseTx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performLeaseWithProofs failed") to.stor.flush(t) @@ -666,7 +691,7 @@ func TestCheckLeaseCancelWithProofs(t *testing.T) { _, err = to.tc.checkLeaseCancelWithProofs(tx, info) assert.NoError(t, err, "checkLeaseCancelWithProofs failed with valid leaseCancel tx") - err = to.tp.performLeaseCancelWithProofs(tx, defaultPerformerInfo()) + _, err = to.tp.performLeaseCancelWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performLeaseCancelWithProofs() failed") _, err = to.tc.checkLeaseCancelWithProofs(tx, info) @@ -674,16 +699,16 @@ func TestCheckLeaseCancelWithProofs(t *testing.T) { } func TestCheckCreateAliasWithSig(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createCreateAliasWithSig(t) - info := defaultCheckerInfo() _, err := to.tc.checkCreateAliasWithSig(tx, info) assert.NoError(t, err, "checkCreateAliasWithSig failed with valid createAlias tx") to.stor.addBlock(t, blockID0) - err = to.tp.performCreateAliasWithSig(tx, defaultPerformerInfo()) + _, err = to.tp.performCreateAliasWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performCreateAliasWithSig failed") to.stor.flush(t) @@ -697,10 +722,10 @@ func TestCheckCreateAliasWithSig(t *testing.T) { } func TestCheckCreateAliasWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createCreateAliasWithProofs(t) - info := defaultCheckerInfo() _, err := to.tc.checkCreateAliasWithProofs(tx, info) assert.Error(t, err, "checkCreateAliasWithProofs did not fail prior to SmartAccounts activation") @@ -711,7 +736,7 @@ func TestCheckCreateAliasWithProofs(t *testing.T) { assert.NoError(t, err, "checkCreateAliasWithProofs failed with valid createAlias tx") to.stor.addBlock(t, blockID0) - err = to.tp.performCreateAliasWithProofs(tx, defaultPerformerInfo()) + _, err = to.tp.performCreateAliasWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performCreateAliasWithProofs failed") to.stor.flush(t) @@ -725,12 +750,12 @@ func TestCheckCreateAliasWithProofs(t *testing.T) { } func TestCheckMassTransferWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) entriesNum := 50 entries := generateMassTransferEntries(t, entriesNum) tx := createMassTransferWithProofs(t, entries) - info := defaultCheckerInfo() _, err := to.tc.checkMassTransferWithProofs(tx, info) assert.Error(t, err, "checkMassTransferWithProofs did not fail prior to feature activation") @@ -756,10 +781,10 @@ func TestCheckMassTransferWithProofs(t *testing.T) { } func TestCheckDataWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createDataWithProofs(t, 1) - info := defaultCheckerInfo() _, err := to.tc.checkDataWithProofs(tx, info) assert.Error(t, err, "checkDataWithProofs did not fail prior to feature activation") @@ -802,12 +827,12 @@ func TestCheckDataWithProofs(t *testing.T) { } func TestCheckSponsorshipWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createSponsorshipWithProofs(t, 1000) assetInfo := to.stor.createAsset(t, tx.AssetID) tx.SenderPK = assetInfo.issuer - info := defaultCheckerInfo() _, err := to.tc.checkSponsorshipWithProofs(tx, info) assert.Error(t, err, "checkSponsorshipWithProofs did not fail prior to feature activation") @@ -845,10 +870,10 @@ func TestCheckSponsorshipWithProofs(t *testing.T) { } func TestCheckSetScriptWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createSetScriptWithProofs(t) - info := defaultCheckerInfo() // Activate sponsorship. to.stor.activateSponsorship(t) @@ -1343,10 +1368,10 @@ func TestCheckSetScriptWithProofsCheckDAppCallables(t *testing.T) { } func TestCheckSetAssetScriptWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createSetAssetScriptWithProofs(t) - info := defaultCheckerInfo() assetInfo := defaultAssetInfo(proto.DigestTail(tx.AssetID), true) assetInfo.issuer = tx.SenderPK @@ -1357,7 +1382,7 @@ func TestCheckSetAssetScriptWithProofs(t *testing.T) { assert.Error(t, err, "checkSetAssetScriptWithProofs did not fail with non-smart asset") // Make it smart. - err = to.stor.entities.scriptsStorage.setAssetScript(tx.AssetID, tx.Script, tx.SenderPK, blockID0) + err = to.stor.entities.scriptsStorage.setAssetScript(tx.AssetID, tx.Script, blockID0) assert.NoError(t, err, "setAssetScript failed") // Now should pass. @@ -1379,13 +1404,14 @@ func TestCheckSetAssetScriptWithProofs(t *testing.T) { } func TestCheckInvokeScriptWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) payments := []proto.ScriptPayment{ {Amount: 1, Asset: *testGlobal.asset0.asset}, } tx := createInvokeScriptWithProofs(t, payments, proto.FunctionCall{}, proto.OptionalAsset{}, 1) - info := defaultCheckerInfo() + to.stor.addBlock(t, blockID0) assetId := tx.Payments[0].Asset.ID to.stor.createAsset(t, assetId) @@ -1419,7 +1445,8 @@ func TestCheckInvokeScriptWithProofs(t *testing.T) { } func TestCheckUpdateAssetInfoWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createUpdateAssetInfoWithProofs(t) // We create asset using random block here on purpose, this way @@ -1428,7 +1455,6 @@ func TestCheckUpdateAssetInfoWithProofs(t *testing.T) { to.stor.createAsset(t, tx.FeeAsset.ID) tx.SenderPK = assetInfo.issuer - info := defaultCheckerInfo() info.height = 100001 // Check fail prior to activation. @@ -1461,10 +1487,10 @@ func TestCheckUpdateAssetInfoWithProofs(t *testing.T) { } func TestCheckInvokeExpressionWithProofs(t *testing.T) { - to := createCheckerTestObjects(t) + info := defaultCheckerInfo() + to := createCheckerTestObjects(t, info) tx := createInvokeExpressionWithProofs(t, make([]byte, 1), proto.OptionalAsset{}, 1) - info := defaultCheckerInfo() // Check activation. _, err := to.tc.checkInvokeScriptWithProofs(tx, info) diff --git a/pkg/state/transaction_differ.go b/pkg/state/transaction_differ.go index 1b297c55d..b4450f1ea 100644 --- a/pkg/state/transaction_differ.go +++ b/pkg/state/transaction_differ.go @@ -77,14 +77,14 @@ func newBalanceDiff(balance, leaseIn, leaseOut int64, updateMinIntermediateBalan // applyTo() applies diff to the profile given. // It does not change input profile, and returns the updated version. // It also checks that it is legitimate to apply this diff to the profile (negative balances / overflows). -func (diff *balanceDiff) applyTo(profile *balanceProfile) (*balanceProfile, error) { +func (diff *balanceDiff) applyTo(profile balanceProfile) (balanceProfile, error) { // Check min intermediate change. minBalance, err := common.AddInt(diff.minBalance, int64(profile.balance)) if err != nil { - return nil, errors.Errorf("failed to add balance and min balance diff: %v\n", err) + return balanceProfile{}, errors.Errorf("failed to add balance and min balance diff: %v", err) } if minBalance < 0 { - return nil, errors.Errorf( + return balanceProfile{}, errors.Errorf( "negative intermediate balance (Attempt to transfer unavailable funds): balance is %d; diff is: %d\n", profile.balance, diff.minBalance, @@ -93,29 +93,29 @@ func (diff *balanceDiff) applyTo(profile *balanceProfile) (*balanceProfile, erro // Check main balance diff. newBalance, err := common.AddInt(diff.balance, int64(profile.balance)) if err != nil { - return nil, errors.Errorf("failed to add balance and balance diff: %v\n", err) + return balanceProfile{}, errors.Errorf("failed to add balance and balance diff: %v", err) } if newBalance < 0 { - return nil, errors.New("negative result balance (Attempt to transfer unavailable funds)") + return balanceProfile{}, errors.New("negative result balance (Attempt to transfer unavailable funds)") } newLeaseIn, err := common.AddInt(diff.leaseIn, profile.leaseIn) if err != nil { - return nil, errors.Errorf("failed to add leaseIn and leaseIn diff: %v\n", err) + return balanceProfile{}, errors.Errorf("failed to add leaseIn and leaseIn diff: %v", err) } // Check leasing change. newLeaseOut, err := common.AddInt(diff.leaseOut, profile.leaseOut) if err != nil { - return nil, errors.Errorf("failed to add leaseOut and leaseOut diff: %v\n", err) + return balanceProfile{}, errors.Errorf("failed to add leaseOut and leaseOut diff: %v", err) } if (newBalance < newLeaseOut) && !diff.allowLeasedTransfer { - return nil, errs.NewTxValidationError("Reason: Cannot lease more than own") + return balanceProfile{}, errs.NewTxValidationError("Reason: Cannot lease more than own") } // Create new profile. - newProfile := &balanceProfile{} - newProfile.balance = uint64(newBalance) - newProfile.leaseIn = newLeaseIn - newProfile.leaseOut = newLeaseOut - return newProfile, nil + return balanceProfile{ + balance: uint64(newBalance), + leaseIn: newLeaseIn, + leaseOut: newLeaseOut, + }, nil } // applyToAssetBalance() is similar to applyTo() but does not deal with leasing. diff --git a/pkg/state/transaction_differ_test.go b/pkg/state/transaction_differ_test.go index 317b6afdf..89b646412 100644 --- a/pkg/state/transaction_differ_test.go +++ b/pkg/state/transaction_differ_test.go @@ -26,18 +26,31 @@ var ( ) type differTestObjects struct { - stor *testStorageObjects - td *transactionDiffer - tp *transactionPerformer + stor *testStorageObjects + td *transactionDiffer + tp *transactionPerformer + stateActionsCounter *proto.StateActionsCounter } -func createDifferTestObjects(t *testing.T) *differTestObjects { +func createDifferTestObjects(t *testing.T, checkerInfo *checkerInfo) *differTestObjects { stor := createStorageObjects(t, true) td, err := newTransactionDiffer(stor.entities, settings.MainNetSettings) require.NoError(t, err, "newTransactionDiffer() failed") - tp, err := newTransactionPerformer(stor.entities, settings.MainNetSettings) + + actionsCounter := new(proto.StateActionsCounter) + + snapshotApplier := newBlockSnapshotsApplier( + newBlockSnapshotsApplierInfo( + checkerInfo, + settings.MainNetSettings.AddressSchemeCharacter, + actionsCounter, + ), + newSnapshotApplierStorages(stor.entities), + ) + snapshotGen := newSnapshotGenerator(stor.entities, settings.MainNetSettings.AddressSchemeCharacter) + tp := newTransactionPerformer(stor.entities, settings.MainNetSettings, &snapshotGen, &snapshotApplier) require.NoError(t, err, "newTransactionPerformer() failed") - return &differTestObjects{stor, td, tp} + return &differTestObjects{stor, td, tp, actionsCounter} } func createGenesis() *proto.Genesis { @@ -45,7 +58,8 @@ func createGenesis() *proto.Genesis { } func TestCreateDiffGenesis(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createGenesis() ch, err := to.td.createDiffGenesis(tx, defaultDifferInfo()) @@ -66,7 +80,8 @@ func createPayment(t *testing.T) *proto.Payment { } func TestCreateDiffPayment(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createPayment(t) ch, err := to.td.createDiffPayment(tx, defaultDifferInfo()) @@ -93,7 +108,8 @@ func createTransferWithSig(t *testing.T) *proto.TransferWithSig { } func TestCreateDiffTransferWithSig(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createTransferWithSig(t) feeFullAssetID := tx.FeeAsset.ID @@ -150,7 +166,8 @@ func createTransferWithProofs(t *testing.T) *proto.TransferWithProofs { } func TestCreateDiffTransferWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createTransferWithProofs(t) feeFullAssetID := tx.FeeAsset.ID @@ -214,7 +231,8 @@ func createNFTIssueWithSig(t *testing.T) *proto.IssueWithSig { } func TestCreateDiffIssueWithSig(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createIssueWithSig(t, 1000) ch, err := to.td.createDiffIssueWithSig(tx, defaultDifferInfo()) @@ -248,7 +266,8 @@ func createNFTIssueWithProofs(t *testing.T) *proto.IssueWithProofs { } func TestCreateDiffIssueWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createIssueWithProofs(t, 1000) ch, err := to.td.createDiffIssueWithProofs(tx, defaultDifferInfo()) @@ -275,7 +294,8 @@ func createReissueWithSig(t *testing.T, feeUnits int) *proto.ReissueWithSig { } func TestCreateDiffReissueWithSig(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createReissueWithSig(t, 1000) ch, err := to.td.createDiffReissueWithSig(tx, defaultDifferInfo()) @@ -301,7 +321,8 @@ func createReissueWithProofs(t *testing.T, feeUnits int) *proto.ReissueWithProof } func TestCreateDiffReissueWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createReissueWithProofs(t, 1000) ch, err := to.td.createDiffReissueWithProofs(tx, defaultDifferInfo()) @@ -327,7 +348,8 @@ func createBurnWithSig(t *testing.T) *proto.BurnWithSig { } func TestCreateDiffBurnWithSig(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createBurnWithSig(t) ch, err := to.td.createDiffBurnWithSig(tx, defaultDifferInfo()) @@ -353,7 +375,8 @@ func createBurnWithProofs(t *testing.T) *proto.BurnWithProofs { } func TestCreateDiffBurnWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createBurnWithProofs(t) ch, err := to.td.createDiffBurnWithProofs(tx, defaultDifferInfo()) @@ -399,7 +422,8 @@ func createExchangeWithSig(t *testing.T) *proto.ExchangeWithSig { //} func TestCreateDiffExchangeWithSig(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createExchangeWithSig(t) ch, err := to.td.createDiffExchange(tx, defaultDifferInfo()) @@ -452,7 +476,8 @@ func createUnorderedExchangeWithProofs(t *testing.T, v int) *proto.ExchangeWithP } func TestCreateDiffExchangeWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createExchangeWithProofs(t) ch, err := to.td.createDiffExchange(tx, defaultDifferInfo()) @@ -561,7 +586,8 @@ func createExchangeV2WithProofsWithOrdersV3(t *testing.T, info orderBuildInfo) * } func TestCreateDiffExchangeV2WithProofsWithOrdersV3(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createExchangeV2WithProofsWithOrdersV3(t, orderBuildInfo{ price: 10e8, @@ -592,7 +618,8 @@ func TestCreateDiffExchangeV2WithProofsWithOrdersV3(t *testing.T) { } func TestCreateDiffExchangeV3WithProofsWithMixedOrders(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) const ( asset0Decimals = 5 @@ -699,7 +726,7 @@ func TestCreateDiffExchangeV3WithProofsWithMixedOrders(t *testing.T) { // and produces an incorrect or unexpected diff, should be fixes some how // // func TestCreateDiffExchangeWithSignature(t *testing.T) { -// to, path := createDifferTestObjects(t) +// to, path := createDifferTestObjects(t, checkerInfo) // // to.stor.createAssetWithDecimals(t, testGlobal.asset0.asset.ID, 8) // to.stor.createAssetWithDecimals(t, testGlobal.asset1.asset.ID, 8) @@ -733,7 +760,8 @@ func TestCreateDiffExchangeV3WithProofsWithMixedOrders(t *testing.T) { // assert.Equal(t, correctAddrs, ch.addrs) // } func TestCreateDiffExchangeV3WithProofsWithOrdersV4(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) to.stor.createAssetWithDecimals(t, testGlobal.asset0.asset.ID, 0) to.stor.createAssetWithDecimals(t, testGlobal.asset1.asset.ID, 8) @@ -806,7 +834,8 @@ func createLeaseWithSig(t *testing.T) *proto.LeaseWithSig { } func TestCreateDiffLeaseWithSig(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createLeaseWithSig(t) ch, err := to.td.createDiffLeaseWithSig(tx, defaultDifferInfo()) @@ -833,7 +862,8 @@ func createLeaseWithProofs(t *testing.T) *proto.LeaseWithProofs { } func TestCreateDiffLeaseWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createLeaseWithProofs(t) ch, err := to.td.createDiffLeaseWithProofs(tx, defaultDifferInfo()) @@ -860,12 +890,13 @@ func createLeaseCancelWithSig(t *testing.T, leaseID crypto.Digest) *proto.LeaseC } func TestCreateDiffLeaseCancelWithSig(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) leaseTx := createLeaseWithSig(t) - info := defaultPerformerInfo() + info := defaultPerformerInfo(to.stateActionsCounter) to.stor.addBlock(t, blockID0) - err := to.tp.performLeaseWithSig(leaseTx, info) + _, err := to.tp.performLeaseWithSig(leaseTx, info, nil, nil) assert.NoError(t, err, "performLeaseWithSig failed") tx := createLeaseCancelWithSig(t, *leaseTx.ID) @@ -893,12 +924,13 @@ func createLeaseCancelWithProofs(t *testing.T, leaseID crypto.Digest) *proto.Lea } func TestCreateDiffLeaseCancelWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) leaseTx := createLeaseWithProofs(t) - info := defaultPerformerInfo() + info := defaultPerformerInfo(to.stateActionsCounter) to.stor.addBlock(t, blockID0) - err := to.tp.performLeaseWithProofs(leaseTx, info) + _, err := to.tp.performLeaseWithProofs(leaseTx, info, nil, nil) assert.NoError(t, err, "performLeaseWithProofs failed") tx := createLeaseCancelWithProofs(t, *leaseTx.ID) @@ -930,7 +962,8 @@ func createCreateAliasWithSig(t *testing.T) *proto.CreateAliasWithSig { } func TestCreateDiffCreateAliasWithSig(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createCreateAliasWithSig(t) ch, err := to.td.createDiffCreateAliasWithSig(tx, defaultDifferInfo()) @@ -959,7 +992,8 @@ func createCreateAliasWithProofs(t *testing.T) *proto.CreateAliasWithProofs { } func TestCreateDiffCreateAliasWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createCreateAliasWithProofs(t) ch, err := to.td.createDiffCreateAliasWithProofs(tx, defaultDifferInfo()) @@ -995,7 +1029,8 @@ func createMassTransferWithProofs(t *testing.T, transfers []proto.MassTransferEn } func TestCreateDiffMassTransferWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) entriesNum := 66 entries := generateMassTransferEntries(t, entriesNum) @@ -1035,7 +1070,8 @@ func createDataWithProofs(t *testing.T, entriesNum int) *proto.DataWithProofs { } func TestCreateDiffDataWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createDataWithProofs(t, 1) ch, err := to.td.createDiffDataWithProofs(tx, defaultDifferInfo()) @@ -1060,7 +1096,8 @@ func createSponsorshipWithProofs(t *testing.T, fee uint64) *proto.SponsorshipWit } func TestCreateDiffSponsorshipWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createSponsorshipWithProofs(t, 1000) ch, err := to.td.createDiffSponsorshipWithProofs(tx, defaultDifferInfo()) @@ -1092,7 +1129,8 @@ func createSetScriptWithProofs(t *testing.T, customScriptBytes ...[]byte) *proto } func TestCreateDiffSetScriptWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createSetScriptWithProofs(t) ch, err := to.td.createDiffSetScriptWithProofs(tx, defaultDifferInfo()) @@ -1119,7 +1157,8 @@ func createSetAssetScriptWithProofs(t *testing.T) *proto.SetAssetScriptWithProof } func TestCreateDiffSetAssetScriptWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createSetAssetScriptWithProofs(t) ch, err := to.td.createDiffSetAssetScriptWithProofs(tx, defaultDifferInfo()) @@ -1144,7 +1183,8 @@ func createInvokeScriptWithProofs(t *testing.T, pmts proto.ScriptPayments, fc pr } func TestCreateDiffInvokeScriptWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) feeConst, ok := feeConstants[proto.InvokeScriptTransaction] assert.Equal(t, ok, true) @@ -1206,7 +1246,8 @@ func createUpdateAssetInfoWithProofs(t *testing.T) *proto.UpdateAssetInfoWithPro } func TestCreateDiffUpdateAssetInfoWithProofs(t *testing.T) { - to := createDifferTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createDifferTestObjects(t, checkerInfo) tx := createUpdateAssetInfoWithProofs(t) ch, err := to.td.createDiffUpdateAssetInfoWithProofs(tx, defaultDifferInfo()) diff --git a/pkg/state/transaction_handler.go b/pkg/state/transaction_handler.go index d54eb858f..0a2360f98 100644 --- a/pkg/state/transaction_handler.go +++ b/pkg/state/transaction_handler.go @@ -24,7 +24,7 @@ type scriptEstimation struct { func (e *scriptEstimation) isPresent() bool { return e != nil } type txCheckFunc func(proto.Transaction, *checkerInfo) (txCheckerData, error) -type txPerformFunc func(proto.Transaction, *performerInfo) error +type txPerformFunc func(proto.Transaction, *performerInfo, *invocationResult, txDiff) (txSnapshot, error) type txCreateDiffFunc func(proto.Transaction, *differInfo) (txBalanceChanges, error) type txCountFeeFunc func(proto.Transaction, *feeDistribution) error @@ -47,88 +47,116 @@ type transactionHandler struct { } // TODO: see TODO on GetTypeInfo() in proto/transactions.go. +// performer builds snapshots. func buildHandles(tc *transactionChecker, tp *transactionPerformer, td *transactionDiffer, tf *transactionFeeCounter) handles { return handles{ proto.TransactionTypeInfo{Type: proto.GenesisTransaction, ProofVersion: proto.Signature}: txHandleFuncs{ - tc.checkGenesis, nil, td.createDiffGenesis, nil, + tc.checkGenesis, tp.performGenesis, + td.createDiffGenesis, nil, }, proto.TransactionTypeInfo{Type: proto.PaymentTransaction, ProofVersion: proto.Signature}: txHandleFuncs{ - tc.checkPayment, nil, td.createDiffPayment, tf.minerFeePayment, + tc.checkPayment, tp.performPayment, + td.createDiffPayment, tf.minerFeePayment, }, proto.TransactionTypeInfo{Type: proto.TransferTransaction, ProofVersion: proto.Signature}: txHandleFuncs{ - tc.checkTransferWithSig, nil, td.createDiffTransferWithSig, tf.minerFeeTransferWithSig, + tc.checkTransferWithSig, tp.performTransferWithSig, + td.createDiffTransferWithSig, tf.minerFeeTransferWithSig, }, proto.TransactionTypeInfo{Type: proto.TransferTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkTransferWithProofs, nil, td.createDiffTransferWithProofs, tf.minerFeeTransferWithProofs, + tc.checkTransferWithProofs, tp.performTransferWithProofs, + td.createDiffTransferWithProofs, tf.minerFeeTransferWithProofs, }, proto.TransactionTypeInfo{Type: proto.IssueTransaction, ProofVersion: proto.Signature}: txHandleFuncs{ - tc.checkIssueWithSig, tp.performIssueWithSig, td.createDiffIssueWithSig, tf.minerFeeIssueWithSig, + tc.checkIssueWithSig, tp.performIssueWithSig, + td.createDiffIssueWithSig, tf.minerFeeIssueWithSig, }, proto.TransactionTypeInfo{Type: proto.IssueTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkIssueWithProofs, tp.performIssueWithProofs, td.createDiffIssueWithProofs, tf.minerFeeIssueWithProofs, + tc.checkIssueWithProofs, tp.performIssueWithProofs, + td.createDiffIssueWithProofs, tf.minerFeeIssueWithProofs, }, proto.TransactionTypeInfo{Type: proto.ReissueTransaction, ProofVersion: proto.Signature}: txHandleFuncs{ - tc.checkReissueWithSig, tp.performReissueWithSig, td.createDiffReissueWithSig, tf.minerFeeReissueWithSig, + tc.checkReissueWithSig, tp.performReissueWithSig, + td.createDiffReissueWithSig, tf.minerFeeReissueWithSig, }, proto.TransactionTypeInfo{Type: proto.ReissueTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkReissueWithProofs, tp.performReissueWithProofs, td.createDiffReissueWithProofs, tf.minerFeeReissueWithProofs, + tc.checkReissueWithProofs, tp.performReissueWithProofs, + td.createDiffReissueWithProofs, tf.minerFeeReissueWithProofs, }, proto.TransactionTypeInfo{Type: proto.BurnTransaction, ProofVersion: proto.Signature}: txHandleFuncs{ - tc.checkBurnWithSig, tp.performBurnWithSig, td.createDiffBurnWithSig, tf.minerFeeBurnWithSig, + tc.checkBurnWithSig, tp.performBurnWithSig, + td.createDiffBurnWithSig, tf.minerFeeBurnWithSig, }, proto.TransactionTypeInfo{Type: proto.BurnTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkBurnWithProofs, tp.performBurnWithProofs, td.createDiffBurnWithProofs, tf.minerFeeBurnWithProofs, + tc.checkBurnWithProofs, tp.performBurnWithProofs, + td.createDiffBurnWithProofs, tf.minerFeeBurnWithProofs, }, proto.TransactionTypeInfo{Type: proto.ExchangeTransaction, ProofVersion: proto.Signature}: txHandleFuncs{ - tc.checkExchangeWithSig, tp.performExchange, td.createDiffExchange, tf.minerFeeExchange, + tc.checkExchangeWithSig, tp.performExchange, + td.createDiffExchange, tf.minerFeeExchange, }, proto.TransactionTypeInfo{Type: proto.ExchangeTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkExchangeWithProofs, tp.performExchange, td.createDiffExchange, tf.minerFeeExchange, + tc.checkExchangeWithProofs, tp.performExchange, + td.createDiffExchange, tf.minerFeeExchange, }, proto.TransactionTypeInfo{Type: proto.LeaseTransaction, ProofVersion: proto.Signature}: txHandleFuncs{ - tc.checkLeaseWithSig, tp.performLeaseWithSig, td.createDiffLeaseWithSig, tf.minerFeeLeaseWithSig, + tc.checkLeaseWithSig, tp.performLeaseWithSig, + td.createDiffLeaseWithSig, tf.minerFeeLeaseWithSig, }, proto.TransactionTypeInfo{Type: proto.LeaseTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkLeaseWithProofs, tp.performLeaseWithProofs, td.createDiffLeaseWithProofs, tf.minerFeeLeaseWithProofs, + tc.checkLeaseWithProofs, tp.performLeaseWithProofs, + td.createDiffLeaseWithProofs, tf.minerFeeLeaseWithProofs, }, proto.TransactionTypeInfo{Type: proto.LeaseCancelTransaction, ProofVersion: proto.Signature}: txHandleFuncs{ - tc.checkLeaseCancelWithSig, tp.performLeaseCancelWithSig, td.createDiffLeaseCancelWithSig, tf.minerFeeLeaseCancelWithSig, + tc.checkLeaseCancelWithSig, tp.performLeaseCancelWithSig, + td.createDiffLeaseCancelWithSig, tf.minerFeeLeaseCancelWithSig, }, proto.TransactionTypeInfo{Type: proto.LeaseCancelTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkLeaseCancelWithProofs, tp.performLeaseCancelWithProofs, td.createDiffLeaseCancelWithProofs, tf.minerFeeLeaseCancelWithProofs, + tc.checkLeaseCancelWithProofs, tp.performLeaseCancelWithProofs, + td.createDiffLeaseCancelWithProofs, tf.minerFeeLeaseCancelWithProofs, }, proto.TransactionTypeInfo{Type: proto.CreateAliasTransaction, ProofVersion: proto.Signature}: txHandleFuncs{ - tc.checkCreateAliasWithSig, tp.performCreateAliasWithSig, td.createDiffCreateAliasWithSig, tf.minerFeeCreateAliasWithSig, + tc.checkCreateAliasWithSig, tp.performCreateAliasWithSig, + td.createDiffCreateAliasWithSig, tf.minerFeeCreateAliasWithSig, }, proto.TransactionTypeInfo{Type: proto.CreateAliasTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkCreateAliasWithProofs, tp.performCreateAliasWithProofs, td.createDiffCreateAliasWithProofs, tf.minerFeeCreateAliasWithProofs, + tc.checkCreateAliasWithProofs, tp.performCreateAliasWithProofs, + td.createDiffCreateAliasWithProofs, tf.minerFeeCreateAliasWithProofs, }, proto.TransactionTypeInfo{Type: proto.MassTransferTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkMassTransferWithProofs, nil, td.createDiffMassTransferWithProofs, tf.minerFeeMassTransferWithProofs, + tc.checkMassTransferWithProofs, tp.performMassTransferWithProofs, + td.createDiffMassTransferWithProofs, tf.minerFeeMassTransferWithProofs, }, proto.TransactionTypeInfo{Type: proto.DataTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkDataWithProofs, tp.performDataWithProofs, td.createDiffDataWithProofs, tf.minerFeeDataWithProofs, + tc.checkDataWithProofs, tp.performDataWithProofs, + td.createDiffDataWithProofs, tf.minerFeeDataWithProofs, }, proto.TransactionTypeInfo{Type: proto.SponsorshipTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkSponsorshipWithProofs, tp.performSponsorshipWithProofs, td.createDiffSponsorshipWithProofs, tf.minerFeeSponsorshipWithProofs, + tc.checkSponsorshipWithProofs, tp.performSponsorshipWithProofs, + td.createDiffSponsorshipWithProofs, tf.minerFeeSponsorshipWithProofs, }, proto.TransactionTypeInfo{Type: proto.SetScriptTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkSetScriptWithProofs, tp.performSetScriptWithProofs, td.createDiffSetScriptWithProofs, tf.minerFeeSetScriptWithProofs, + tc.checkSetScriptWithProofs, tp.performSetScriptWithProofs, + td.createDiffSetScriptWithProofs, tf.minerFeeSetScriptWithProofs, }, proto.TransactionTypeInfo{Type: proto.SetAssetScriptTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkSetAssetScriptWithProofs, tp.performSetAssetScriptWithProofs, td.createDiffSetAssetScriptWithProofs, tf.minerFeeSetAssetScriptWithProofs, + tc.checkSetAssetScriptWithProofs, tp.performSetAssetScriptWithProofs, + td.createDiffSetAssetScriptWithProofs, tf.minerFeeSetAssetScriptWithProofs, }, proto.TransactionTypeInfo{Type: proto.InvokeScriptTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkInvokeScriptWithProofs, tp.performInvokeScriptWithProofs, td.createDiffInvokeScriptWithProofs, tf.minerFeeInvokeScriptWithProofs, + tc.checkInvokeScriptWithProofs, tp.performInvokeScriptWithProofs, + td.createDiffInvokeScriptWithProofs, tf.minerFeeInvokeScriptWithProofs, }, proto.TransactionTypeInfo{Type: proto.InvokeExpressionTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkInvokeExpressionWithProofs, tp.performInvokeExpressionWithProofs, td.createDiffInvokeExpressionWithProofs, tf.minerFeeInvokeExpressionWithProofs, + tc.checkInvokeExpressionWithProofs, tp.performInvokeExpressionWithProofs, + td.createDiffInvokeExpressionWithProofs, tf.minerFeeInvokeExpressionWithProofs, }, proto.TransactionTypeInfo{Type: proto.UpdateAssetInfoTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkUpdateAssetInfoWithProofs, tp.performUpdateAssetInfoWithProofs, td.createDiffUpdateAssetInfoWithProofs, tf.minerFeeUpdateAssetInfoWithProofs, + tc.checkUpdateAssetInfoWithProofs, tp.performUpdateAssetInfoWithProofs, + td.createDiffUpdateAssetInfoWithProofs, tf.minerFeeUpdateAssetInfoWithProofs, }, proto.TransactionTypeInfo{Type: proto.EthereumMetamaskTransaction, ProofVersion: proto.Proof}: txHandleFuncs{ - tc.checkEthereumTransactionWithProofs, tp.performEthereumTransactionWithProofs, td.createDiffEthereumTransactionWithProofs, tf.minerFeeEthereumTxWithProofs, + tc.checkEthereumTransactionWithProofs, tp.performEthereumTransactionWithProofs, + td.createDiffEthereumTransactionWithProofs, tf.minerFeeEthereumTxWithProofs, }, } } @@ -137,15 +165,14 @@ func newTransactionHandler( genesis proto.BlockID, stor *blockchainEntitiesStorage, settings *settings.BlockchainSettings, + snapshotGenerator *snapshotGenerator, + snapshotApplier extendedSnapshotApplier, ) (*transactionHandler, error) { tc, err := newTransactionChecker(genesis, stor, settings) if err != nil { return nil, err } - tp, err := newTransactionPerformer(stor, settings) - if err != nil { - return nil, err - } + tp := newTransactionPerformer(stor, settings, snapshotGenerator, snapshotApplier) td, err := newTransactionDiffer(stor, settings) if err != nil { return nil, err @@ -170,17 +197,18 @@ func (h *transactionHandler) checkTx(tx proto.Transaction, info *checkerInfo) (t return funcs.check(tx, info) } -func (h *transactionHandler) performTx(tx proto.Transaction, info *performerInfo) error { +func (h *transactionHandler) performTx(tx proto.Transaction, info *performerInfo, + invocationRes *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tv := tx.GetTypeInfo() funcs, ok := h.funcs[tv] if !ok { - return errors.Errorf("No function handler implemented for tx struct type %T\n", tx) + return txSnapshot{}, errors.Errorf("no function handler implemented for tx struct type %T", tx) } if funcs.perform == nil { - // No perform func for this combination of transaction type and version. - return nil + // performer function must not be nil + return txSnapshot{}, errors.Errorf("performer function handler is nil for tx struct type %T", tx) } - return funcs.perform(tx, info) + return funcs.perform(tx, info, invocationRes, balanceChanges) } func (h *transactionHandler) createDiffTx(tx proto.Transaction, info *differInfo) (txBalanceChanges, error) { diff --git a/pkg/state/transaction_performer.go b/pkg/state/transaction_performer.go index 8555275aa..0c7c24641 100644 --- a/pkg/state/transaction_performer.go +++ b/pkg/state/transaction_performer.go @@ -12,34 +12,98 @@ import ( type performerInfo struct { height uint64 - stateActionsCounter *proto.StateActionsCounter blockID proto.BlockID + currentMinerAddress proto.WavesAddress + stateActionsCounter *proto.StateActionsCounter checkerData txCheckerData } -func newPerformerInfo(height proto.Height, stateActionsCounter *proto.StateActionsCounter, blockID proto.BlockID, checkerData txCheckerData) *performerInfo { - return &performerInfo{height, stateActionsCounter, blockID, checkerData} // all fields must be initialized +func newPerformerInfo(height proto.Height, stateActionsCounter *proto.StateActionsCounter, + blockID proto.BlockID, currentMinerAddress proto.WavesAddress, + checkerData txCheckerData) *performerInfo { + return &performerInfo{height, blockID, + currentMinerAddress, stateActionsCounter, + checkerData} // all fields must be initialized } type transactionPerformer struct { - stor *blockchainEntitiesStorage - settings *settings.BlockchainSettings + stor *blockchainEntitiesStorage + settings *settings.BlockchainSettings + snapshotGenerator *snapshotGenerator // initialized in appendTx + snapshotApplier extendedSnapshotApplier // initialized in appendTx +} + +func newTransactionPerformer(stor *blockchainEntitiesStorage, settings *settings.BlockchainSettings, + snapshotGenerator *snapshotGenerator, snapshotApplier extendedSnapshotApplier) *transactionPerformer { + return &transactionPerformer{stor: stor, settings: settings, + snapshotGenerator: snapshotGenerator, snapshotApplier: snapshotApplier} } -func newTransactionPerformer(stor *blockchainEntitiesStorage, settings *settings.BlockchainSettings) (*transactionPerformer, error) { - return &transactionPerformer{stor, settings}, nil +func (tp *transactionPerformer) performGenesis( + transaction proto.Transaction, + _ *performerInfo, _ *invocationResult, + balanceChanges txDiff) (txSnapshot, error) { + _, ok := transaction.(*proto.Genesis) + if !ok { + return txSnapshot{}, errors.New("failed to convert interface to genesis transaction") + } + snapshot, err := tp.snapshotGenerator.generateSnapshotForGenesisTx(balanceChanges) + if err != nil { + return txSnapshot{}, err + } + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performIssue(tx *proto.Issue, assetID crypto.Digest, info *performerInfo) error { +func (tp *transactionPerformer) performPayment(transaction proto.Transaction, _ *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { + _, ok := transaction.(*proto.Payment) + if !ok { + return txSnapshot{}, errors.New("failed to convert interface to payment transaction") + } + snapshot, err := tp.snapshotGenerator.generateSnapshotForPaymentTx(balanceChanges) + if err != nil { + return txSnapshot{}, err + } + return snapshot, snapshot.Apply(tp.snapshotApplier) +} + +func (tp *transactionPerformer) performTransfer(balanceChanges txDiff) (txSnapshot, error) { + snapshot, err := tp.snapshotGenerator.generateSnapshotForTransferTx(balanceChanges) + if err != nil { + return txSnapshot{}, err + } + return snapshot, snapshot.Apply(tp.snapshotApplier) +} + +func (tp *transactionPerformer) performTransferWithSig(transaction proto.Transaction, _ *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { + _, ok := transaction.(*proto.TransferWithSig) + if !ok { + return txSnapshot{}, errors.New("failed to convert interface to transfer with sig transaction") + } + return tp.performTransfer(balanceChanges) +} + +func (tp *transactionPerformer) performTransferWithProofs(transaction proto.Transaction, _ *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { + _, ok := transaction.(*proto.TransferWithProofs) + if !ok { + return txSnapshot{}, errors.New("failed to convert interface to transfer with proofs transaction") + } + return tp.performTransfer(balanceChanges) +} + +func (tp *transactionPerformer) performIssue(tx *proto.Issue, txID crypto.Digest, + assetID crypto.Digest, info *performerInfo, + balanceChanges txDiff, scriptEstimation *scriptEstimation, script *proto.Script) (txSnapshot, error) { blockHeight := info.height + 1 // Create new asset. assetInfo := &assetInfo{ assetConstInfo: assetConstInfo{ - tail: proto.DigestTail(assetID), - issuer: tx.SenderPK, - decimals: tx.Decimals, - issueHeight: blockHeight, - issueSequenceInBlock: info.stateActionsCounter.NextIssueActionNumber(), + tail: proto.DigestTail(assetID), + issuer: tx.SenderPK, + decimals: tx.Decimals, + issueHeight: blockHeight, }, assetChangeableInfo: assetChangeableInfo{ quantity: *big.NewInt(int64(tx.Quantity)), @@ -49,401 +113,411 @@ func (tp *transactionPerformer) performIssue(tx *proto.Issue, assetID crypto.Dig reissuable: tx.Reissuable, }, } - if err := tp.stor.assets.issueAsset(proto.AssetIDFromDigest(assetID), assetInfo, info.blockID); err != nil { - return errors.Wrap(err, "failed to issue asset") + snapshot, err := tp.snapshotGenerator.generateSnapshotForIssueTx(assetID, txID, tx.SenderPK, + *assetInfo, balanceChanges, scriptEstimation, script) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performIssueWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performIssueWithSig(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.IssueWithSig) if !ok { - return errors.New("failed to convert interface to IssueWithSig transaction") + return txSnapshot{}, errors.New("failed to convert interface to IssueWithSig transaction") } txID, err := tx.GetID(tp.settings.AddressSchemeCharacter) if err != nil { - return errors.Errorf("failed to get transaction ID: %v\n", err) + return txSnapshot{}, errors.Errorf("failed to get transaction ID: %v", err) } assetID, err := crypto.NewDigestFromBytes(txID) if err != nil { - return err - } - if err := tp.stor.scriptsStorage.setAssetScript(assetID, proto.Script{}, tx.SenderPK, info.blockID); err != nil { - return err + return txSnapshot{}, err } - return tp.performIssue(&tx.Issue, assetID, info) + return tp.performIssue(&tx.Issue, assetID, assetID, info, balanceChanges, nil, nil) } -func (tp *transactionPerformer) performIssueWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performIssueWithProofs(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.IssueWithProofs) if !ok { - return errors.New("failed to convert interface to IssueWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to IssueWithProofs transaction") } txID, err := tx.GetID(tp.settings.AddressSchemeCharacter) if err != nil { - return errors.Errorf("failed to get transaction ID: %v\n", err) + return txSnapshot{}, errors.Errorf("failed to get transaction ID: %v", err) } assetID, err := crypto.NewDigestFromBytes(txID) if err != nil { - return err - } - if err := tp.stor.scriptsStorage.setAssetScript(assetID, tx.Script, tx.SenderPK, info.blockID); err != nil { - return err + return txSnapshot{}, err } - if se := info.checkerData.scriptEstimation; se.isPresent() { // script estimation is present and not nil - // Save complexities to storage, so we won't have to calculate it every time the script is called. - if scErr := tp.stor.scriptsComplexity.saveComplexitiesForAsset(assetID, *se, info.blockID); scErr != nil { - return scErr - } - } - return tp.performIssue(&tx.Issue, assetID, info) + return tp.performIssue(&tx.Issue, assetID, assetID, info, + balanceChanges, info.checkerData.scriptEstimation, &tx.Script) } -func (tp *transactionPerformer) performReissue(tx *proto.Reissue, info *performerInfo) error { +func (tp *transactionPerformer) performReissue(tx *proto.Reissue, _ *performerInfo, + balanceChanges txDiff) (txSnapshot, error) { // Modify asset. change := &assetReissueChange{ reissuable: tx.Reissuable, diff: int64(tx.Quantity), } - if err := tp.stor.assets.reissueAsset(proto.AssetIDFromDigest(tx.AssetID), change, info.blockID); err != nil { - return errors.Wrap(err, "failed to reissue asset") + + snapshot, err := tp.snapshotGenerator.generateSnapshotForReissueTx(tx.AssetID, *change, balanceChanges) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performReissueWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performReissueWithSig(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.ReissueWithSig) if !ok { - return errors.New("failed to convert interface to ReissueWithSig transaction") + return txSnapshot{}, errors.New("failed to convert interface to ReissueWithSig transaction") } - return tp.performReissue(&tx.Reissue, info) + return tp.performReissue(&tx.Reissue, info, balanceChanges) } -func (tp *transactionPerformer) performReissueWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performReissueWithProofs(transaction proto.Transaction, + info *performerInfo, _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.ReissueWithProofs) if !ok { - return errors.New("failed to convert interface to ReissueWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to ReissueWithProofs transaction") } - return tp.performReissue(&tx.Reissue, info) + return tp.performReissue(&tx.Reissue, info, balanceChanges) } -func (tp *transactionPerformer) performBurn(tx *proto.Burn, info *performerInfo) error { +func (tp *transactionPerformer) performBurn(tx *proto.Burn, _ *performerInfo, + balanceChanges txDiff) (txSnapshot, error) { // Modify asset. change := &assetBurnChange{ diff: int64(tx.Amount), } - if err := tp.stor.assets.burnAsset(proto.AssetIDFromDigest(tx.AssetID), change, info.blockID); err != nil { - return errors.Wrap(err, "failed to burn asset") + + snapshot, err := tp.snapshotGenerator.generateSnapshotForBurnTx(tx.AssetID, *change, balanceChanges) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performBurnWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performBurnWithSig(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.BurnWithSig) if !ok { - return errors.New("failed to convert interface to BurnWithSig transaction") + return txSnapshot{}, errors.New("failed to convert interface to BurnWithSig transaction") } - return tp.performBurn(&tx.Burn, info) + return tp.performBurn(&tx.Burn, info, balanceChanges) } -func (tp *transactionPerformer) performBurnWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performBurnWithProofs(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.BurnWithProofs) if !ok { - return errors.New("failed to convert interface to BurnWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to BurnWithProofs transaction") } - return tp.performBurn(&tx.Burn, info) + return tp.performBurn(&tx.Burn, info, balanceChanges) } -func (tp *transactionPerformer) increaseOrderVolume(order proto.Order, tx proto.Exchange, info *performerInfo) error { - orderId, err := order.GetID() - if err != nil { - return err - } - fee := tx.GetBuyMatcherFee() - if order.GetOrderType() == proto.Sell { - fee = tx.GetSellMatcherFee() - } - if err := tp.stor.ordersVolumes.increaseFilledFee(orderId, fee, info.blockID); err != nil { - return err - } - if err := tp.stor.ordersVolumes.increaseFilledAmount(orderId, tx.GetAmount(), info.blockID); err != nil { - return err - } - return nil -} - -func (tp *transactionPerformer) performExchange(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performExchange(transaction proto.Transaction, _ *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(proto.Exchange) if !ok { - return errors.New("failed to convert interface to Exchange transaction") + return txSnapshot{}, errors.New("failed to convert interface to Exchange transaction") } - so, err := tx.GetSellOrder() + sellOrder, err := tx.GetSellOrder() if err != nil { - return errors.Wrap(err, "no sell order") - } - if err := tp.increaseOrderVolume(so, tx, info); err != nil { - return err + return txSnapshot{}, errors.Wrap(err, "no sell order") } - bo, err := tx.GetBuyOrder() + buyOrder, err := tx.GetBuyOrder() if err != nil { - return errors.Wrap(err, "no buy order") + return txSnapshot{}, errors.Wrap(err, "no buy order") } - if err := tp.increaseOrderVolume(bo, tx, info); err != nil { - return err + volume := tx.GetAmount() + sellFee := tx.GetSellMatcherFee() + buyFee := tx.GetBuyMatcherFee() + + // snapshot must be generated before the state with orders is changed + snapshot, err := tp.snapshotGenerator.generateSnapshotForExchangeTx(sellOrder, + sellFee, buyOrder, buyFee, volume, balanceChanges) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performLease(tx *proto.Lease, id *crypto.Digest, info *performerInfo) error { +func (tp *transactionPerformer) performLease(tx *proto.Lease, txID *crypto.Digest, info *performerInfo, + balanceChanges txDiff) (txSnapshot, error) { senderAddr, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, tx.SenderPK) if err != nil { - return err + return txSnapshot{}, err } var recipientAddr proto.WavesAddress if addr := tx.Recipient.Address(); addr == nil { recipientAddr, err = tp.stor.aliases.newestAddrByAlias(tx.Recipient.Alias().Alias) if err != nil { - return errors.Errorf("invalid alias: %v\n", err) + return txSnapshot{}, errors.Errorf("invalid alias: %v", err) } } else { recipientAddr = *addr } // Add leasing to lease state. l := &leasing{ - Sender: senderAddr, - Recipient: recipientAddr, - Amount: tx.Amount, - Height: info.height, - Status: LeaseActive, - } - if err := tp.stor.leases.addLeasing(*id, l, info.blockID); err != nil { - return errors.Wrap(err, "failed to add leasing") + Sender: senderAddr, + Recipient: recipientAddr, + Amount: tx.Amount, + OriginHeight: info.height, + Status: LeaseActive, + } + leaseID := *txID + snapshot, err := tp.snapshotGenerator.generateSnapshotForLeaseTx(l, leaseID, txID, balanceChanges) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performLeaseWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performLeaseWithSig(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.LeaseWithSig) if !ok { - return errors.New("failed to convert interface to LeaseWithSig transaction") + return txSnapshot{}, errors.New("failed to convert interface to LeaseWithSig transaction") } - return tp.performLease(&tx.Lease, tx.ID, info) + return tp.performLease(&tx.Lease, tx.ID, info, balanceChanges) } -func (tp *transactionPerformer) performLeaseWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performLeaseWithProofs(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.LeaseWithProofs) if !ok { - return errors.New("failed to convert interface to LeaseWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to LeaseWithProofs transaction") } - return tp.performLease(&tx.Lease, tx.ID, info) + return tp.performLease(&tx.Lease, tx.ID, info, balanceChanges) } -func (tp *transactionPerformer) performLeaseCancel(tx *proto.LeaseCancel, txID *crypto.Digest, info *performerInfo) error { - if err := tp.stor.leases.cancelLeasing(tx.LeaseID, info.blockID, info.height, txID); err != nil { - return errors.Wrap(err, "failed to cancel leasing") +func (tp *transactionPerformer) performLeaseCancel(tx *proto.LeaseCancel, txID *crypto.Digest, info *performerInfo, + balanceChanges txDiff) (txSnapshot, error) { + snapshot, err := tp.snapshotGenerator.generateSnapshotForLeaseCancelTx(txID, tx.LeaseID, info.height, balanceChanges) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performLeaseCancelWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performLeaseCancelWithSig(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.LeaseCancelWithSig) if !ok { - return errors.New("failed to convert interface to LeaseCancelWithSig transaction") + return txSnapshot{}, errors.New("failed to convert interface to LeaseCancelWithSig transaction") } - return tp.performLeaseCancel(&tx.LeaseCancel, tx.ID, info) + return tp.performLeaseCancel(&tx.LeaseCancel, tx.ID, info, balanceChanges) } -func (tp *transactionPerformer) performLeaseCancelWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performLeaseCancelWithProofs(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.LeaseCancelWithProofs) if !ok { - return errors.New("failed to convert interface to LeaseCancelWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to LeaseCancelWithProofs transaction") } - return tp.performLeaseCancel(&tx.LeaseCancel, tx.ID, info) + return tp.performLeaseCancel(&tx.LeaseCancel, tx.ID, info, balanceChanges) } -func (tp *transactionPerformer) performCreateAlias(tx *proto.CreateAlias, info *performerInfo) error { +func (tp *transactionPerformer) performCreateAlias(tx *proto.CreateAlias, + _ *performerInfo, balanceChanges txDiff) (txSnapshot, error) { senderAddr, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, tx.SenderPK) if err != nil { - return err + return txSnapshot{}, err } - // Save alias to aliases storage. - if err := tp.stor.aliases.createAlias(tx.Alias.Alias, senderAddr, info.blockID); err != nil { - return err + + snapshot, err := tp.snapshotGenerator.generateSnapshotForCreateAliasTx(senderAddr, tx.Alias, balanceChanges) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performCreateAliasWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performCreateAliasWithSig(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.CreateAliasWithSig) if !ok { - return errors.New("failed to convert interface to CreateAliasWithSig transaction") + return txSnapshot{}, errors.New("failed to convert interface to CreateAliasWithSig transaction") } - return tp.performCreateAlias(&tx.CreateAlias, info) + return tp.performCreateAlias(&tx.CreateAlias, info, balanceChanges) } -func (tp *transactionPerformer) performCreateAliasWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performCreateAliasWithProofs(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.CreateAliasWithProofs) if !ok { - return errors.New("failed to convert interface to CreateAliasWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to CreateAliasWithProofs transaction") + } + return tp.performCreateAlias(&tx.CreateAlias, info, balanceChanges) +} + +func (tp *transactionPerformer) performMassTransferWithProofs(transaction proto.Transaction, _ *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { + _, ok := transaction.(*proto.MassTransferWithProofs) + if !ok { + return txSnapshot{}, errors.New("failed to convert interface to CreateAliasWithProofs transaction") } - return tp.performCreateAlias(&tx.CreateAlias, info) + snapshot, err := tp.snapshotGenerator.generateSnapshotForMassTransferTx(balanceChanges) + if err != nil { + return txSnapshot{}, err + } + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performDataWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performDataWithProofs(transaction proto.Transaction, + _ *performerInfo, _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.DataWithProofs) if !ok { - return errors.New("failed to convert interface to DataWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to DataWithProofs transaction") } senderAddr, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, tx.SenderPK) if err != nil { - return err + return txSnapshot{}, err } - for _, entry := range tx.Entries { - if err := tp.stor.accountsDataStor.appendEntry(senderAddr, entry, info.blockID); err != nil { - return err - } + + snapshot, err := tp.snapshotGenerator.generateSnapshotForDataTx(senderAddr, tx.Entries, balanceChanges) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performSponsorshipWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performSponsorshipWithProofs(transaction proto.Transaction, _ *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.SponsorshipWithProofs) if !ok { - return errors.New("failed to convert interface to SponsorshipWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to SponsorshipWithProofs transaction") } - if err := tp.stor.sponsoredAssets.sponsorAsset(tx.AssetID, tx.MinAssetFee, info.blockID); err != nil { - return errors.Wrap(err, "failed to sponsor asset") + + snapshot, err := tp.snapshotGenerator.generateSnapshotForSponsorshipTx(tx.AssetID, tx.MinAssetFee, balanceChanges) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performSetScriptWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performSetScriptWithProofs(transaction proto.Transaction, info *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.SetScriptWithProofs) if !ok { - return errors.New("failed to convert interface to SetScriptWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to SetScriptWithProofs transaction") } + se := info.checkerData.scriptEstimation if !se.isPresent() { - return errors.New("script estimations must be set for SetScriptWithProofs tx") + return txSnapshot{}, errors.New("script estimations must be set for SetScriptWithProofs tx") } - // script estimation is present and not nil - err := storeScriptByAddress(tp.stor, tp.settings.AddressSchemeCharacter, tx.SenderPK, tx.Script, *se, info.blockID) - if err != nil { - return errors.Wrapf(err, "failed to perform SetScriptWithProofs tx %q", tx.ID.String()) - } - return err -} -func storeScriptByAddress( - stor *blockchainEntitiesStorage, - scheme proto.Scheme, - senderPK crypto.PublicKey, - script proto.Script, - se scriptEstimation, - blockID proto.BlockID, -) error { - senderAddr, err := proto.NewAddressFromPublicKey(scheme, senderPK) + snapshot, err := tp.snapshotGenerator.generateSnapshotForSetScriptTx(tx.SenderPK, + tx.Script, *se, balanceChanges) if err != nil { - return errors.Wrapf(err, "failed to create addr from PK %q", senderPK.String()) - } - if setErr := stor.scriptsStorage.setAccountScript(senderAddr, script, senderPK, blockID); setErr != nil { - return errors.Wrapf(setErr, "failed to set account script on addr %q", senderAddr.String()) + return txSnapshot{}, errors.Wrap(err, "failed to generate snapshot for set script tx") } - // Save complexity to storage, so we won't have to calculate it every time the script is called. - if setErr := stor.scriptsComplexity.saveComplexitiesForAddr(senderAddr, se, blockID); setErr != nil { - return errors.Wrapf(setErr, "failed to save script complexities for addr %q", senderAddr.String()) - } - return nil + + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performSetAssetScriptWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performSetAssetScriptWithProofs(transaction proto.Transaction, + info *performerInfo, _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.SetAssetScriptWithProofs) if !ok { - return errors.New("failed to convert interface to SetAssetScriptWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to SetAssetScriptWithProofs transaction") } + se := info.checkerData.scriptEstimation if !se.isPresent() { - return errors.New("script estimations must be set for SetAssetScriptWithProofs tx") + return txSnapshot{}, errors.New("script estimations must be set for SetAssetScriptWithProofs tx") } - // script estimation is present and not nil - if err := tp.stor.scriptsStorage.setAssetScript(tx.AssetID, tx.Script, tx.SenderPK, info.blockID); err != nil { - return errors.Wrap(err, "failed to set asset script") - } - // Save complexity to storage, so we won't have to calculate it every time the script is called. - if err := tp.stor.scriptsComplexity.saveComplexitiesForAsset(tx.AssetID, *se, info.blockID); err != nil { - return errors.Wrapf(err, "failed to save script complexity for asset %q", tx.AssetID.String()) + snapshot, err := tp.snapshotGenerator.generateSnapshotForSetAssetScriptTx(tx.AssetID, tx.Script, balanceChanges, *se) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performInvokeScriptWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performInvokeScriptWithProofs(transaction proto.Transaction, + info *performerInfo, + _ *invocationResult, + balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.InvokeScriptWithProofs) if !ok { - return errors.New("failed to convert interface to InvokeScriptWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to InvokeScriptWithProofs transaction") } - if err := tp.stor.commitUncertain(info.blockID); err != nil { - return errors.Wrapf(err, "failed to commit invoke changes for tx %q", tx.ID.String()) - } - se := info.checkerData.scriptEstimation - if !se.isPresent() { // nothing to do, no estimation to save - return nil + txIDBytes, err := transaction.GetID(tp.settings.AddressSchemeCharacter) + if err != nil { + return txSnapshot{}, errors.Errorf("failed to get transaction ID: %v", err) } - // script estimation is present an not nil - - // we've pulled up an old script which estimation had been done by an old estimator - // in txChecker we've estimated script with a new estimator - // this is the place where we have to store new estimation - scriptAddr, err := recipientToAddress(tx.ScriptRecipient, tp.stor.aliases) + txID, err := crypto.NewDigestFromBytes(txIDBytes) if err != nil { - return errors.Wrap(err, "failed to get sender for InvokeScriptWithProofs") + return txSnapshot{}, err } - // update callable and summary complexity, verifier complexity remains the same - if scErr := tp.stor.scriptsComplexity.updateCallableComplexitiesForAddr(scriptAddr, *se, info.blockID); scErr != nil { - return errors.Wrapf(scErr, "failed to save complexity for addr %q in tx %q", - scriptAddr.String(), tx.ID.String(), - ) + se := info.checkerData.scriptEstimation + snapshot, err := tp.snapshotGenerator.generateSnapshotForInvokeScript(txID, tx.ScriptRecipient, balanceChanges, se) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performInvokeExpressionWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performInvokeExpressionWithProofs(transaction proto.Transaction, + _ *performerInfo, _ *invocationResult, + balanceChanges txDiff) (txSnapshot, error) { if _, ok := transaction.(*proto.InvokeExpressionTransactionWithProofs); !ok { - return errors.New("failed to convert interface to InvokeExpressionWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to InvokeExpressionWithProofs transaction") + } + txIDBytes, err := transaction.GetID(tp.settings.AddressSchemeCharacter) + if err != nil { + return txSnapshot{}, errors.Errorf("failed to get transaction ID: %v", err) } - if err := tp.stor.commitUncertain(info.blockID); err != nil { - return errors.Wrap(err, "failed to commit invoke changes") + txID, err := crypto.NewDigestFromBytes(txIDBytes) + if err != nil { + return txSnapshot{}, err + } + snapshot, err := tp.snapshotGenerator.generateSnapshotForInvokeExpressionTx(txID, balanceChanges) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performEthereumTransactionWithProofs(transaction proto.Transaction, info *performerInfo) error { - ethTx, ok := transaction.(*proto.EthereumTransaction) +func (tp *transactionPerformer) performEthereumTransactionWithProofs(transaction proto.Transaction, _ *performerInfo, + _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { + _, ok := transaction.(*proto.EthereumTransaction) if !ok { - return errors.New("failed to convert interface to EthereumTransaction transaction") + return txSnapshot{}, errors.New("failed to convert interface to EthereumTransaction transaction") } - if _, ok := ethTx.TxKind.(*proto.EthereumInvokeScriptTxKind); ok { - if err := tp.stor.commitUncertain(info.blockID); err != nil { - return errors.Wrap(err, "failed to commit invoke changes") - } + txIDBytes, err := transaction.GetID(tp.settings.AddressSchemeCharacter) + if err != nil { + return txSnapshot{}, errors.Errorf("failed to get transaction ID: %v", err) } - // nothing to do for proto.EthereumTransferWavesTxKind and proto.EthereumTransferAssetsErc20TxKind - return nil + txID, err := crypto.NewDigestFromBytes(txIDBytes) + if err != nil { + return txSnapshot{}, err + } + snapshot, err := tp.snapshotGenerator.generateSnapshotForEthereumInvokeScriptTx(txID, balanceChanges) + if err != nil { + return txSnapshot{}, err + } + return snapshot, snapshot.Apply(tp.snapshotApplier) } -func (tp *transactionPerformer) performUpdateAssetInfoWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performUpdateAssetInfoWithProofs(transaction proto.Transaction, + info *performerInfo, _ *invocationResult, balanceChanges txDiff) (txSnapshot, error) { tx, ok := transaction.(*proto.UpdateAssetInfoWithProofs) if !ok { - return errors.New("failed to convert interface to UpdateAssetInfoWithProofs transaction") + return txSnapshot{}, errors.New("failed to convert interface to UpdateAssetInfoWithProofs transaction") } blockHeight := info.height + 1 - ch := &assetInfoChange{ - newName: tx.Name, - newDescription: tx.Description, - newHeight: blockHeight, - } - if err := tp.stor.assets.updateAssetInfo(tx.AssetID, ch, info.blockID); err != nil { - return errors.Wrap(err, "failed to update asset info") + snapshot, err := tp.snapshotGenerator.generateSnapshotForUpdateAssetInfoTx(tx.AssetID, + tx.Name, tx.Description, blockHeight, balanceChanges) + if err != nil { + return txSnapshot{}, err } - return nil + return snapshot, snapshot.Apply(tp.snapshotApplier) } diff --git a/pkg/state/transaction_performer_test.go b/pkg/state/transaction_performer_test.go index 359320173..38815ae64 100644 --- a/pkg/state/transaction_performer_test.go +++ b/pkg/state/transaction_performer_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/wavesplatform/gowaves/pkg/crypto" "github.com/wavesplatform/gowaves/pkg/proto" "github.com/wavesplatform/gowaves/pkg/ride" "github.com/wavesplatform/gowaves/pkg/ride/serialization" @@ -16,27 +15,51 @@ import ( ) type performerTestObjects struct { - stor *testStorageObjects - tp *transactionPerformer + stor *testStorageObjects + tp *transactionPerformer + stateActionsCounter *proto.StateActionsCounter } -func createPerformerTestObjects(t *testing.T) *performerTestObjects { +func createPerformerTestObjects(t *testing.T, checkerInfo *checkerInfo) *performerTestObjects { stor := createStorageObjects(t, true) - tp, err := newTransactionPerformer(stor.entities, settings.MainNetSettings) - require.NoError(t, err, "newTransactionPerformer() failed") - return &performerTestObjects{stor, tp} + actionsCounter := new(proto.StateActionsCounter) + + snapshotApplier := newBlockSnapshotsApplier( + newBlockSnapshotsApplierInfo( + checkerInfo, + settings.MainNetSettings.AddressSchemeCharacter, + actionsCounter, + ), + newSnapshotApplierStorages(stor.entities), + ) + snapshotGen := newSnapshotGenerator(stor.entities, settings.MainNetSettings.AddressSchemeCharacter) + + tp := newTransactionPerformer(stor.entities, settings.MainNetSettings, &snapshotGen, &snapshotApplier) + + return &performerTestObjects{stor, tp, actionsCounter} } -func defaultPerformerInfo() *performerInfo { - return newPerformerInfo(0, new(proto.StateActionsCounter), blockID0, txCheckerData{}) +func defaultPerformerInfo(stateActionsCounter *proto.StateActionsCounter) *performerInfo { + _ = stateActionsCounter + return newPerformerInfo(0, stateActionsCounter, blockID0, proto.WavesAddress{}, txCheckerData{}) } -func TestPerformIssueWithSig(t *testing.T) { - to := createPerformerTestObjects(t) +func defaultCheckerInfoHeight0() *checkerInfo { + return &checkerInfo{ + currentTimestamp: defaultTimestamp, + parentTimestamp: defaultTimestamp - settings.MainNetSettings.MaxTxTimeBackOffset/2, + blockID: blockID0, + blockVersion: 1, + height: 0, + } +} +func TestPerformIssueWithSig(t *testing.T) { + checkerInfo := defaultCheckerInfoHeight0() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) tx := createIssueWithSig(t, 1000) - err := to.tp.performIssueWithSig(tx, defaultPerformerInfo()) + _, err := to.tp.performIssueWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performIssueWithSig() failed") to.stor.flush(t) expectedAssetInfo := assetInfo{ @@ -63,12 +86,12 @@ func TestPerformIssueWithSig(t *testing.T) { } func TestPerformIssueWithProofs(t *testing.T) { - to := createPerformerTestObjects(t) - + checkerInfo := defaultCheckerInfoHeight0() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) tx := createIssueWithProofs(t, 1000) - err := to.tp.performIssueWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performIssueWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performIssueWithProofs() failed") to.stor.flush(t) expectedAssetInfo := assetInfo{ @@ -95,11 +118,13 @@ func TestPerformIssueWithProofs(t *testing.T) { } func TestPerformReissueWithSig(t *testing.T) { - to := createPerformerTestObjects(t) + + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createReissueWithSig(t, 1000) - err := to.tp.performReissueWithSig(tx, defaultPerformerInfo()) + _, err := to.tp.performReissueWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performReissueWithSig() failed") to.stor.flush(t) assetInfo.reissuable = tx.Reissuable @@ -112,11 +137,12 @@ func TestPerformReissueWithSig(t *testing.T) { } func TestPerformReissueWithProofs(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createReissueWithProofs(t, 1000) - err := to.tp.performReissueWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performReissueWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performReissueWithProofs() failed") to.stor.flush(t) assetInfo.reissuable = tx.Reissuable @@ -129,11 +155,12 @@ func TestPerformReissueWithProofs(t *testing.T) { } func TestPerformBurnWithSig(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createBurnWithSig(t) - err := to.tp.performBurnWithSig(tx, defaultPerformerInfo()) + _, err := to.tp.performBurnWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performBurnWithSig() failed") to.stor.flush(t) assetInfo.quantity.Sub(&assetInfo.quantity, big.NewInt(int64(tx.Amount))) @@ -145,11 +172,12 @@ func TestPerformBurnWithSig(t *testing.T) { } func TestPerformBurnWithProofs(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createBurnWithProofs(t) - err := to.tp.performBurnWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performBurnWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performBurnWithProofs() failed") to.stor.flush(t) assetInfo.quantity.Sub(&assetInfo.quantity, big.NewInt(int64(tx.Amount))) @@ -161,60 +189,50 @@ func TestPerformBurnWithProofs(t *testing.T) { } func TestPerformExchange(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) tx := createExchangeWithSig(t) - err := to.tp.performExchange(tx, defaultPerformerInfo()) + _, err := to.tp.performExchange(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performExchange() failed") - sellOrderId, err := tx.GetOrder2().GetID() + sellOrderID, err := tx.GetOrder2().GetID() assert.NoError(t, err) - filledFee, err := to.stor.entities.ordersVolumes.newestFilledFee(sellOrderId) + filledAmount, filledFee, err := to.stor.entities.ordersVolumes.newestFilled(sellOrderID) assert.NoError(t, err) assert.Equal(t, tx.GetSellMatcherFee(), filledFee) - - filledAmount, err := to.stor.entities.ordersVolumes.newestFilledAmount(sellOrderId) - assert.NoError(t, err) assert.Equal(t, tx.GetAmount(), filledAmount) - buyOrderId, err := tx.GetOrder1().GetID() + buyOrderID, err := tx.GetOrder1().GetID() assert.NoError(t, err) - filledFee, err = to.stor.entities.ordersVolumes.newestFilledFee(buyOrderId) + filledAmount, filledFee, err = to.stor.entities.ordersVolumes.newestFilled(buyOrderID) assert.NoError(t, err) assert.Equal(t, tx.GetBuyMatcherFee(), filledFee) - - filledAmount, err = to.stor.entities.ordersVolumes.newestFilledAmount(buyOrderId) - assert.NoError(t, err) assert.Equal(t, tx.GetAmount(), filledAmount) to.stor.flush(t) - filledFee, err = to.stor.entities.ordersVolumes.newestFilledFee(sellOrderId) + filledAmount, filledFee, err = to.stor.entities.ordersVolumes.newestFilled(sellOrderID) assert.NoError(t, err) assert.Equal(t, tx.GetSellMatcherFee(), filledFee) - - filledAmount, err = to.stor.entities.ordersVolumes.newestFilledAmount(sellOrderId) - assert.NoError(t, err) assert.Equal(t, tx.GetAmount(), filledAmount) - filledFee, err = to.stor.entities.ordersVolumes.newestFilledFee(buyOrderId) + filledAmount, filledFee, err = to.stor.entities.ordersVolumes.newestFilled(buyOrderID) assert.NoError(t, err) assert.Equal(t, tx.GetBuyMatcherFee(), filledFee) - - filledAmount, err = to.stor.entities.ordersVolumes.newestFilledAmount(buyOrderId) - assert.NoError(t, err) assert.Equal(t, tx.GetAmount(), filledAmount) } func TestPerformLeaseWithSig(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) tx := createLeaseWithSig(t) - err := to.tp.performLeaseWithSig(tx, defaultPerformerInfo()) + _, err := to.tp.performLeaseWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performLeaseWithSig() failed") to.stor.flush(t) leasingInfo := &leasing{ @@ -231,11 +249,12 @@ func TestPerformLeaseWithSig(t *testing.T) { } func TestPerformLeaseWithProofs(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) tx := createLeaseWithProofs(t) - err := to.tp.performLeaseWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performLeaseWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performLeaseWithProofs() failed") to.stor.flush(t) leasingInfo := &leasing{ @@ -252,23 +271,24 @@ func TestPerformLeaseWithProofs(t *testing.T) { } func TestPerformLeaseCancelWithSig(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) leaseTx := createLeaseWithSig(t) - err := to.tp.performLeaseWithSig(leaseTx, defaultPerformerInfo()) + _, err := to.tp.performLeaseWithSig(leaseTx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performLeaseWithSig() failed") to.stor.flush(t) tx := createLeaseCancelWithSig(t, *leaseTx.ID) leasingInfo := &leasing{ OriginTransactionID: leaseTx.ID, - Status: LeaseCanceled, + Status: LeaseCancelled, Amount: leaseTx.Amount, Recipient: *leaseTx.Recipient.Address(), Sender: testGlobal.senderInfo.addr, CancelTransactionID: tx.ID, } - err = to.tp.performLeaseCancelWithSig(tx, defaultPerformerInfo()) + _, err = to.tp.performLeaseCancelWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performLeaseCancelWithSig() failed") to.stor.flush(t) info, err := to.stor.entities.leases.leasingInfo(*leaseTx.ID) @@ -277,23 +297,24 @@ func TestPerformLeaseCancelWithSig(t *testing.T) { } func TestPerformLeaseCancelWithProofs(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) leaseTx := createLeaseWithProofs(t) - err := to.tp.performLeaseWithProofs(leaseTx, defaultPerformerInfo()) + _, err := to.tp.performLeaseWithProofs(leaseTx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performLeaseWithProofs() failed") to.stor.flush(t) tx := createLeaseCancelWithProofs(t, *leaseTx.ID) leasingInfo := &leasing{ OriginTransactionID: leaseTx.ID, - Status: LeaseCanceled, + Status: LeaseCancelled, Amount: leaseTx.Amount, Recipient: *leaseTx.Recipient.Address(), Sender: testGlobal.senderInfo.addr, CancelTransactionID: tx.ID, } - err = to.tp.performLeaseCancelWithProofs(tx, defaultPerformerInfo()) + _, err = to.tp.performLeaseCancelWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performLeaseCancelWithProofs() failed") to.stor.flush(t) info, err := to.stor.entities.leases.leasingInfo(*leaseTx.ID) @@ -302,11 +323,12 @@ func TestPerformLeaseCancelWithProofs(t *testing.T) { } func TestPerformCreateAliasWithSig(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) tx := createCreateAliasWithSig(t) - err := to.tp.performCreateAliasWithSig(tx, defaultPerformerInfo()) + _, err := to.tp.performCreateAliasWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performCreateAliasWithSig() failed") to.stor.flush(t) addr, err := to.stor.entities.aliases.addrByAlias(tx.Alias.Alias) @@ -314,7 +336,7 @@ func TestPerformCreateAliasWithSig(t *testing.T) { assert.Equal(t, testGlobal.senderInfo.addr, addr, "invalid address by alias after performing CreateAliasWithSig transaction") // Test stealing aliases. - err = to.tp.performCreateAliasWithSig(tx, defaultPerformerInfo()) + _, err = to.tp.performCreateAliasWithSig(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performCreateAliasWithSig() failed") to.stor.flush(t) err = to.stor.entities.aliases.disableStolenAliases(blockID0) @@ -325,11 +347,12 @@ func TestPerformCreateAliasWithSig(t *testing.T) { } func TestPerformCreateAliasWithProofs(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) tx := createCreateAliasWithProofs(t) - err := to.tp.performCreateAliasWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performCreateAliasWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performCreateAliasWithProofs() failed") to.stor.flush(t) addr, err := to.stor.entities.aliases.addrByAlias(tx.Alias.Alias) @@ -337,7 +360,7 @@ func TestPerformCreateAliasWithProofs(t *testing.T) { assert.Equal(t, testGlobal.senderInfo.addr, addr, "invalid address by alias after performing CreateAliasWithProofs transaction") // Test stealing aliases. - err = to.tp.performCreateAliasWithProofs(tx, defaultPerformerInfo()) + _, err = to.tp.performCreateAliasWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performCreateAliasWithProofs() failed") to.stor.flush(t) err = to.stor.entities.aliases.disableStolenAliases(blockID0) @@ -348,7 +371,8 @@ func TestPerformCreateAliasWithProofs(t *testing.T) { } func TestPerformDataWithProofs(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) @@ -356,7 +380,7 @@ func TestPerformDataWithProofs(t *testing.T) { entry := &proto.IntegerDataEntry{Key: "TheKey", Value: int64(666)} tx.Entries = []proto.DataEntry{entry} - err := to.tp.performDataWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performDataWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performDataWithProofs() failed") to.stor.flush(t) @@ -366,12 +390,13 @@ func TestPerformDataWithProofs(t *testing.T) { } func TestPerformSponsorshipWithProofs(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) tx := createSponsorshipWithProofs(t, 1000) - err := to.tp.performSponsorshipWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performSponsorshipWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performSponsorshipWithProofs() failed") assetID := proto.AssetIDFromDigest(tx.AssetID) @@ -409,7 +434,8 @@ func TestPerformSponsorshipWithProofs(t *testing.T) { } func TestPerformSetScriptWithProofs(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) @@ -431,9 +457,10 @@ func TestPerformSetScriptWithProofs(t *testing.T) { require.NoError(t, err) tx := createSetScriptWithProofs(t, scriptBytes) - pi := *defaultPerformerInfo() + pi := *defaultPerformerInfo(to.stateActionsCounter) pi.checkerData.scriptEstimation = &scriptEstimation{} - err = to.tp.performSetScriptWithProofs(tx, &pi) + _, err = to.tp.performSetScriptWithProofs(tx, &pi, nil, nil) + assert.NoError(t, err, "performSetScriptWithProofs() failed") addr := testGlobal.senderInfo.addr @@ -497,12 +524,13 @@ func TestPerformSetScriptWithProofs(t *testing.T) { } func TestPerformSetAssetScriptWithProofs(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) to.stor.addBlock(t, blockID0) tx := createSetAssetScriptWithProofs(t) - pi := *defaultPerformerInfo() + pi := *defaultPerformerInfo(to.stateActionsCounter) currentEstimatorVersion := 4 tree, err := serialization.Parse(tx.Script) @@ -515,7 +543,8 @@ func TestPerformSetAssetScriptWithProofs(t *testing.T) { scriptIsEmpty: false, estimation: estimation, } - err = to.tp.performSetAssetScriptWithProofs(tx, &pi) + checkerInfo.blockID = blockID0 + _, err = to.tp.performSetAssetScriptWithProofs(tx, &pi, nil, nil) assert.NoError(t, err, "performSetAssetScriptWithProofs() failed") fullAssetID := tx.AssetID @@ -555,7 +584,7 @@ func TestPerformSetAssetScriptWithProofs(t *testing.T) { assert.Equal(t, testGlobal.scriptAst, scriptAst) // Test discarding script. - err = to.stor.entities.scriptsStorage.setAssetScript(fullAssetID, proto.Script{}, crypto.PublicKey{}, blockID0) + err = to.stor.entities.scriptsStorage.setAssetScript(fullAssetID, proto.Script{}, blockID0) assert.NoError(t, err, "setAssetScript() failed") // Test newest before flushing. @@ -591,11 +620,12 @@ func TestPerformSetAssetScriptWithProofs(t *testing.T) { } func TestPerformUpdateAssetInfoWithProofs(t *testing.T) { - to := createPerformerTestObjects(t) + checkerInfo := defaultCheckerInfo() + to := createPerformerTestObjects(t, checkerInfo) assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createUpdateAssetInfoWithProofs(t) - err := to.tp.performUpdateAssetInfoWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performUpdateAssetInfoWithProofs(tx, defaultPerformerInfo(to.stateActionsCounter), nil, nil) assert.NoError(t, err, "performUpdateAssetInfoWithProofs() failed") to.stor.flush(t) assetInfo.name = tx.Name diff --git a/pkg/state/tx_snapshot.go b/pkg/state/tx_snapshot.go new file mode 100644 index 000000000..f271298e6 --- /dev/null +++ b/pkg/state/tx_snapshot.go @@ -0,0 +1,37 @@ +package state + +import ( + "github.com/pkg/errors" + + "github.com/wavesplatform/gowaves/pkg/proto" +) + +type extendedSnapshotApplier interface { + SetApplierInfo(info *blockSnapshotsApplierInfo) + proto.SnapshotApplier + internalSnapshotApplier +} + +type txSnapshot struct { + regular []proto.AtomicSnapshot + internal []internalSnapshot +} + +func (ts txSnapshot) Apply(a extendedSnapshotApplier) error { + // internal snapshots must be applied at the end + for _, rs := range ts.regular { + if !rs.IsGeneratedByTxDiff() { + err := rs.Apply(a) + if err != nil { + return errors.Wrap(err, "failed to apply regular transaction snapshot") + } + } + } + for _, is := range ts.internal { + err := is.ApplyInternal(a) + if err != nil { + return errors.Wrap(err, "failed to apply internal transaction snapshot") + } + } + return nil +}