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 +}