diff --git a/pkg/state/appender.go b/pkg/state/appender.go index 65386d42f..da87caf22 100644 --- a/pkg/state/appender.go +++ b/pkg/state/appender.go @@ -319,49 +319,56 @@ 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) (TransactionSnapshot, 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 nil, 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)) + // TODO get balances snapshots + if err := a.diffStor.saveTxDiff(applicationRes.changes.diff); err != nil { + return nil, 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 TransactionSnapshot + if applicationRes.status { // We only perform tx in case it has not failed. performerInfo := &performerInfo{ height: params.checkerInfo.height, - stateActionsCounter: params.stateActionsCounterInBlock, blockID: params.checkerInfo.blockID, + currentMinerAddress: currentMinerAddress, + stateActionsCounter: params.stateActionsCounterInBlock, + checkerInfo: params.checkerInfo, // performer needs to know the estimator version which is stored in checker info } - if err := a.txHandler.performTx(tx, performerInfo); err != nil { - return wrapErr(TxCommitmentError, errors.Errorf("failed to perform: %v", err)) + // TODO other snapshots + snapshot, err = a.txHandler.performTx(tx, performerInfo, invocationRes, applicationRes) + if err != nil { + return nil, 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 nil, 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 nil, 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 nil, wrapErr(TxCommitmentError, errors.Errorf("failed to write transaction to storage: %v", err)) } } - return nil + return snapshot, nil } func (a *txAppender) verifyWavesTxSigAndData(tx proto.Transaction, params *appendTxParams, accountHasVerifierScript bool) error { @@ -405,18 +412,19 @@ 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) { @@ -470,13 +478,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") } @@ -507,7 +516,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) @@ -540,10 +549,17 @@ 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 + snapshot, 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 } + // a temporary dummy for linters + if len(snapshot) > 1000 { + zap.S().Debug(snapshot) + } // Store additional data for API: transaction by address. if !params.validatingUtx && a.buildApiData { if err := a.saveTransactionIdByAddresses(applicationRes.changes.addresses(), txID, blockID); err != nil { @@ -553,6 +569,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) (TransactionSnapshot, error) { + addrWavesBalanceDiff, _, err := addressBalanceDiffFromTxDiff(minerAndRewardDiff, a.settings.AddressSchemeCharacter) + if err != nil { + return nil, errors.Wrap(err, "failed to create balance diff from tx diff") + } + // add miner address to the diff + var snapshot TransactionSnapshot + for wavesAddress, diffAmount := range addrWavesBalanceDiff { + + fullBalance, err := a.stor.balances.wavesBalance(wavesAddress.ID()) + if err != nil { + return nil, errors.Wrap(err, "failed to receive sender's waves balance") + } + newBalance := &WavesBalanceSnapshot{ + Address: wavesAddress, + Balance: uint64(int64(fullBalance.balance) + diffAmount.balance), + } + snapshot = append(snapshot, newBalance) + } + return snapshot, nil +} + func (a *txAppender) appendBlock(params *appendBlockParams) error { // Reset block complexity counter. defer func() { @@ -588,12 +627,21 @@ func (a *txAppender) appendBlock(params *appendBlockParams) error { // 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 } + // create the initial snapshot + initialSnapshot, err := a.createInitialBlockSnapshot(minerAndRewardDiff) + if err != nil { + return errors.Wrap(err, "failed to create initial snapshot") + } + // a temporary dummy for linters + if len(initialSnapshot) > 100 { + zap.S().Debug(initialSnapshot) + } // Save miner diff first. - if err := a.diffStor.saveTxDiff(minerDiff); err != nil { + if err := a.diffStor.saveTxDiff(minerAndRewardDiff); err != nil { return err } blockInfo, err := a.currentBlockInfo() @@ -633,13 +681,14 @@ func (a *txAppender) appendBlock(params *appendBlockParams) error { invokeExpressionActivated: invokeExpressionActivated, validatingUtx: false, stateActionsCounterInBlock: stateActionsCounterInBlock, + 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 } @@ -669,7 +718,7 @@ type applicationResult struct { changes txBalanceChanges } -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: @@ -681,17 +730,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) { @@ -798,19 +847,20 @@ func (a *txAppender) handleExchange(tx proto.Transaction, info *fallibleValidati return &applicationResult{true, scriptsRuns, successfulChanges}, 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. 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 ab580e91d..29b62adb9 100644 --- a/pkg/state/block_differ_test.go +++ b/pkg/state/block_differ_test.go @@ -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/fee_validation_test.go b/pkg/state/fee_validation_test.go index 9c1f40fef..aeca6a518 100644 --- a/pkg/state/fee_validation_test.go +++ b/pkg/state/fee_validation_test.go @@ -67,7 +67,7 @@ func TestAccountHasVerifierAfterRollbackFilterFalse(t *testing.T) { assert.NoError(t, err, "failed to receive an address from public key") txPerformerInfo := &performerInfo{blockID: blockID2} - 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) @@ -99,7 +99,7 @@ func TestAccountDoesNotHaveScriptAfterRollbackFilterTrue(t *testing.T) { assert.NoError(t, err, "failed to receive an address from public key") txPerformerInfo := &performerInfo{blockID: blockID2} - 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) diff --git a/pkg/state/invoke_applier.go b/pkg/state/invoke_applier.go index 4ed473d67..8c92f45ea 100644 --- a/pkg/state/invoke_applier.go +++ b/pkg/state/invoke_applier.go @@ -729,7 +729,7 @@ func (ia *invokeApplier) fallibleValidation(tx proto.Transaction, info *addlInvo // 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) { +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() @@ -748,34 +748,34 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV var err error scriptAddr, err = recipientToAddress(transaction.ScriptRecipient, ia.stor.aliases) if err != nil { - return nil, errors.Wrap(err, "recipientToAddress() failed") + return nil, nil, errors.Wrap(err, "recipientToAddress() failed") } 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") + return nil, nil, errors.Wrapf(err, "failed to apply script invocation") } tree, err = ia.stor.scriptsStorage.newestScriptByAddr(scriptAddr) if err != nil { - return nil, errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptAddr.String()) + return nil, nil, errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptAddr.String()) } si, err := ia.stor.scriptsStorage.newestScriptBasicInfoByAddressID(scriptAddr.ID()) if err != nil { - return nil, errors.Wrapf(err, "failed to get script's public key on address '%s'", scriptAddr.String()) + return nil, nil, errors.Wrapf(err, "failed to get script's public key on address '%s'", scriptAddr.String()) } scriptPK = si.PK case *proto.InvokeExpressionTransactionWithProofs: addr, err := proto.NewAddressFromPublicKey(ia.settings.AddressSchemeCharacter, transaction.SenderPK) if err != nil { - return nil, errors.Wrap(err, "recipientToAddress() failed") + return nil, nil, errors.Wrap(err, "recipientToAddress() failed") } sender = addr scriptAddr = addr tree, err = serialization.Parse(transaction.Expression) if err != nil { - return nil, errors.Wrap(err, "failed to parse decoded invoke expression into tree") + return nil, nil, errors.Wrap(err, "failed to parse decoded invoke expression into tree") } txID = *transaction.ID scriptPK = transaction.SenderPK @@ -784,27 +784,27 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV var err error scriptAddr, err = transaction.WavesAddressTo(ia.settings.AddressSchemeCharacter) if err != nil { - return nil, err + return nil, 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") + return nil, nil, errors.Wrapf(err, "failed to apply script invocation") } tree, err = ia.stor.scriptsStorage.newestScriptByAddr(scriptAddr) if err != nil { - return nil, errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptAddr.String()) + return nil, nil, errors.Wrapf(err, "failed to instantiate script on address '%s'", scriptAddr.String()) } si, err := ia.stor.scriptsStorage.newestScriptBasicInfoByAddressID(scriptAddr.ID()) if err != nil { - return nil, errors.Wrapf(err, "failed to get script's public key on address '%s'", scriptAddr.String()) + return nil, nil, errors.Wrapf(err, "failed to get script's public key on address '%s'", scriptAddr.String()) } scriptPK = si.PK default: - return nil, errors.Errorf("failed to apply an invoke script: unexpected type of transaction (%T)", tx) + return nil, nil, errors.Errorf("failed to apply an invoke script: unexpected type of transaction (%T)", tx) } // If BlockV5 feature is not activated, we never accept failed transactions. @@ -813,32 +813,33 @@ 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. paymentSmartAssets, err := ia.txHandler.checkTx(tx, info.checkerInfo) if err != nil { - return nil, err + return nil, nil, err } // 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) + return nil, nil, errors.Errorf("multiple payments is not allowed for RIDE library version %d", 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") + 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. @@ -849,14 +850,15 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV isCheap := int(ia.sc.recentTxComplexity) <= FailFreeInvokeComplexity if info.rideV6Activated { if !info.acceptFailed || isCheap { - return nil, errors.Wrapf( + 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"), ) } - res := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} - return ia.handleInvocationResult(txID, info, res) + invocationRes := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} + applicationRes, err := ia.handleInvocationResult(txID, info, invocationRes) + 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 @@ -867,31 +869,35 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV // 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( + 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"), ) } - res := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} - return ia.handleInvocationResult(txID, info, res) + + invocationRes := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} + applicationRes, err := ia.handleInvocationResult(txID, info, invocationRes) + 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, errors.Wrapf( + 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"), ) } - res := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} - return ia.handleInvocationResult(txID, info, res) + + invocationRes := &invocationResult{failed: true, code: proto.DAppError, text: err.Error(), changes: failedChanges} + applicationRes, err := ia.handleInvocationResult(txID, info, invocationRes) + return invocationRes, applicationRes, err case ride.Undefined, ride.EvaluationFailure: // Unhandled or evaluator error - return nil, errors.Wrapf(err, "invocation of transaction '%s' failed", txID.String()) + return nil, nil, errors.Wrapf(err, "invocation of transaction '%s' failed", txID.String()) default: - return nil, errors.Wrapf(err, "invocation of transaction '%s' failed", txID.String()) + return nil, nil, errors.Wrapf(err, "invocation of transaction '%s' failed", txID.String()) } } var scriptRuns uint64 = 0 @@ -899,7 +905,7 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV if !info.rideV5Activated { actionScriptRuns, err := ia.countActionScriptRuns(r.ScriptActions()) if err != nil { - return nil, errors.Wrap(err, "failed to countActionScriptRuns") + return nil, nil, errors.Wrap(err, "failed to countActionScriptRuns") } scriptRuns += uint64(len(paymentSmartAssets)) + actionScriptRuns } @@ -908,7 +914,7 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV if info.rideV5Activated { treeEstimation, err := ia.stor.scriptsComplexity.newestScriptComplexityByAddr(info.senderAddress, info.checkerInfo.estimatorVersion()) if err != nil { - return nil, errors.Wrap(err, "invoke failed to get verifier complexity") + return nil, nil, errors.Wrap(err, "invoke failed to get verifier complexity") } if treeEstimation.Verifier > FreeVerifierComplexity { scriptRuns++ @@ -917,7 +923,7 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV scriptRuns++ } } - var res *invocationResult + var invocationRes *invocationResult code, changes, err := ia.fallibleValidation(tx, &addlInvokeInfo{ fallibleValidationParams: info, scriptAddr: scriptAddr, @@ -935,9 +941,9 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV if !info.acceptFailed || (ia.sc.recentTxComplexity <= FailFreeInvokeComplexity && info.checkerInfo.height >= ia.settings.InternalInvokeCorrectFailRejectBehaviourAfterHeight) { - return nil, err + return nil, nil, err } - res = &invocationResult{ + invocationRes = &invocationResult{ failed: true, code: code, text: err.Error(), @@ -946,14 +952,16 @@ func (ia *invokeApplier) applyInvokeScript(tx proto.Transaction, info *fallibleV changes: changes, } } else { - res = &invocationResult{ + invocationRes = &invocationResult{ failed: false, scriptRuns: scriptRuns, actions: r.ScriptActions(), changes: changes, } } - return ia.handleInvocationResult(txID, info, res) + + applicationRes, err := ia.handleInvocationResult(txID, info, invocationRes) + return invocationRes, applicationRes, err } type invocationResult struct { diff --git a/pkg/state/invoke_applier_test.go b/pkg/state/invoke_applier_test.go index 114890419..d9d044267 100644 --- a/pkg/state/invoke_applier_test.go +++ b/pkg/state/invoke_applier_test.go @@ -129,15 +129,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) { @@ -195,7 +195,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 } diff --git a/pkg/state/transaction_checker_test.go b/pkg/state/transaction_checker_test.go index dfafe5333..aab51f692 100644 --- a/pkg/state/transaction_checker_test.go +++ b/pkg/state/transaction_checker_test.go @@ -242,7 +242,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(), nil, nil) assert.NoError(t, err, "performReissueWithSig failed") to.stor.addBlock(t, blockID0) to.stor.flush(t) @@ -289,7 +289,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(), nil, nil) assert.NoError(t, err, "performReissueWithProofs failed") to.stor.addBlock(t, blockID0) to.stor.flush(t) @@ -611,7 +611,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(), nil, nil) assert.NoError(t, err, "performLeaseWithSig failed") to.stor.flush(t) @@ -639,7 +639,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(), nil, nil) assert.NoError(t, err, "performLeaseWithProofs failed") to.stor.flush(t) @@ -655,7 +655,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(), nil, nil) assert.NoError(t, err, "performLeaseCancelWithProofs() failed") _, err = to.tc.checkLeaseCancelWithProofs(tx, info) @@ -672,7 +672,7 @@ func TestCheckCreateAliasWithSig(t *testing.T) { 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(), nil, nil) assert.NoError(t, err, "performCreateAliasWithSig failed") to.stor.flush(t) @@ -700,7 +700,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(), nil, nil) assert.NoError(t, err, "performCreateAliasWithProofs failed") to.stor.flush(t) diff --git a/pkg/state/transaction_differ_test.go b/pkg/state/transaction_differ_test.go index 317b6afdf..c1c4c51c5 100644 --- a/pkg/state/transaction_differ_test.go +++ b/pkg/state/transaction_differ_test.go @@ -865,7 +865,7 @@ func TestCreateDiffLeaseCancelWithSig(t *testing.T) { leaseTx := createLeaseWithSig(t) info := defaultPerformerInfo() 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) @@ -898,7 +898,7 @@ func TestCreateDiffLeaseCancelWithProofs(t *testing.T) { leaseTx := createLeaseWithProofs(t) info := defaultPerformerInfo() 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) diff --git a/pkg/state/transaction_handler.go b/pkg/state/transaction_handler.go index 33a3c686e..16ce0b524 100644 --- a/pkg/state/transaction_handler.go +++ b/pkg/state/transaction_handler.go @@ -8,7 +8,7 @@ import ( ) type txCheckFunc func(proto.Transaction, *checkerInfo) ([]crypto.Digest, error) -type txPerformFunc func(proto.Transaction, *performerInfo) error +type txPerformFunc func(proto.Transaction, *performerInfo, *invocationResult, *applicationResult) (TransactionSnapshot, error) type txCreateDiffFunc func(proto.Transaction, *differInfo) (txBalanceChanges, error) type txCountFeeFunc func(proto.Transaction, *feeDistribution) error @@ -31,19 +31,20 @@ 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, @@ -88,7 +89,7 @@ func buildHandles(tc *transactionChecker, tp *transactionPerformer, td *transact 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, @@ -154,17 +155,18 @@ func (h *transactionHandler) checkTx(tx proto.Transaction, info *checkerInfo) ([ 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, applicationRes *applicationResult) (TransactionSnapshot, 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 nil, errors.Errorf("No function handler implemented for tx struct type %T\n", tx) } if funcs.perform == nil { // No perform func for this combination of transaction type and version. - return nil + return nil, nil } - return funcs.perform(tx, info) + + return funcs.perform(tx, info, invocationRes, applicationRes) } 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 152d92a51..c5e7992a0 100644 --- a/pkg/state/transaction_performer.go +++ b/pkg/state/transaction_performer.go @@ -11,8 +11,10 @@ import ( type performerInfo struct { height uint64 - stateActionsCounter *proto.StateActionsCounter blockID proto.BlockID + currentMinerAddress proto.WavesAddress + stateActionsCounter *proto.StateActionsCounter + checkerInfo *checkerInfo } type transactionPerformer struct { @@ -24,7 +26,43 @@ func newTransactionPerformer(stor *blockchainEntitiesStorage, settings *settings return &transactionPerformer{stor, settings}, nil } -func (tp *transactionPerformer) performIssue(tx *proto.Issue, assetID crypto.Digest, info *performerInfo) error { +func (tp *transactionPerformer) performGenesis(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { + _, ok := transaction.(*proto.Genesis) + if !ok { + return nil, errors.New("failed to convert interface to genesis transaction") + } + return tp.generateSnapshotForGenesisTx(applicationRes) +} + +func (tp *transactionPerformer) performPayment(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { + _, ok := transaction.(*proto.Payment) + if !ok { + return nil, errors.New("failed to convert interface to payment transaction") + } + return tp.generateSnapshotForPaymentTx(applicationRes) +} + +func (tp *transactionPerformer) performTransfer(applicationRes *applicationResult) (TransactionSnapshot, error) { + return tp.generateSnapshotForTransferTx(applicationRes) +} + +func (tp *transactionPerformer) performTransferWithSig(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { + _, ok := transaction.(*proto.TransferWithSig) + if !ok { + return nil, errors.New("failed to convert interface to transfer with sig transaction") + } + return tp.performTransfer(applicationRes) +} + +func (tp *transactionPerformer) performTransferWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { + _, ok := transaction.(*proto.TransferWithProofs) + if !ok { + return nil, errors.New("failed to convert interface to transfer with proofs transaction") + } + return tp.performTransfer(applicationRes) +} + +func (tp *transactionPerformer) performIssue(tx *proto.Issue, txID crypto.Digest, assetID crypto.Digest, info *performerInfo, applicationRes *applicationResult) (TransactionSnapshot, error) { blockHeight := info.height + 1 // Create new asset. assetInfo := &assetInfo{ @@ -43,149 +81,177 @@ func (tp *transactionPerformer) performIssue(tx *proto.Issue, assetID crypto.Dig reissuable: tx.Reissuable, }, } + + snapshot, err := tp.generateSnapshotForIssueTx(assetID, txID, tx.SenderPK, *assetInfo, applicationRes) + if err != nil { + return nil, err + } + if err := tp.stor.assets.issueAsset(proto.AssetIDFromDigest(assetID), assetInfo, info.blockID); err != nil { - return errors.Wrap(err, "failed to issue asset") + return nil, errors.Wrap(err, "failed to issue asset") } - return nil + + return snapshot, nil } -func (tp *transactionPerformer) performIssueWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performIssueWithSig(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.IssueWithSig) if !ok { - return errors.New("failed to convert interface to IssueWithSig transaction") + return nil, 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 nil, errors.Errorf("failed to get transaction ID: %v\n", err) } assetID, err := crypto.NewDigestFromBytes(txID) if err != nil { - return err + return nil, err } if err := tp.stor.scriptsStorage.setAssetScript(assetID, proto.Script{}, tx.SenderPK, info.blockID); err != nil { - return err + return nil, err } - return tp.performIssue(&tx.Issue, assetID, info) + return tp.performIssue(&tx.Issue, assetID, assetID, info, applicationRes) } -func (tp *transactionPerformer) performIssueWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performIssueWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.IssueWithProofs) if !ok { - return errors.New("failed to convert interface to IssueWithProofs transaction") + return nil, 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 nil, errors.Errorf("failed to get transaction ID: %v\n", err) } assetID, err := crypto.NewDigestFromBytes(txID) if err != nil { - return err + return nil, err } if err := tp.stor.scriptsStorage.setAssetScript(assetID, tx.Script, tx.SenderPK, info.blockID); err != nil { - return err + return nil, err } - return tp.performIssue(&tx.Issue, assetID, info) + return tp.performIssue(&tx.Issue, assetID, assetID, info, applicationRes) } -func (tp *transactionPerformer) performReissue(tx *proto.Reissue, info *performerInfo) error { +func (tp *transactionPerformer) performReissue(tx *proto.Reissue, info *performerInfo, applicationRes *applicationResult) (TransactionSnapshot, error) { // Modify asset. change := &assetReissueChange{ reissuable: tx.Reissuable, diff: int64(tx.Quantity), } + + snapshot, err := tp.generateSnapshotForReissueTx(tx.AssetID, *change, applicationRes) + if err != nil { + return nil, err + } + if err := tp.stor.assets.reissueAsset(proto.AssetIDFromDigest(tx.AssetID), change, info.blockID); err != nil { - return errors.Wrap(err, "failed to reissue asset") + return nil, errors.Wrap(err, "failed to reissue asset") } - return nil + return snapshot, nil } -func (tp *transactionPerformer) performReissueWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performReissueWithSig(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.ReissueWithSig) if !ok { - return errors.New("failed to convert interface to ReissueWithSig transaction") + return nil, errors.New("failed to convert interface to ReissueWithSig transaction") } - return tp.performReissue(&tx.Reissue, info) + return tp.performReissue(&tx.Reissue, info, applicationRes) } -func (tp *transactionPerformer) performReissueWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performReissueWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.ReissueWithProofs) if !ok { - return errors.New("failed to convert interface to ReissueWithProofs transaction") + return nil, errors.New("failed to convert interface to ReissueWithProofs transaction") } - return tp.performReissue(&tx.Reissue, info) + return tp.performReissue(&tx.Reissue, info, applicationRes) } -func (tp *transactionPerformer) performBurn(tx *proto.Burn, info *performerInfo) error { +func (tp *transactionPerformer) performBurn(tx *proto.Burn, info *performerInfo, applicationRes *applicationResult) (TransactionSnapshot, error) { // Modify asset. change := &assetBurnChange{ diff: int64(tx.Amount), } + + snapshot, err := tp.generateSnapshotForBurnTx(tx.AssetID, *change, applicationRes) + if err != nil { + return nil, err + } + if err := tp.stor.assets.burnAsset(proto.AssetIDFromDigest(tx.AssetID), change, info.blockID); err != nil { - return errors.Wrap(err, "failed to burn asset") + return nil, errors.Wrap(err, "failed to burn asset") } - return nil + + return snapshot, nil } -func (tp *transactionPerformer) performBurnWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performBurnWithSig(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.BurnWithSig) if !ok { - return errors.New("failed to convert interface to BurnWithSig transaction") + return nil, errors.New("failed to convert interface to BurnWithSig transaction") } - return tp.performBurn(&tx.Burn, info) + return tp.performBurn(&tx.Burn, info, applicationRes) } -func (tp *transactionPerformer) performBurnWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performBurnWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.BurnWithProofs) if !ok { - return errors.New("failed to convert interface to BurnWithProofs transaction") + return nil, errors.New("failed to convert interface to BurnWithProofs transaction") } - return tp.performBurn(&tx.Burn, info) + return tp.performBurn(&tx.Burn, info, applicationRes) } -func (tp *transactionPerformer) increaseOrderVolume(order proto.Order, tx proto.Exchange, info *performerInfo) error { +func (tp *transactionPerformer) increaseOrderVolume(order proto.Order, fee uint64, volume uint64, info *performerInfo) error { orderID, err := order.GetID() if err != nil { return err } - fee := tx.GetBuyMatcherFee() - if order.GetOrderType() == proto.Sell { - fee = tx.GetSellMatcherFee() - } - return tp.stor.ordersVolumes.increaseFilled(orderID, tx.GetAmount(), fee, info.blockID) + return tp.stor.ordersVolumes.increaseFilled(orderID, volume, fee, info.blockID) } -func (tp *transactionPerformer) performExchange(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performExchange(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(proto.Exchange) if !ok { - return errors.New("failed to convert interface to Exchange transaction") + return nil, 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") + return nil, errors.Wrap(err, "no sell order") } - if err := tp.increaseOrderVolume(so, tx, info); err != nil { - return err + buyOrder, err := tx.GetBuyOrder() + if err != nil { + return nil, errors.Wrap(err, "no buy order") } - bo, err := tx.GetBuyOrder() + volume := tx.GetAmount() + sellFee := tx.GetSellMatcherFee() + buyFee := tx.GetBuyMatcherFee() + + // snapshot must be generated before the state with orders is changed + snapshot, err := tp.generateSnapshotForExchangeTx(sellOrder, sellFee, buyOrder, buyFee, volume, applicationRes) if err != nil { - return errors.Wrap(err, "no buy order") + return nil, err } - if err := tp.increaseOrderVolume(bo, tx, info); err != nil { - return err + + err = tp.increaseOrderVolume(sellOrder, sellFee, volume, info) + if err != nil { + return nil, err + } + err = tp.increaseOrderVolume(buyOrder, buyFee, volume, info) + if err != nil { + return nil, err } - return nil + return snapshot, nil } -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, applicationRes *applicationResult) (TransactionSnapshot, error) { senderAddr, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, tx.SenderPK) if err != nil { - return err + return nil, 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 nil, errors.Errorf("invalid alias: %v\n", err) } } else { recipientAddr = *addr @@ -198,171 +264,319 @@ func (tp *transactionPerformer) performLease(tx *proto.Lease, id *crypto.Digest, 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") + snapshot, err := tp.generateSnapshotForLeaseTx(*l, txID, txID, applicationRes) + if err != nil { + return nil, nil + } + + if err := tp.stor.leases.addLeasing(txID, l, info.blockID); err != nil { + return nil, errors.Wrap(err, "failed to add leasing") } - return nil + return snapshot, nil } -func (tp *transactionPerformer) performLeaseWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performLeaseWithSig(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.LeaseWithSig) if !ok { - return errors.New("failed to convert interface to LeaseWithSig transaction") + return nil, 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, applicationRes) } -func (tp *transactionPerformer) performLeaseWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performLeaseWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.LeaseWithProofs) if !ok { - return errors.New("failed to convert interface to LeaseWithProofs transaction") + return nil, 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, applicationRes) } -func (tp *transactionPerformer) performLeaseCancel(tx *proto.LeaseCancel, txID *crypto.Digest, info *performerInfo) error { +func (tp *transactionPerformer) performLeaseCancel(tx *proto.LeaseCancel, txID *crypto.Digest, info *performerInfo, applicationRes *applicationResult) (TransactionSnapshot, error) { + oldLease, err := tp.stor.leases.newestLeasingInfo(tx.LeaseID) + if err != nil { + return nil, errors.Wrap(err, "failed to receiver leasing info") + } + + snapshot, err := tp.generateSnapshotForLeaseCancelTx(txID, *oldLease, tx.LeaseID, *oldLease.OriginTransactionID, info.height, applicationRes) + if err != nil { + return nil, err + } if err := tp.stor.leases.cancelLeasing(tx.LeaseID, info.blockID, info.height, txID); err != nil { - return errors.Wrap(err, "failed to cancel leasing") + return nil, errors.Wrap(err, "failed to cancel leasing") } - return nil + return snapshot, nil } -func (tp *transactionPerformer) performLeaseCancelWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performLeaseCancelWithSig(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.LeaseCancelWithSig) if !ok { - return errors.New("failed to convert interface to LeaseCancelWithSig transaction") + return nil, 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, applicationRes) } -func (tp *transactionPerformer) performLeaseCancelWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performLeaseCancelWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.LeaseCancelWithProofs) if !ok { - return errors.New("failed to convert interface to LeaseCancelWithProofs transaction") + return nil, 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, applicationRes) } -func (tp *transactionPerformer) performCreateAlias(tx *proto.CreateAlias, info *performerInfo) error { +func (tp *transactionPerformer) performCreateAlias(tx *proto.CreateAlias, info *performerInfo, applicationRes *applicationResult) (TransactionSnapshot, error) { senderAddr, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, tx.SenderPK) if err != nil { - return err + return nil, err + } + + snapshot, err := tp.generateSnapshotForCreateAliasTx(senderAddr, tx.Alias, applicationRes) + if err != nil { + return nil, err } - // Save alias to aliases storage. if err := tp.stor.aliases.createAlias(tx.Alias.Alias, senderAddr, info.blockID); err != nil { - return err + return nil, err } - return nil + return snapshot, nil } -func (tp *transactionPerformer) performCreateAliasWithSig(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performCreateAliasWithSig(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.CreateAliasWithSig) if !ok { - return errors.New("failed to convert interface to CreateAliasWithSig transaction") + return nil, errors.New("failed to convert interface to CreateAliasWithSig transaction") } - return tp.performCreateAlias(&tx.CreateAlias, info) + return tp.performCreateAlias(&tx.CreateAlias, info, applicationRes) } -func (tp *transactionPerformer) performCreateAliasWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performCreateAliasWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.CreateAliasWithProofs) if !ok { - return errors.New("failed to convert interface to CreateAliasWithProofs transaction") + return nil, errors.New("failed to convert interface to CreateAliasWithProofs transaction") + } + return tp.performCreateAlias(&tx.CreateAlias, info, applicationRes) +} + +func (tp *transactionPerformer) performMassTransferWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { + _, ok := transaction.(*proto.MassTransferWithProofs) + if !ok { + return nil, errors.New("failed to convert interface to CreateAliasWithProofs transaction") } - return tp.performCreateAlias(&tx.CreateAlias, info) + return tp.generateSnapshotForMassTransferTx(applicationRes) } -func (tp *transactionPerformer) performDataWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performDataWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { + tx, ok := transaction.(*proto.DataWithProofs) if !ok { - return errors.New("failed to convert interface to DataWithProofs transaction") + return nil, errors.New("failed to convert interface to DataWithProofs transaction") } senderAddr, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, tx.SenderPK) if err != nil { - return err + return nil, err + } + + snapshot, err := tp.generateSnapshotForDataTx(senderAddr, tx.Entries, applicationRes) + if err != nil { + return nil, err } for _, entry := range tx.Entries { if err := tp.stor.accountsDataStor.appendEntry(senderAddr, entry, info.blockID); err != nil { - return err + return nil, err } } - return nil + return snapshot, nil } -func (tp *transactionPerformer) performSponsorshipWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performSponsorshipWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.SponsorshipWithProofs) if !ok { - return errors.New("failed to convert interface to SponsorshipWithProofs transaction") + return nil, errors.New("failed to convert interface to SponsorshipWithProofs transaction") + } + + snapshot, err := tp.generateSnapshotForSponsorshipTx(tx.AssetID, tx.MinAssetFee, applicationRes) + if err != nil { + return nil, err } if err := tp.stor.sponsoredAssets.sponsorAsset(tx.AssetID, tx.MinAssetFee, info.blockID); err != nil { - return errors.Wrap(err, "failed to sponsor asset") + return nil, errors.Wrap(err, "failed to sponsor asset") } - return nil + return snapshot, nil } -func (tp *transactionPerformer) performSetScriptWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performSetScriptWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.SetScriptWithProofs) if !ok { - return errors.New("failed to convert interface to SetScriptWithProofs transaction") + return nil, errors.New("failed to convert interface to SetScriptWithProofs transaction") } senderAddr, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, tx.SenderPK) if err != nil { - return err + return nil, err + } + + snapshot, err := tp.generateSnapshotForSetScriptTx(senderAddr, tx.SenderPK, tx.Script, info, applicationRes) + if err != nil { + return nil, err } if err := tp.stor.scriptsStorage.setAccountScript(senderAddr, tx.Script, tx.SenderPK, info.blockID); err != nil { - return errors.Wrap(err, "failed to set account script") + return nil, errors.Wrap(err, "failed to set account script") } - return nil + return snapshot, nil } -func (tp *transactionPerformer) performSetAssetScriptWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performSetAssetScriptWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.SetAssetScriptWithProofs) if !ok { - return errors.New("failed to convert interface to SetAssetScriptWithProofs transaction") + return nil, errors.New("failed to convert interface to SetAssetScriptWithProofs transaction") } + + snapshot, err := tp.generateSnapshotForSetAssetScriptTx(tx.AssetID, tx.Script, applicationRes) + if err != nil { + return nil, err + } + 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") + return nil, errors.Wrap(err, "failed to set asset script") + } + return snapshot, nil +} + +func addToWavesBalanceDiff(addrWavesBalanceDiff addressWavesBalanceDiff, + senderAddress proto.WavesAddress, + recipientAddress proto.WavesAddress, + amount int64) { + if _, ok := addrWavesBalanceDiff[senderAddress]; ok { + prevBalance := addrWavesBalanceDiff[senderAddress] + prevBalance.balance -= amount + addrWavesBalanceDiff[senderAddress] = prevBalance + } else { + addrWavesBalanceDiff[senderAddress] = balanceDiff{balance: amount} + } + + if _, ok := addrWavesBalanceDiff[recipientAddress]; ok { + prevRecipientBalance := addrWavesBalanceDiff[recipientAddress] + prevRecipientBalance.balance += amount + addrWavesBalanceDiff[recipientAddress] = prevRecipientBalance + } else { + addrWavesBalanceDiff[recipientAddress] = balanceDiff{balance: amount} + } +} + +// subtracts the amount from the sender's balance and add it to the recipient's balane +func addSenderRecipientToAssetBalanceDiff(addrAssetBalanceDiff addressAssetBalanceDiff, + senderAddress proto.WavesAddress, + recipientAddress proto.WavesAddress, + asset proto.AssetID, + amount int64) { + keySender := assetBalanceDiffKey{address: senderAddress, asset: asset} + keyRecipient := assetBalanceDiffKey{address: recipientAddress, asset: asset} + + if _, ok := addrAssetBalanceDiff[keySender]; ok { + prevSenderBalance := addrAssetBalanceDiff[keySender] + prevSenderBalance -= amount + addrAssetBalanceDiff[keySender] = prevSenderBalance + } else { + addrAssetBalanceDiff[keySender] = amount + } + + if _, ok := addrAssetBalanceDiff[keyRecipient]; ok { + prevRecipientBalance := addrAssetBalanceDiff[keyRecipient] + prevRecipientBalance += amount + addrAssetBalanceDiff[keyRecipient] = prevRecipientBalance + } else { + addrAssetBalanceDiff[keyRecipient] = amount + } +} + +// adds/subtracts the amount to the sender balance +func addSenderToAssetBalanceDiff(addrAssetBalanceDiff addressAssetBalanceDiff, + senderAddress proto.WavesAddress, + asset proto.AssetID, + amount int64) { + keySender := assetBalanceDiffKey{address: senderAddress, asset: asset} + + if _, ok := addrAssetBalanceDiff[keySender]; ok { + prevSenderBalance := addrAssetBalanceDiff[keySender] + prevSenderBalance += amount + addrAssetBalanceDiff[keySender] = prevSenderBalance + } else { + addrAssetBalanceDiff[keySender] = amount } - return nil + } -func (tp *transactionPerformer) performInvokeScriptWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performInvokeScriptWithProofs(transaction proto.Transaction, info *performerInfo, invocationRes *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { if _, ok := transaction.(*proto.InvokeScriptWithProofs); !ok { - return errors.New("failed to convert interface to InvokeScriptWithProofs transaction") + return nil, errors.New("failed to convert interface to InvokeScriptWithProofs transaction") } if err := tp.stor.commitUncertain(info.blockID); err != nil { - return errors.Wrap(err, "failed to commit invoke changes") + return nil, errors.Wrap(err, "failed to commit invoke changes") + } + txIDBytes, err := transaction.GetID(tp.settings.AddressSchemeCharacter) + if err != nil { + return nil, errors.Errorf("failed to get transaction ID: %v\n", err) + } + txID, err := crypto.NewDigestFromBytes(txIDBytes) + if err != nil { + return nil, err + } + + snapshot, err := tp.generateSnapshotForInvokeScriptTx(txID, info, invocationRes, applicationRes) + if err != nil { + return nil, errors.Wrap(err, "failed to generate a snapshot for an invoke transaction") } - return nil + + return snapshot, nil } -func (tp *transactionPerformer) performInvokeExpressionWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performInvokeExpressionWithProofs(transaction proto.Transaction, info *performerInfo, invocationRes *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { if _, ok := transaction.(*proto.InvokeExpressionTransactionWithProofs); !ok { - return errors.New("failed to convert interface to InvokeExpressionWithProofs transaction") + return nil, errors.New("failed to convert interface to InvokeExpressionWithProofs transaction") } if err := tp.stor.commitUncertain(info.blockID); err != nil { - return errors.Wrap(err, "failed to commit invoke changes") + return nil, errors.Wrap(err, "failed to commit invoke changes") } - return nil + txIDBytes, err := transaction.GetID(tp.settings.AddressSchemeCharacter) + if err != nil { + return nil, errors.Errorf("failed to get transaction ID: %v\n", err) + } + txID, err := crypto.NewDigestFromBytes(txIDBytes) + if err != nil { + return nil, err + } + + return tp.generateSnapshotForInvokeExpressionTx(txID, info, invocationRes, applicationRes) } -func (tp *transactionPerformer) performEthereumTransactionWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performEthereumTransactionWithProofs(transaction proto.Transaction, info *performerInfo, invocationRes *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { ethTx, ok := transaction.(*proto.EthereumTransaction) if !ok { - return errors.New("failed to convert interface to EthereumTransaction transaction") + return nil, 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") + return nil, errors.Wrap(err, "failed to commit invoke changes") } } - // nothing to do for proto.EthereumTransferWavesTxKind and proto.EthereumTransferAssetsErc20TxKind - return nil + txIDBytes, err := transaction.GetID(tp.settings.AddressSchemeCharacter) + if err != nil { + return nil, errors.Errorf("failed to get transaction ID: %v\n", err) + } + txID, err := crypto.NewDigestFromBytes(txIDBytes) + if err != nil { + return nil, err + } + + snapshot, err := tp.generateSnapshotForEthereumInvokeScriptTx(txID, info, invocationRes, applicationRes) + if err != nil { + return nil, errors.Wrap(err, "failed to generate a snapshot for an invoke transaction") + } + + return snapshot, nil } -func (tp *transactionPerformer) performUpdateAssetInfoWithProofs(transaction proto.Transaction, info *performerInfo) error { +func (tp *transactionPerformer) performUpdateAssetInfoWithProofs(transaction proto.Transaction, info *performerInfo, _ *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { tx, ok := transaction.(*proto.UpdateAssetInfoWithProofs) if !ok { - return errors.New("failed to convert interface to UpdateAssetInfoWithProofs transaction") + return nil, errors.New("failed to convert interface to UpdateAssetInfoWithProofs transaction") } blockHeight := info.height + 1 ch := &assetInfoChange{ @@ -370,8 +584,13 @@ func (tp *transactionPerformer) performUpdateAssetInfoWithProofs(transaction pro newDescription: tx.Description, newHeight: blockHeight, } + + snapshot, err := tp.generateSnapshotForUpdateAssetInfoTx(tx.AssetID, tx.Name, tx.Description, blockHeight, applicationRes) + if err != nil { + return nil, err + } if err := tp.stor.assets.updateAssetInfo(tx.AssetID, ch, info.blockID); err != nil { - return errors.Wrap(err, "failed to update asset info") + return nil, errors.Wrap(err, "failed to update asset info") } - return nil + return snapshot, nil } diff --git a/pkg/state/transaction_performer_snapshots.go b/pkg/state/transaction_performer_snapshots.go new file mode 100644 index 000000000..c2a1383f3 --- /dev/null +++ b/pkg/state/transaction_performer_snapshots.go @@ -0,0 +1,745 @@ +package state + +import ( + "github.com/pkg/errors" + "github.com/wavesplatform/gowaves/pkg/crypto" + "github.com/wavesplatform/gowaves/pkg/proto" + "math/big" +) + +type assetBalanceDiffKey struct { + address proto.WavesAddress + asset proto.AssetID +} + +type addressWavesBalanceDiff map[proto.WavesAddress]balanceDiff +type addressAssetBalanceDiff map[assetBalanceDiffKey]int64 + +func (tp *transactionPerformer) generateSnapshotForGenesisTx(applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + return tp.generateBalancesSnapshot(applicationRes) +} + +func (tp *transactionPerformer) generateSnapshotForPaymentTx(applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + return tp.generateBalancesSnapshot(applicationRes) +} + +func (tp *transactionPerformer) generateSnapshotForTransferTx(applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + return tp.generateBalancesSnapshot(applicationRes) +} + +func (tp *transactionPerformer) generateSnapshotForIssueTx(assetID crypto.Digest, txID crypto.Digest, senderPK crypto.PublicKey, assetInfo assetInfo, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + var snapshot TransactionSnapshot + + addrWavesBalanceDiff, addrAssetBalanceDiff, err := addressBalanceDiffFromTxDiff(applicationRes.changes.diff, tp.settings.AddressSchemeCharacter) + if err != nil { + return nil, 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 *AssetBalanceSnapshot + for key, diffAmount := range addrAssetBalanceDiff { + if key.asset == proto.AssetIDFromDigest(assetID) { + // remove the element from the array + + delete(addrAssetBalanceDiff, key) + specialAssetSnapshot = &AssetBalanceSnapshot{ + Address: key.address, + AssetID: assetID, + Balance: uint64(diffAmount), + } + } + } + + issueStaticInfoSnapshot := &StaticAssetInfoSnapshot{ + AssetID: assetID, + IssuerPublicKey: senderPK, + SourceTransactionID: txID, + Decimals: assetInfo.decimals, + IsNFT: assetInfo.isNFT(), + } + + assetDescription := &AssetDescriptionSnapshot{ + AssetID: assetID, + AssetName: assetInfo.name, + AssetDescription: assetInfo.description, + ChangeHeight: assetInfo.lastNameDescChangeHeight, + } + + assetReissuability := &AssetVolumeSnapshot{ + AssetID: assetID, + IsReissuable: assetInfo.reissuable, + TotalQuantity: assetInfo.quantity, + } + snapshot = append(snapshot, issueStaticInfoSnapshot, assetDescription, assetReissuability) + + wavesBalancesSnapshot, assetBalancesSnapshot, err := tp.generateBalancesAtomicSnapshots(addrWavesBalanceDiff, addrAssetBalanceDiff) + if err != nil { + return nil, errors.Wrap(err, "failed to build a snapshot from a genesis transaction") + } + + for i := range wavesBalancesSnapshot { + snapshot = append(snapshot, &wavesBalancesSnapshot[i]) + } + for i := range assetBalancesSnapshot { + snapshot = append(snapshot, &assetBalancesSnapshot[i]) + } + if specialAssetSnapshot != nil { + snapshot = append(snapshot, specialAssetSnapshot) + } + + return snapshot, nil +} + +func (tp *transactionPerformer) generateSnapshotForReissueTx(assetID crypto.Digest, change assetReissueChange, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + quantityDiff := big.NewInt(change.diff) + assetInfo, err := tp.stor.assets.newestAssetInfo(proto.AssetIDFromDigest(assetID)) + if err != nil { + return nil, err + } + resQuantity := assetInfo.quantity.Add(&assetInfo.quantity, quantityDiff) + + snapshot, err := tp.generateBalancesSnapshot(applicationRes) + if err != nil { + return nil, errors.Wrap(err, "failed to generate a snapshot based on transaction's diffs") + } + assetReissuability := &AssetVolumeSnapshot{ + AssetID: assetID, + TotalQuantity: *resQuantity, + IsReissuable: change.reissuable, + } + snapshot = append(snapshot, assetReissuability) + return snapshot, nil +} + +func (tp *transactionPerformer) generateSnapshotForBurnTx(assetID crypto.Digest, change assetBurnChange, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + quantityDiff := big.NewInt(change.diff) + assetInfo, err := tp.stor.assets.newestAssetInfo(proto.AssetIDFromDigest(assetID)) + if err != nil { + return nil, err + } + resQuantity := assetInfo.quantity.Sub(&assetInfo.quantity, quantityDiff) + + snapshot, err := tp.generateBalancesSnapshot(applicationRes) + if err != nil { + return nil, errors.Wrap(err, "failed to generate a snapshot based on transaction's diffs") + } + assetReissuability := &AssetVolumeSnapshot{ + AssetID: assetID, + TotalQuantity: *resQuantity, + IsReissuable: assetInfo.reissuable, + } + snapshot = append(snapshot, assetReissuability) + return snapshot, nil +} + +func (tp *transactionPerformer) generateSnapshotForExchangeTx(sellOrder proto.Order, sellFee uint64, buyOrder proto.Order, buyFee uint64, volume uint64, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + snapshot, err := tp.generateBalancesSnapshot(applicationRes) + if err != nil { + return nil, errors.Wrap(err, "failed to generate a snapshot based on transaction's diffs") + } + + sellOrderID, err := sellOrder.GetID() + if err != nil { + return nil, err + } + sellOrderAtomicSnapshot, err := tp.generateOrderAtomicSnapshot(sellOrderID, volume, sellFee) + if err != nil { + return nil, err + } + buyOrderID, err := buyOrder.GetID() + if err != nil { + return nil, err + } + buyOrderAtomicSnapshot, err := tp.generateOrderAtomicSnapshot(buyOrderID, volume, buyFee) + if err != nil { + return nil, err + } + + snapshot = append(snapshot, sellOrderAtomicSnapshot, buyOrderAtomicSnapshot) + return snapshot, nil +} + +func (tp *transactionPerformer) generateSnapshotForLeaseTx(lease leasing, leaseID crypto.Digest, originalTxID crypto.Digest, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + var err error + snapshot, err := tp.generateBalancesSnapshot(applicationRes) + if err != nil { + return nil, errors.Wrap(err, "failed to generate a snapshot based on transaction's diffs") + } + amount := int64(lease.Amount) + leaseStatusSnapshot, senderLeaseBalanceSnapshot, recipientLeaseBalanceSnapshot, err := tp.generateLeaseAtomicSnapshots(leaseID, lease, originalTxID, lease.Sender, lease.Recipient, amount) + if err != nil { + return nil, errors.Wrap(err, "failed to generate snapshots for a lease transaction") + } + + snapshot = append(snapshot, leaseStatusSnapshot, senderLeaseBalanceSnapshot, recipientLeaseBalanceSnapshot) + return snapshot, nil +} + +func (tp *transactionPerformer) generateSnapshotForLeaseCancelTx(txID *crypto.Digest, oldLease leasing, leaseID crypto.Digest, originalTxID crypto.Digest, cancelHeight uint64, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + var err error + snapshot, err := tp.generateBalancesSnapshot(applicationRes) + if err != nil { + return nil, errors.Wrap(err, "failed to generate a snapshot based on transaction's diffs") + } + negativeAmount := -int64(oldLease.Amount) + leaseStatusSnapshot, senderLeaseBalanceSnapshot, recipientLeaseBalanceSnapshot, err := tp.generateLeaseAtomicSnapshots(leaseID, oldLease, originalTxID, oldLease.Sender, oldLease.Recipient, negativeAmount) + if err != nil { + return nil, errors.Wrap(err, "failed to generate snapshots for a lease transaction") + } + leaseStatusSnapshot.Status = LeaseStateStatus{ + Value: LeaseCanceled, + CancelHeight: cancelHeight, + CancelTransactionID: txID, + } + + snapshot = append(snapshot, leaseStatusSnapshot, senderLeaseBalanceSnapshot, recipientLeaseBalanceSnapshot) + return snapshot, nil +} + +func (tp *transactionPerformer) generateSnapshotForCreateAliasTx(senderAddress proto.WavesAddress, alias proto.Alias, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + snapshot, err := tp.generateBalancesSnapshot(applicationRes) + if err != nil { + return nil, err + } + aliasSnapshot := &AliasSnapshot{ + Address: senderAddress, + Alias: alias, + } + snapshot = append(snapshot, aliasSnapshot) + return snapshot, nil +} + +func (tp *transactionPerformer) generateSnapshotForMassTransferTx(applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + return tp.generateBalancesSnapshot(applicationRes) +} + +func (tp *transactionPerformer) generateSnapshotForDataTx(senderAddress proto.WavesAddress, entries []proto.DataEntry, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + snapshot, err := tp.generateBalancesSnapshot(applicationRes) + if err != nil { + return nil, err + } + dataEntriesSnapshot := &DataEntriesSnapshot{ + Address: senderAddress, + DataEntries: entries, + } + snapshot = append(snapshot, dataEntriesSnapshot) + return snapshot, nil +} + +func (tp *transactionPerformer) generateSnapshotForSponsorshipTx(assetID crypto.Digest, minAssetFee uint64, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + snapshot, err := tp.generateBalancesSnapshot(applicationRes) + if err != nil { + return nil, err + } + sponsorshipSnapshot := &SponsorshipSnapshot{ + AssetID: assetID, + MinSponsoredFee: minAssetFee, + } + snapshot = append(snapshot, sponsorshipSnapshot) + return snapshot, nil +} + +func (tp *transactionPerformer) generateSnapshotForSetScriptTx(senderAddress proto.WavesAddress, senderPK crypto.PublicKey, script proto.Script, info *performerInfo, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + snapshot, err := tp.generateBalancesSnapshot(applicationRes) + if err != nil { + return nil, err + } + estimatorVersion := info.checkerInfo.estimatorVersion() + // the complexity was saved before when evaluated in checker + treeEstimation, err := tp.stor.scriptsComplexity.newestScriptComplexityByAddr(senderAddress, estimatorVersion) + if err != nil { + return nil, errors.Wrap(err, "failed to get verifier complexity from storage") + } + complexity := treeEstimation.Verifier + + sponsorshipSnapshot := &AccountScriptSnapshot{ + SenderPublicKey: senderPK, + Script: script, + VerifierComplexity: uint64(complexity), + } + snapshot = append(snapshot, sponsorshipSnapshot) + return snapshot, nil +} + +func (tp *transactionPerformer) generateSnapshotForSetAssetScriptTx(assetID crypto.Digest, script proto.Script, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + snapshot, err := tp.generateBalancesSnapshot(applicationRes) + if err != nil { + return nil, err + } + // the complexity was saved before when evaluated in checker + treeEstimation, err := tp.stor.scriptsComplexity.newestScriptComplexityByAsset(proto.AssetIDFromDigest(assetID)) + if err != nil { + return nil, errors.Wrap(err, "failed to get verifier complexity from storage") + } + complexity := treeEstimation.Verifier + + sponsorshipSnapshot := &AssetScriptSnapshot{ + AssetID: assetID, + Script: script, + Complexity: uint64(complexity), + } + snapshot = append(snapshot, sponsorshipSnapshot) + return snapshot, nil +} + +func (tp *transactionPerformer) generateSnapshotForInvokeScriptTx(txID crypto.Digest, info *performerInfo, invocationRes *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { + return tp.generateInvokeSnapshot(txID, info, invocationRes, applicationRes) +} + +func (tp *transactionPerformer) generateSnapshotForInvokeExpressionTx(txID crypto.Digest, info *performerInfo, invocationRes *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { + return tp.generateInvokeSnapshot(txID, info, invocationRes, applicationRes) +} + +func (tp *transactionPerformer) generateSnapshotForEthereumInvokeScriptTx(txID crypto.Digest, info *performerInfo, invocationRes *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { + return tp.generateInvokeSnapshot(txID, info, invocationRes, applicationRes) +} + +func (tp *transactionPerformer) generateSnapshotForUpdateAssetInfoTx(assetID crypto.Digest, assetName string, assetDescription string, changeHeight proto.Height, applicationRes *applicationResult) (TransactionSnapshot, error) { + if applicationRes == nil { + return nil, nil + } + snapshot, err := tp.generateBalancesSnapshot(applicationRes) + if err != nil { + return nil, err + } + sponsorshipSnapshot := &AssetDescriptionSnapshot{ + AssetID: assetID, + AssetName: assetName, + AssetDescription: assetDescription, + ChangeHeight: changeHeight, + } + snapshot = append(snapshot, sponsorshipSnapshot) + return snapshot, nil +} + +// TODO optimize this +func (tp *transactionPerformer) generateInvokeSnapshot(txID crypto.Digest, info *performerInfo, invocationRes *invocationResult, applicationRes *applicationResult) (TransactionSnapshot, error) { + + blockHeight := info.height + 1 + + addrWavesBalanceDiff, addrAssetBalanceDiff, err := addressBalanceDiffFromTxDiff(applicationRes.changes.diff, tp.settings.AddressSchemeCharacter) + if err != nil { + return nil, errors.Wrap(err, "failed to create balance diff from tx diff") + } + var snapshot TransactionSnapshot + var dataEntries = make(map[proto.WavesAddress]proto.DataEntries) + if invocationRes != nil { + + for _, action := range invocationRes.actions { + + switch a := action.(type) { + case *proto.DataEntryScriptAction: + senderAddr, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, *a.Sender) + if err != nil { + return nil, err + } + // construct the map first and create the snapshot later for convenience + if _, ok := dataEntries[senderAddr]; ok { + entries := dataEntries[senderAddr] + entries = append(entries, a.Entry) + dataEntries[senderAddr] = entries + } else { + dataEntries[senderAddr] = proto.DataEntries{a.Entry} + } + + case *proto.AttachedPaymentScriptAction: + senderAddress, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, *a.Sender) + if err != nil { + return nil, errors.Wrap(err, "failed to get an address from a public key") + } + recipientAddress, err := recipientToAddress(a.Recipient, tp.stor.aliases) + if err != nil { + return nil, errors.Wrap(err, "failed to apply attached payment") + } + // No balance validation done below + if a.Asset.Present { // Update asset balance + addSenderRecipientToAssetBalanceDiff(addrAssetBalanceDiff, senderAddress, recipientAddress, proto.AssetIDFromDigest(a.Asset.ID), a.Amount) + } else { // Update Waves balance + addToWavesBalanceDiff(addrWavesBalanceDiff, senderAddress, recipientAddress, a.Amount) + } + case *proto.TransferScriptAction: + senderAddress, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, *a.Sender) + if err != nil { + return nil, errors.Wrap(err, "failed to get an address from a public key") + } + recipientAddress, err := recipientToAddress(a.Recipient, tp.stor.aliases) + if err != nil { + return nil, errors.Wrap(err, "failed to apply attached payment") + } + // No balance validation done below + if a.Asset.Present { // Update asset balance + addSenderRecipientToAssetBalanceDiff(addrAssetBalanceDiff, senderAddress, recipientAddress, proto.AssetIDFromDigest(a.Asset.ID), a.Amount) + } else { // Update Waves balance + addToWavesBalanceDiff(addrWavesBalanceDiff, senderAddress, recipientAddress, a.Amount) + } + case *proto.SponsorshipScriptAction: + sponsorshipSnapshot := &SponsorshipSnapshot{ + AssetID: a.AssetID, + MinSponsoredFee: uint64(a.MinFee), + } + snapshot = append(snapshot, sponsorshipSnapshot) + case *proto.IssueScriptAction: + assetInfo := assetInfo{ + assetConstInfo: assetConstInfo{ + tail: proto.DigestTail(a.ID), + issuer: *a.Sender, + decimals: uint8(a.Decimals), + issueHeight: blockHeight, + issueSequenceInBlock: info.stateActionsCounter.NextIssueActionNumber(), + }, + assetChangeableInfo: assetChangeableInfo{ + quantity: *big.NewInt(a.Quantity), + name: a.Name, + description: a.Description, + lastNameDescChangeHeight: blockHeight, + reissuable: a.Reissuable, + }, + } + issuerAddress, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, *a.Sender) + if err != nil { + return nil, errors.Wrap(err, "failed to get an address from a public key") + } + + issueStaticInfoSnapshot := &StaticAssetInfoSnapshot{ + AssetID: a.ID, + IssuerPublicKey: *a.Sender, + SourceTransactionID: txID, + Decimals: assetInfo.decimals, + IsNFT: assetInfo.isNFT(), + } + + assetDescription := &AssetDescriptionSnapshot{ + AssetID: a.ID, + AssetName: assetInfo.name, + AssetDescription: assetInfo.description, + ChangeHeight: assetInfo.lastNameDescChangeHeight, + } + + assetReissuability := &AssetVolumeSnapshot{ + AssetID: a.ID, + IsReissuable: assetInfo.reissuable, + TotalQuantity: assetInfo.quantity, + } + snapshot = append(snapshot, issueStaticInfoSnapshot, assetDescription, assetReissuability) + + addSenderToAssetBalanceDiff(addrAssetBalanceDiff, issuerAddress, proto.AssetIDFromDigest(a.ID), a.Quantity) + + case *proto.ReissueScriptAction: + + assetInfo, err := tp.stor.assets.newestAssetInfo(proto.AssetIDFromDigest(a.AssetID)) + if err != nil { + return nil, err + } + quantityDiff := big.NewInt(a.Quantity) + resQuantity := assetInfo.quantity.Add(&assetInfo.quantity, quantityDiff) + assetReissuability := &AssetVolumeSnapshot{ + AssetID: a.AssetID, + TotalQuantity: *resQuantity, + IsReissuable: a.Reissuable, + } + + issueAddress, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, *a.Sender) + if err != nil { + return nil, errors.Wrap(err, "failed to get an address from a public key") + } + addSenderToAssetBalanceDiff(addrAssetBalanceDiff, issueAddress, proto.AssetIDFromDigest(a.AssetID), a.Quantity) + snapshot = append(snapshot, assetReissuability) + + case *proto.BurnScriptAction: + assetInfo, err := tp.stor.assets.newestAssetInfo(proto.AssetIDFromDigest(a.AssetID)) + if err != nil { + return nil, err + } + quantityDiff := big.NewInt(a.Quantity) + resQuantity := assetInfo.quantity.Sub(&assetInfo.quantity, quantityDiff) + assetReissuability := &AssetVolumeSnapshot{ + AssetID: a.AssetID, + TotalQuantity: *resQuantity, + IsReissuable: assetInfo.reissuable, + } + + issueAddress, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, *a.Sender) + if err != nil { + return nil, errors.Wrap(err, "failed to get an address from a public key") + } + addSenderToAssetBalanceDiff(addrAssetBalanceDiff, issueAddress, proto.AssetIDFromDigest(a.AssetID), -a.Quantity) + snapshot = append(snapshot, assetReissuability) + case *proto.LeaseScriptAction: + senderAddr, err := proto.NewAddressFromPublicKey(tp.settings.AddressSchemeCharacter, *a.Sender) + if err != nil { + return nil, err + } + var recipientAddr proto.WavesAddress + if addr := a.Recipient.Address(); addr == nil { + recipientAddr, err = tp.stor.aliases.newestAddrByAlias(a.Recipient.Alias().Alias) + if err != nil { + return nil, errors.Errorf("invalid alias: %v\n", err) + } + } else { + recipientAddr = *addr + } + l := &leasing{ + Sender: senderAddr, + Recipient: recipientAddr, + Amount: uint64(a.Amount), + Height: info.height, + Status: LeaseActive, + } + var amount = int64(l.Amount) + leaseStatusSnapshot, senderLeaseBalanceSnapshot, recipientLeaseBalanceSnapshot, err := tp.generateLeaseAtomicSnapshots(a.ID, *l, txID, senderAddr, recipientAddr, amount) + if err != nil { + return nil, errors.Wrap(err, "failed to generate snapshots for a lease action") + } + snapshot = append(snapshot, leaseStatusSnapshot, senderLeaseBalanceSnapshot, recipientLeaseBalanceSnapshot) + case *proto.LeaseCancelScriptAction: + l, err := tp.stor.leases.leasingInfo(a.LeaseID) + if err != nil { + return nil, errors.Wrap(err, "failed to receiver leasing info") + } + + var amount = -int64(l.Amount) + leaseStatusSnapshot, senderLeaseBalanceSnapshot, recipientLeaseBalanceSnapshot, err := tp.generateLeaseAtomicSnapshots(a.LeaseID, *l, txID, l.Sender, l.Recipient, amount) + if err != nil { + return nil, errors.Wrap(err, "failed to generate snapshots for a lease cancel action") + } + snapshot = append(snapshot, leaseStatusSnapshot, senderLeaseBalanceSnapshot, recipientLeaseBalanceSnapshot) + default: + return nil, errors.Errorf("unknown script action type %T", a) + } + } + + for address, entries := range dataEntries { + dataEntrySnapshot := &DataEntriesSnapshot{Address: address, DataEntries: entries} + snapshot = append(snapshot, dataEntrySnapshot) + } + + } + + wavesBalancesSnapshot, assetBalancesSnapshot, err := tp.generateBalancesAtomicSnapshots(addrWavesBalanceDiff, addrAssetBalanceDiff) + if err != nil { + return nil, errors.Wrap(err, "failed to build a snapshot from a genesis transaction") + } + + for i := range wavesBalancesSnapshot { + snapshot = append(snapshot, &wavesBalancesSnapshot[i]) + } + for i := range assetBalancesSnapshot { + snapshot = append(snapshot, &assetBalancesSnapshot[i]) + } + + return snapshot, nil +} + +func (tp *transactionPerformer) generateLeaseAtomicSnapshots(leaseID crypto.Digest, l leasing, originalTxID crypto.Digest, + senderAddress proto.WavesAddress, receiverAddress proto.WavesAddress, amount int64) (*LeaseStateSnapshot, *LeaseBalanceSnapshot, *LeaseBalanceSnapshot, error) { + leaseStatusSnapshot := &LeaseStateSnapshot{ + LeaseID: leaseID, + Status: LeaseStateStatus{ + Value: l.Status, + }, + Amount: l.Amount, + Sender: l.Sender, + Recipient: l.Recipient, + OriginTransactionID: &originalTxID, + Height: l.Height, + } + + senderBalanceProfile, err := tp.stor.balances.newestWavesBalance(senderAddress.ID()) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to receive sender's waves balance") + } + senderLeaseBalanceSnapshot := &LeaseBalanceSnapshot{ + Address: senderAddress, + LeaseIn: uint64(senderBalanceProfile.leaseIn), + LeaseOut: uint64(senderBalanceProfile.leaseOut + amount), + } + + receiverBalanceProfile, err := tp.stor.balances.newestWavesBalance(receiverAddress.ID()) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "failed to receive recipient's waves balance") + } + recipientLeaseBalanceSnapshot := &LeaseBalanceSnapshot{ + Address: receiverAddress, + LeaseIn: uint64(receiverBalanceProfile.leaseIn + amount), + LeaseOut: uint64(receiverBalanceProfile.leaseOut), + } + + return leaseStatusSnapshot, senderLeaseBalanceSnapshot, recipientLeaseBalanceSnapshot, nil +} + +func (tp *transactionPerformer) generateOrderAtomicSnapshot(orderID []byte, volume uint64, fee uint64) (*FilledVolumeFeeSnapshot, error) { + newestFilledAmount, newestFilledFee, err := tp.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") + } + orderSnapshot := &FilledVolumeFeeSnapshot{ + OrderID: orderIdDigset, + FilledFee: newestFilledFee + fee, + FilledVolume: newestFilledAmount + volume, + } + return orderSnapshot, nil +} + +func (tp *transactionPerformer) generateBalancesSnapshot(applicationRes *applicationResult) (TransactionSnapshot, error) { + var transactionSnapshot TransactionSnapshot + addrWavesBalanceDiff, addrAssetBalanceDiff, err := addressBalanceDiffFromTxDiff(applicationRes.changes.diff, tp.settings.AddressSchemeCharacter) + if err != nil { + return nil, errors.Wrap(err, "failed to create balance diff from tx diff") + } + wavesBalancesSnapshot, assetBalancesSnapshot, err := tp.generateBalancesAtomicSnapshots(addrWavesBalanceDiff, addrAssetBalanceDiff) + if err != nil { + return nil, errors.Wrap(err, "failed to build a snapshot from a genesis transaction") + } + for i := range wavesBalancesSnapshot { + transactionSnapshot = append(transactionSnapshot, &wavesBalancesSnapshot[i]) + } + for i := range assetBalancesSnapshot { + transactionSnapshot = append(transactionSnapshot, &assetBalancesSnapshot[i]) + } + return transactionSnapshot, nil +} + +func (tp *transactionPerformer) generateBalancesAtomicSnapshots(addrWavesBalanceDiff addressWavesBalanceDiff, addrAssetBalanceDiff addressAssetBalanceDiff) ([]WavesBalanceSnapshot, []AssetBalanceSnapshot, error) { + wavesBalanceSnapshot, err := tp.constructWavesBalanceSnapshotFromDiff(addrWavesBalanceDiff) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to construct waves balance snapshot") + } + if len(addrAssetBalanceDiff) == 0 { + return wavesBalanceSnapshot, nil, nil + } + + assetBalanceSnapshot, err := tp.constructAssetBalanceSnapshotFromDiff(addrAssetBalanceDiff) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to construct asset balance snapshot") + } + return wavesBalanceSnapshot, assetBalanceSnapshot, nil +} + +func addressBalanceDiffFromTxDiff(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 { + assetBalanceKey := &assetBalanceKey{} + err := assetBalanceKey.unmarshal([]byte(balanceKeyString)) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to convert balance key to asset balance key") + } + asset := assetBalanceKey.asset + address, err := assetBalanceKey.address.ToWavesAddress(scheme) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to convert address id to waves address") + } + assetBalKey := assetBalanceDiffKey{address: address, asset: asset} + addrAssetBalanceDiff[assetBalKey] = diffAmount.balance + continue + } + address, err := wavesBalanceKey.address.ToWavesAddress(scheme) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to convert address id to waves address") + } + // if the waves balance diff is 0, it means it did not change. Though the record might occur when LeaseIn and LeaseOut change, + // but they are handled separately in snapshots + if diffAmount.balance == 0 { + continue + } + addrWavesBalanceDiff[address] = diffAmount + } + return addrWavesBalanceDiff, addrAssetBalanceDiff, nil +} + +// from txDiff and fees. no validation needed at this point +func (tp *transactionPerformer) constructWavesBalanceSnapshotFromDiff(diff addressWavesBalanceDiff) ([]WavesBalanceSnapshot, error) { + var wavesBalances []WavesBalanceSnapshot + // add miner address to the diff + + for wavesAddress, diffAmount := range diff { + + fullBalance, err := tp.stor.balances.newestWavesBalance(wavesAddress.ID()) + if err != nil { + return nil, errors.Wrap(err, "failed to receive sender's waves balance") + } + newBalance := WavesBalanceSnapshot{ + Address: wavesAddress, + Balance: uint64(int64(fullBalance.balance) + diffAmount.balance), + } + wavesBalances = append(wavesBalances, newBalance) + } + return wavesBalances, nil +} + +func (tp *transactionPerformer) constructAssetBalanceSnapshotFromDiff(diff addressAssetBalanceDiff) ([]AssetBalanceSnapshot, error) { + var assetBalances []AssetBalanceSnapshot + // add miner address to the diff + + for key, diffAmount := range diff { + balance, err := tp.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 := tp.stor.assets.newestAssetInfo(key.asset) + if err != nil { + return nil, errors.Wrap(err, "failed to get newest asset info") + } + + newBalance := 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/transaction_performer_test.go b/pkg/state/transaction_performer_test.go index bcb9dd445..97ba679d4 100644 --- a/pkg/state/transaction_performer_test.go +++ b/pkg/state/transaction_performer_test.go @@ -25,7 +25,7 @@ func createPerformerTestObjects(t *testing.T) *performerTestObjects { } func defaultPerformerInfo() *performerInfo { - return &performerInfo{0, new(proto.StateActionsCounter), blockID0} + return &performerInfo{0, blockID0, proto.WavesAddress{}, new(proto.StateActionsCounter), nil} } func TestPerformIssueWithSig(t *testing.T) { @@ -33,7 +33,7 @@ func TestPerformIssueWithSig(t *testing.T) { to.stor.addBlock(t, blockID0) tx := createIssueWithSig(t, 1000) - err := to.tp.performIssueWithSig(tx, defaultPerformerInfo()) + _, err := to.tp.performIssueWithSig(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performIssueWithSig() failed") to.stor.flush(t) expectedAssetInfo := assetInfo{ @@ -65,7 +65,7 @@ func TestPerformIssueWithProofs(t *testing.T) { to.stor.addBlock(t, blockID0) tx := createIssueWithProofs(t, 1000) - err := to.tp.performIssueWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performIssueWithProofs(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performIssueWithProofs() failed") to.stor.flush(t) expectedAssetInfo := assetInfo{ @@ -96,7 +96,7 @@ func TestPerformReissueWithSig(t *testing.T) { 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(), nil, nil) assert.NoError(t, err, "performReissueWithSig() failed") to.stor.flush(t) assetInfo.reissuable = tx.Reissuable @@ -113,7 +113,7 @@ func TestPerformReissueWithProofs(t *testing.T) { 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(), nil, nil) assert.NoError(t, err, "performReissueWithProofs() failed") to.stor.flush(t) assetInfo.reissuable = tx.Reissuable @@ -130,7 +130,7 @@ func TestPerformBurnWithSig(t *testing.T) { assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createBurnWithSig(t) - err := to.tp.performBurnWithSig(tx, defaultPerformerInfo()) + _, err := to.tp.performBurnWithSig(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performBurnWithSig() failed") to.stor.flush(t) assetInfo.quantity.Sub(&assetInfo.quantity, big.NewInt(int64(tx.Amount))) @@ -146,7 +146,7 @@ func TestPerformBurnWithProofs(t *testing.T) { assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createBurnWithProofs(t) - err := to.tp.performBurnWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performBurnWithProofs(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performBurnWithProofs() failed") to.stor.flush(t) assetInfo.quantity.Sub(&assetInfo.quantity, big.NewInt(int64(tx.Amount))) @@ -162,7 +162,7 @@ func TestPerformExchange(t *testing.T) { to.stor.addBlock(t, blockID0) tx := createExchangeWithSig(t) - err := to.tp.performExchange(tx, defaultPerformerInfo()) + _, err := to.tp.performExchange(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performExchange() failed") sellOrderID, err := tx.GetOrder2().GetID() @@ -199,7 +199,7 @@ func TestPerformLeaseWithSig(t *testing.T) { to.stor.addBlock(t, blockID0) tx := createLeaseWithSig(t) - err := to.tp.performLeaseWithSig(tx, defaultPerformerInfo()) + _, err := to.tp.performLeaseWithSig(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performLeaseWithSig() failed") to.stor.flush(t) leasingInfo := &leasing{ @@ -220,7 +220,7 @@ func TestPerformLeaseWithProofs(t *testing.T) { to.stor.addBlock(t, blockID0) tx := createLeaseWithProofs(t) - err := to.tp.performLeaseWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performLeaseWithProofs(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performLeaseWithProofs() failed") to.stor.flush(t) leasingInfo := &leasing{ @@ -241,7 +241,7 @@ func TestPerformLeaseCancelWithSig(t *testing.T) { to.stor.addBlock(t, blockID0) leaseTx := createLeaseWithSig(t) - err := to.tp.performLeaseWithSig(leaseTx, defaultPerformerInfo()) + _, err := to.tp.performLeaseWithSig(leaseTx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performLeaseWithSig() failed") to.stor.flush(t) tx := createLeaseCancelWithSig(t, *leaseTx.ID) @@ -253,7 +253,7 @@ func TestPerformLeaseCancelWithSig(t *testing.T) { Sender: testGlobal.senderInfo.addr, CancelTransactionID: tx.ID, } - err = to.tp.performLeaseCancelWithSig(tx, defaultPerformerInfo()) + _, err = to.tp.performLeaseCancelWithSig(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performLeaseCancelWithSig() failed") to.stor.flush(t) info, err := to.stor.entities.leases.leasingInfo(*leaseTx.ID) @@ -266,7 +266,7 @@ func TestPerformLeaseCancelWithProofs(t *testing.T) { to.stor.addBlock(t, blockID0) leaseTx := createLeaseWithProofs(t) - err := to.tp.performLeaseWithProofs(leaseTx, defaultPerformerInfo()) + _, err := to.tp.performLeaseWithProofs(leaseTx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performLeaseWithProofs() failed") to.stor.flush(t) tx := createLeaseCancelWithProofs(t, *leaseTx.ID) @@ -278,7 +278,7 @@ func TestPerformLeaseCancelWithProofs(t *testing.T) { Sender: testGlobal.senderInfo.addr, CancelTransactionID: tx.ID, } - err = to.tp.performLeaseCancelWithProofs(tx, defaultPerformerInfo()) + _, err = to.tp.performLeaseCancelWithProofs(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performLeaseCancelWithProofs() failed") to.stor.flush(t) info, err := to.stor.entities.leases.leasingInfo(*leaseTx.ID) @@ -291,7 +291,7 @@ func TestPerformCreateAliasWithSig(t *testing.T) { to.stor.addBlock(t, blockID0) tx := createCreateAliasWithSig(t) - err := to.tp.performCreateAliasWithSig(tx, defaultPerformerInfo()) + _, err := to.tp.performCreateAliasWithSig(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performCreateAliasWithSig() failed") to.stor.flush(t) addr, err := to.stor.entities.aliases.addrByAlias(tx.Alias.Alias) @@ -299,7 +299,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(), nil, nil) assert.NoError(t, err, "performCreateAliasWithSig() failed") to.stor.flush(t) err = to.stor.entities.aliases.disableStolenAliases(blockID0) @@ -314,7 +314,7 @@ func TestPerformCreateAliasWithProofs(t *testing.T) { to.stor.addBlock(t, blockID0) tx := createCreateAliasWithProofs(t) - err := to.tp.performCreateAliasWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performCreateAliasWithProofs(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performCreateAliasWithProofs() failed") to.stor.flush(t) addr, err := to.stor.entities.aliases.addrByAlias(tx.Alias.Alias) @@ -322,7 +322,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(), nil, nil) assert.NoError(t, err, "performCreateAliasWithProofs() failed") to.stor.flush(t) err = to.stor.entities.aliases.disableStolenAliases(blockID0) @@ -341,7 +341,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(), nil, nil) assert.NoError(t, err, "performDataWithProofs() failed") to.stor.flush(t) @@ -356,7 +356,7 @@ func TestPerformSponsorshipWithProofs(t *testing.T) { to.stor.addBlock(t, blockID0) tx := createSponsorshipWithProofs(t, 1000) - err := to.tp.performSponsorshipWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performSponsorshipWithProofs(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performSponsorshipWithProofs() failed") assetID := proto.AssetIDFromDigest(tx.AssetID) @@ -416,7 +416,7 @@ func TestPerformSetScriptWithProofs(t *testing.T) { require.NoError(t, err) tx := createSetScriptWithProofs(t, scriptBytes) - err = to.tp.performSetScriptWithProofs(tx, defaultPerformerInfo()) + _, err = to.tp.performSetScriptWithProofs(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performSetScriptWithProofs() failed") addr := testGlobal.senderInfo.addr @@ -485,7 +485,7 @@ func TestPerformSetAssetScriptWithProofs(t *testing.T) { to.stor.addBlock(t, blockID0) tx := createSetAssetScriptWithProofs(t) - err := to.tp.performSetAssetScriptWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performSetAssetScriptWithProofs(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performSetAssetScriptWithProofs() failed") fullAssetID := tx.AssetID @@ -565,7 +565,7 @@ func TestPerformUpdateAssetInfoWithProofs(t *testing.T) { assetInfo := to.stor.createAsset(t, testGlobal.asset0.asset.ID) tx := createUpdateAssetInfoWithProofs(t) - err := to.tp.performUpdateAssetInfoWithProofs(tx, defaultPerformerInfo()) + _, err := to.tp.performUpdateAssetInfoWithProofs(tx, defaultPerformerInfo(), nil, nil) assert.NoError(t, err, "performUpdateAssetInfoWithProofs() failed") to.stor.flush(t) assetInfo.name = tx.Name diff --git a/pkg/state/transaction_snapshot_test.go b/pkg/state/transaction_snapshot_test.go new file mode 100644 index 000000000..c261d2bc2 --- /dev/null +++ b/pkg/state/transaction_snapshot_test.go @@ -0,0 +1,981 @@ +package state + +import ( + "encoding/base64" + "encoding/json" + "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" + "github.com/wavesplatform/gowaves/pkg/settings" + "math/big" + "sort" + "testing" +) + +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(checker *checkerInfo) *performerInfo { + return &performerInfo{0, blockID0, proto.WavesAddress{}, new(proto.StateActionsCounter), checker} +} + +func defaultCheckerInfoHeight() *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, stor *testStorageObjects) *checkerTestObjects { + 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} +} + +func TestDefaultTransferWavesAndAssetSnapshot(t *testing.T) { + to := createDifferTestObjects(t) + + 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{true, 0, ch} + transactionSnapshot, err := to.tp.performTransferWithSig(tx, defaultPerformerInfo(), nil, applicationRes) + assert.NoError(t, err, "failed to perform transfer tx") + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + Balance: 299700000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + Balance: 200000, + }, + } + + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +// TODO send only txBalanceChanges to perfomer +func TestDefaultIssueTransactionSnapshot(t *testing.T) { + to := createDifferTestObjects(t) + + 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{true, 0, ch} + transactionSnapshot, err := to.tp.performIssueWithSig(tx, defaultPerformerInfo(), nil, applicationRes) + assert.NoError(t, err, "failed to perform issue tx") + + expectedSnapshot := TransactionSnapshot{ + &StaticAssetInfoSnapshot{ + AssetID: *tx.ID, + SourceTransactionID: *tx.ID, + IssuerPublicKey: testGlobal.issuerInfo.pk, + Decimals: defaultDecimals, + IsNFT: false}, + &AssetDescriptionSnapshot{ + AssetID: *tx.ID, + AssetName: "asset0", + AssetDescription: "description", + ChangeHeight: 1, + }, + &AssetVolumeSnapshot{ + AssetID: *tx.ID, + TotalQuantity: *big.NewInt(int64(defaultQuantity)), + IsReissuable: true, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + Balance: 299900000, + }, + &AssetBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + AssetID: *tx.ID, + Balance: 1000, + }, + } + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultReissueSnapshot(t *testing.T) { + to := createDifferTestObjects(t) + + 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{true, 0, ch} + transactionSnapshot, err := to.tp.performReissueWithSig(tx, defaultPerformerInfo(), nil, applicationRes) + assert.NoError(t, err, "failed to perform reissue tx") + + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + Balance: 299900000, + }, + &AssetBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + AssetID: testGlobal.asset0.assetID, + Balance: 1050, + }, + &AssetVolumeSnapshot{ + AssetID: testGlobal.asset0.assetID, + TotalQuantity: *big.NewInt(int64(defaultQuantity + 50)), + IsReissuable: false, + }, + } + + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultBurnSnapshot(t *testing.T) { + to := createDifferTestObjects(t) + + 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 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{true, 0, ch} + transactionSnapshot, err := to.tp.performBurnWithSig(tx, defaultPerformerInfo(), nil, applicationRes) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + Balance: 299900000, + }, + &AssetBalanceSnapshot{ + Address: testGlobal.issuerInfo.addr, + AssetID: testGlobal.asset0.assetID, + Balance: 950, + }, + &AssetVolumeSnapshot{ + AssetID: testGlobal.asset0.assetID, + TotalQuantity: *big.NewInt(int64(defaultQuantity - 50)), + IsReissuable: true, + }, + } + + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultExchangeTransaction(t *testing.T) { + to := createDifferTestObjects(t) + + 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{true, 0, ch} + transactionSnapshot, err := to.tp.performExchange(tx, defaultPerformerInfo(), nil, applicationRes) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299999999, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + Balance: 599999998, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.matcherInfo.addr, + Balance: 899900003, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &AssetBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + AssetID: testGlobal.asset0.assetID, + Balance: 10, + }, + &AssetBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + AssetID: testGlobal.asset0.assetID, + Balance: 590, + }, + &AssetBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + AssetID: testGlobal.asset1.assetID, + Balance: 400, + }, + &AssetBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + AssetID: testGlobal.asset1.assetID, + Balance: 100, + }, + &FilledVolumeFeeSnapshot{ + OrderID: *bo.ID, + FilledVolume: 10, + FilledFee: 1, + }, + &FilledVolumeFeeSnapshot{ + OrderID: *so.ID, + FilledVolume: 10, + FilledFee: 2, + }, + } + + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultLeaseSnapshot(t *testing.T) { + to := createDifferTestObjects(t) + + 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{true, 0, ch} + transactionSnapshot, err := to.tp.performLeaseWithSig(tx, defaultPerformerInfo(), nil, applicationRes) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &LeaseStateSnapshot{ + LeaseID: *tx.ID, + Status: LeaseStateStatus{ + Value: LeaseActive, + }, + Amount: 50, + Sender: testGlobal.senderInfo.addr, + Recipient: testGlobal.recipientInfo.addr, + OriginTransactionID: tx.ID, + Height: 0, + }, + &LeaseBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + LeaseIn: 0, + LeaseOut: 50, + }, + &LeaseBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + LeaseIn: 50, + LeaseOut: 0, + }, + } + + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultLeaseCancelSnapshot(t *testing.T) { + to := createDifferTestObjects(t) + + 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, + Height: 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{true, 0, ch} + transactionSnapshot, err := to.tp.performLeaseCancelWithSig(tx, defaultPerformerInfo(), nil, applicationRes) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &LeaseStateSnapshot{ + LeaseID: leaseID, + Status: LeaseStateStatus{ + Value: LeaseCanceled, + CancelHeight: 0, + CancelTransactionID: tx.ID, + }, + Amount: 50, + Sender: testGlobal.senderInfo.addr, + Recipient: testGlobal.recipientInfo.addr, + OriginTransactionID: &leaseID, + Height: 1, + }, + &LeaseBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + LeaseIn: 0, + LeaseOut: 0, + }, + &LeaseBalanceSnapshot{ + Address: testGlobal.recipientInfo.addr, + LeaseIn: 0, + LeaseOut: 0, + }, + } + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultCreateAliasSnapshot(t *testing.T) { + to := createDifferTestObjects(t) + + 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{true, 0, ch} + transactionSnapshot, err := to.tp.performCreateAliasWithSig(tx, defaultPerformerInfo(), nil, applicationRes) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &AliasSnapshot{ + Address: testGlobal.senderInfo.addr, + Alias: *proto.NewAlias(proto.TestNetScheme, "aliasForSender"), + }, + } + + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultDataSnapshot(t *testing.T) { + to := createDifferTestObjects(t) + + 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{true, 0, ch} + transactionSnapshot, err := to.tp.performDataWithProofs(tx, defaultPerformerInfo(), nil, applicationRes) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &DataEntriesSnapshot{ + Address: testGlobal.senderInfo.addr, + DataEntries: []proto.DataEntry{&proto.StringDataEntry{Key: "key_str", Value: "value_str"}, &proto.IntegerDataEntry{Key: "key_int", Value: 2}}, + }, + } + + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultSponsorshipSnapshot(t *testing.T) { + to := createDifferTestObjects(t) + + 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{true, 0, ch} + transactionSnapshot, err := to.tp.performSponsorshipWithProofs(tx, defaultPerformerInfo(), nil, applicationRes) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &SponsorshipSnapshot{ + AssetID: testGlobal.asset0.assetID, + MinSponsoredFee: 500000, + }, + } + + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultSetScriptSnapshot(t *testing.T) { + to := createDifferTestObjects(t) + + 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.stor) + checkerInfo := defaultCheckerInfoHeight() + co.stor = to.stor + _, 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{true, 0, ch} + transactionSnapshot, err := to.tp.performSetScriptWithProofs(tx, defaultPerformerInfoWithChecker(checkerInfo), nil, applicationRes) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &AccountScriptSnapshot{ + SenderPublicKey: testGlobal.senderInfo.pk, + Script: testGlobal.scriptBytes, + VerifierComplexity: 340, + }, + } + + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func TestDefaultSetAssetScriptSnapshot(t *testing.T) { + to := createDifferTestObjects(t) + + 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") + + 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, testGlobal.senderInfo.pk, 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.stor) + checkerInfo := defaultCheckerInfoHeight() + co.stor = to.stor + _, 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{true, 0, ch} + transactionSnapshot, err := to.tp.performSetAssetScriptWithProofs(tx, defaultPerformerInfoWithChecker(checkerInfo), nil, applicationRes) + assert.NoError(t, err, "failed to perform burn tx") + + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 40000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299900000, + }, + &AssetScriptSnapshot{ + AssetID: testGlobal.asset0.assetID, + Script: testGlobal.scriptBytes, + Complexity: 340, + }, + } + + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +} + +func setScript(t *testing.T, to *differTestObjects, addr proto.WavesAddress, pk crypto.PublicKey, script proto.Script) { + tree, err := serialization.Parse(script) + require.NoError(t, err) + estimation, err := ride.EstimateTree(tree, 1) + require.NoError(t, err) + err = to.stor.entities.scriptsComplexity.saveComplexitiesForAddr(addr, map[int]ride.TreeEstimation{1: estimation}, blockID0) + assert.NoError(t, err, "failed to save complexity for address") + err = to.stor.entities.scriptsStorage.setAccountScript(addr, script, pk, blockID0) + assert.NoError(t, err, "failed to set account script") +} + +func TestDefaultInvokeScriptSnapshot(t *testing.T) { + /* + {-# STDLIB_VERSION 5 #-} + {-# CONTENT_TYPE DAPP #-} + {-# SCRIPT_TYPE ACCOUNT #-} + + @Callable(i) + func call() = { + [ + BooleanEntry("bool", true), + IntegerEntry("int", 1), + StringEntry("str", "") + ] + } + */ + script := "AAIFAAAAAAAAAAQIAhIAAAAAAAAAAAEAAAABaQEAAAAEY2FsbAAAAAAJAARMAAAAAgkBAAAADEJvb2xlYW5FbnRyeQAAAAICAAAABGJvb2wGCQAETAAAAAIJAQAAAAxJbnRlZ2VyRW50cnkAAAACAgAAAANpbnQAAAAAAAAAAAEJAARMAAAAAgkBAAAAC1N0cmluZ0VudHJ5AAAAAgIAAAADc3RyAgAAAAAFAAAAA25pbAAAAADr9Rv/" + scriptsBytes, err := base64.StdEncoding.DecodeString(script) + assert.NoError(t, err, "failed to set decode base64 script") + + to := createDifferTestObjects(t) + + to.stor.addBlock(t, blockID0) + to.stor.activateFeature(t, int16(settings.NG)) + to.stor.activateFeature(t, int16(settings.Ride4DApps)) + to.stor.activateFeature(t, int16(settings.RideV5)) + + setScript(t, to, testGlobal.recipientInfo.addr, testGlobal.recipientInfo.pk, scriptsBytes) + + 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") + + functionCall := proto.NewFunctionCall("call", nil) + invokeFee = FeeUnit * feeConstants[proto.InvokeScriptTransaction] + feeAsset = proto.NewOptionalAssetWaves() + + tx := proto.NewUnsignedInvokeScriptWithProofs(1, testGlobal.senderInfo.pk, proto.NewRecipientFromAddress(testGlobal.recipientInfo.addr), functionCall, []proto.ScriptPayment{}, feeAsset, invokeFee, defaultTimestamp) + err = tx.Sign(proto.TestNetScheme, testGlobal.senderInfo.sk) + assert.NoError(t, err, "failed to sign invoke script tx") + + co := createCheckerCustomTestObjects(t, to.stor) + checkerInfo := defaultCheckerInfoHeight() + co.stor = to.stor + _, err = co.tc.checkInvokeScriptWithProofs(tx, checkerInfo) + assert.NoError(t, err, "failed to check invoke script tx") + + ch, err := to.td.createDiffInvokeScriptWithProofs(tx, defaultDifferInfo()) + assert.NoError(t, err, "createDiffInvokeScriptWithProofs() failed") + + actions := []proto.ScriptAction{ + &proto.DataEntryScriptAction{ + Entry: &proto.BooleanDataEntry{Key: "bool", Value: true}, + Sender: &testGlobal.recipientInfo.pk}, + &proto.DataEntryScriptAction{ + Entry: &proto.IntegerDataEntry{Key: "int", Value: 1}, + Sender: &testGlobal.recipientInfo.pk}, + &proto.DataEntryScriptAction{ + Entry: &proto.StringDataEntry{Key: "int", Value: ""}, + Sender: &testGlobal.recipientInfo.pk}, + } + + invocationResult := &invocationResult{actions: actions, changes: ch} + + applicationRes := &applicationResult{true, 0, ch} + transactionSnapshot, err := to.tp.performInvokeScriptWithProofs(tx, defaultPerformerInfoWithChecker(checkerInfo), invocationResult, applicationRes) + assert.NoError(t, err, "failed to perform invoke script tx") + + expectedSnapshot := TransactionSnapshot{ + &WavesBalanceSnapshot{ + Address: testGlobal.minerInfo.addr, + Balance: 200000, + }, + &WavesBalanceSnapshot{ + Address: testGlobal.senderInfo.addr, + Balance: 299500000, + }, + &DataEntriesSnapshot{ + Address: testGlobal.recipientInfo.addr, + DataEntries: []proto.DataEntry{ + &proto.BooleanDataEntry{Key: "bool", Value: true}, + &proto.IntegerDataEntry{Key: "int", Value: 1}, + &proto.StringDataEntry{Key: "int", Value: ""}, + }, + }, + } + + sort.Slice(expectedSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(expectedSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(expectedSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + sort.Slice(transactionSnapshot, func(i, j int) bool { + snapshotI, err := json.Marshal(transactionSnapshot[i]) + assert.NoError(t, err, "failed to marshal snapshots") + snapshotJ, err := json.Marshal(transactionSnapshot[j]) + assert.NoError(t, err, "failed to marshal snapshots") + return string(snapshotI) < string(snapshotJ) + }) + + assert.Equal(t, expectedSnapshot, transactionSnapshot) + to.stor.flush(t) +}