diff --git a/api/groups/blockGroup.go b/api/groups/blockGroup.go index 26d9c05b000..f9f0ed81fd8 100644 --- a/api/groups/blockGroup.go +++ b/api/groups/blockGroup.go @@ -26,6 +26,7 @@ const ( urlParamTokensFilter = "tokens" urlParamWithTxs = "withTxs" urlParamWithLogs = "withLogs" + urlParamForHyperblock = "forHyperblock" ) // blockFacadeHandler defines the methods to be implemented by a facade for handling block requests @@ -219,7 +220,12 @@ func parseBlockQueryOptions(c *gin.Context) (api.BlockQueryOptions, error) { return api.BlockQueryOptions{}, err } - options := api.BlockQueryOptions{WithTransactions: withTxs, WithLogs: withLogs} + forHyperBlock, err := parseBoolUrlParam(c, urlParamForHyperblock) + if err != nil { + return api.BlockQueryOptions{}, err + } + + options := api.BlockQueryOptions{WithTransactions: withTxs, WithLogs: withLogs, ForHyperblock: forHyperBlock} return options, nil } diff --git a/api/groups/blockGroup_test.go b/api/groups/blockGroup_test.go index b190c2f0561..a2ebc61548b 100644 --- a/api/groups/blockGroup_test.go +++ b/api/groups/blockGroup_test.go @@ -90,7 +90,7 @@ func TestBlockGroup_getBlockByNonce(t *testing.T) { t.Parallel() providedNonce := uint64(37) - expectedOptions := api.BlockQueryOptions{WithTransactions: true} + expectedOptions := api.BlockQueryOptions{WithTransactions: true, ForHyperblock: true} expectedBlock := api.Block{ Nonce: 37, Round: 39, @@ -107,7 +107,7 @@ func TestBlockGroup_getBlockByNonce(t *testing.T) { loadBlockGroupResponse( t, facade, - fmt.Sprintf("/block/by-nonce/%d?withTxs=true", providedNonce), + fmt.Sprintf("/block/by-nonce/%d?withTxs=true&forHyperblock=true", providedNonce), "GET", nil, response, diff --git a/api/groups/networkGroup_test.go b/api/groups/networkGroup_test.go index 3eb52a4a0c0..c809a632b56 100644 --- a/api/groups/networkGroup_test.go +++ b/api/groups/networkGroup_test.go @@ -206,6 +206,36 @@ func TestNetworkConfigMetrics_GasLimitGuardedTxShouldWork(t *testing.T) { assert.True(t, keyAndValueFoundInResponse) } +func TestNetworkConfigMetrics_GasLimitRelayedTxShouldWork(t *testing.T) { + t.Parallel() + + statusMetricsProvider := statusHandler.NewStatusMetrics() + key := common.MetricExtraGasLimitRelayedTx + val := uint64(123) + statusMetricsProvider.SetUInt64Value(key, val) + + facade := mock.FacadeStub{} + facade.StatusMetricsHandler = func() external.StatusMetricsHandler { + return statusMetricsProvider + } + + networkGroup, err := groups.NewNetworkGroup(&facade) + require.NoError(t, err) + + ws := startWebServer(networkGroup, "network", getNetworkRoutesConfig()) + + req, _ := http.NewRequest("GET", "/network/config", nil) + resp := httptest.NewRecorder() + ws.ServeHTTP(resp, req) + + respBytes, _ := io.ReadAll(resp.Body) + respStr := string(respBytes) + assert.Equal(t, resp.Code, http.StatusOK) + + keyAndValueFoundInResponse := strings.Contains(respStr, key) && strings.Contains(respStr, fmt.Sprintf("%d", val)) + assert.True(t, keyAndValueFoundInResponse) +} + func TestNetworkStatusMetrics_ShouldWork(t *testing.T) { t.Parallel() diff --git a/api/groups/transactionGroup.go b/api/groups/transactionGroup.go index f35969ed701..86f04dfca22 100644 --- a/api/groups/transactionGroup.go +++ b/api/groups/transactionGroup.go @@ -720,21 +720,23 @@ func (tg *transactionGroup) getTransactionsPoolNonceGapsForSender(sender string, func (tg *transactionGroup) createTransaction(receivedTx *transaction.FrontendTransaction) (*transaction.Transaction, []byte, error) { txArgs := &external.ArgsCreateTransaction{ - Nonce: receivedTx.Nonce, - Value: receivedTx.Value, - Receiver: receivedTx.Receiver, - ReceiverUsername: receivedTx.ReceiverUsername, - Sender: receivedTx.Sender, - SenderUsername: receivedTx.SenderUsername, - GasPrice: receivedTx.GasPrice, - GasLimit: receivedTx.GasLimit, - DataField: receivedTx.Data, - SignatureHex: receivedTx.Signature, - ChainID: receivedTx.ChainID, - Version: receivedTx.Version, - Options: receivedTx.Options, - Guardian: receivedTx.GuardianAddr, - GuardianSigHex: receivedTx.GuardianSignature, + Nonce: receivedTx.Nonce, + Value: receivedTx.Value, + Receiver: receivedTx.Receiver, + ReceiverUsername: receivedTx.ReceiverUsername, + Sender: receivedTx.Sender, + SenderUsername: receivedTx.SenderUsername, + GasPrice: receivedTx.GasPrice, + GasLimit: receivedTx.GasLimit, + DataField: receivedTx.Data, + SignatureHex: receivedTx.Signature, + ChainID: receivedTx.ChainID, + Version: receivedTx.Version, + Options: receivedTx.Options, + Guardian: receivedTx.GuardianAddr, + GuardianSigHex: receivedTx.GuardianSignature, + Relayer: receivedTx.RelayerAddr, + RelayerSignatureHex: receivedTx.RelayerSignature, } start := time.Now() tx, txHash, err := tg.getFacade().CreateTransaction(txArgs) diff --git a/cmd/node/config/enableEpochs.toml b/cmd/node/config/enableEpochs.toml index 48567b6131f..cffdd5ea773 100644 --- a/cmd/node/config/enableEpochs.toml +++ b/cmd/node/config/enableEpochs.toml @@ -333,6 +333,9 @@ # FixRelayedMoveBalanceToNonPayableSCEnableEpoch represents the epoch when the fix for relayed move balance to non payable sc will be enabled FixRelayedMoveBalanceToNonPayableSCEnableEpoch = 1 + # RelayedTransactionsV3EnableEpoch represents the epoch when the relayed transactions v3 will be enabled + RelayedTransactionsV3EnableEpoch = 2 + # BLSMultiSignerEnableEpoch represents the activation epoch for different types of BLS multi-signers BLSMultiSignerEnableEpoch = [ { EnableEpoch = 0, Type = "no-KOSK" }, diff --git a/common/common.go b/common/common.go new file mode 100644 index 00000000000..c1e565043ad --- /dev/null +++ b/common/common.go @@ -0,0 +1,26 @@ +package common + +import "github.com/multiversx/mx-chain-core-go/data" + +// IsValidRelayedTxV3 returns true if the provided transaction is a valid transaction of type relayed v3 +func IsValidRelayedTxV3(tx data.TransactionHandler) bool { + relayedTx, isRelayedV3 := tx.(data.RelayedTransactionHandler) + if !isRelayedV3 { + return false + } + hasValidRelayer := len(relayedTx.GetRelayerAddr()) == len(tx.GetSndAddr()) && len(relayedTx.GetRelayerAddr()) > 0 + hasValidRelayerSignature := len(relayedTx.GetRelayerSignature()) == len(relayedTx.GetSignature()) && len(relayedTx.GetRelayerSignature()) > 0 + return hasValidRelayer && hasValidRelayerSignature +} + +// IsRelayedTxV3 returns true if the provided transaction is a transaction of type relayed v3, without any further checks +func IsRelayedTxV3(tx data.TransactionHandler) bool { + relayedTx, isRelayedV3 := tx.(data.RelayedTransactionHandler) + if !isRelayedV3 { + return false + } + + hasRelayer := len(relayedTx.GetRelayerAddr()) > 0 + hasRelayerSignature := len(relayedTx.GetRelayerSignature()) > 0 + return hasRelayer || hasRelayerSignature +} diff --git a/common/common_test.go b/common/common_test.go new file mode 100644 index 00000000000..5a0ec53a21f --- /dev/null +++ b/common/common_test.go @@ -0,0 +1,70 @@ +package common + +import ( + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/data/smartContractResult" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" +) + +func TestIsValidRelayedTxV3(t *testing.T) { + t.Parallel() + + scr := &smartContractResult.SmartContractResult{} + require.False(t, IsValidRelayedTxV3(scr)) + require.False(t, IsRelayedTxV3(scr)) + + notRelayedTxV3 := &transaction.Transaction{ + Nonce: 1, + Value: big.NewInt(100), + RcvAddr: []byte("receiver"), + SndAddr: []byte("sender0"), + GasPrice: 100, + GasLimit: 10, + Signature: []byte("signature"), + } + require.False(t, IsValidRelayedTxV3(notRelayedTxV3)) + require.False(t, IsRelayedTxV3(notRelayedTxV3)) + + invalidRelayedTxV3 := &transaction.Transaction{ + Nonce: 1, + Value: big.NewInt(100), + RcvAddr: []byte("receiver"), + SndAddr: []byte("sender0"), + GasPrice: 100, + GasLimit: 10, + Signature: []byte("signature"), + RelayerAddr: []byte("relayer"), + } + require.False(t, IsValidRelayedTxV3(invalidRelayedTxV3)) + require.True(t, IsRelayedTxV3(invalidRelayedTxV3)) + + invalidRelayedTxV3 = &transaction.Transaction{ + Nonce: 1, + Value: big.NewInt(100), + RcvAddr: []byte("receiver"), + SndAddr: []byte("sender0"), + GasPrice: 100, + GasLimit: 10, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + } + require.False(t, IsValidRelayedTxV3(invalidRelayedTxV3)) + require.True(t, IsRelayedTxV3(invalidRelayedTxV3)) + + relayedTxV3 := &transaction.Transaction{ + Nonce: 1, + Value: big.NewInt(100), + RcvAddr: []byte("receiver"), + SndAddr: []byte("sender1"), + GasPrice: 100, + GasLimit: 10, + Signature: []byte("signature"), + RelayerAddr: []byte("relayer"), + RelayerSignature: []byte("signature"), + } + require.True(t, IsValidRelayedTxV3(relayedTxV3)) + require.True(t, IsRelayedTxV3(relayedTxV3)) +} diff --git a/common/constants.go b/common/constants.go index 2b56d2f388b..e2ed1d80a5a 100644 --- a/common/constants.go +++ b/common/constants.go @@ -340,6 +340,9 @@ const MetricMinGasLimit = "erd_min_gas_limit" // MetricExtraGasLimitGuardedTx specifies the extra gas limit required for guarded transactions const MetricExtraGasLimitGuardedTx = "erd_extra_gas_limit_guarded_tx" +// MetricExtraGasLimitRelayedTx specifies the extra gas limit required for relayed v3 transactions +const MetricExtraGasLimitRelayedTx = "erd_extra_gas_limit_relayed_tx" + // MetricRewardsTopUpGradientPoint is the metric that specifies the rewards top up gradient point const MetricRewardsTopUpGradientPoint = "erd_rewards_top_up_gradient_point" @@ -737,6 +740,9 @@ const ( // MetricFixRelayedMoveBalanceToNonPayableSCEnableEpoch represents the epoch when the fix for relayed move balance to non-payable sc is enabled MetricFixRelayedMoveBalanceToNonPayableSCEnableEpoch = "erd_fix_relayed_move_balance_to_non_payable_sc_enable_epoch" + // MetricRelayedTransactionsV3EnableEpoch represents the epoch when the relayed transactions v3 are enabled + MetricRelayedTransactionsV3EnableEpoch = "erd_relayed_transactions_v3_enable_epoch" + // MetricMaxNodesChangeEnableEpoch holds configuration for changing the maximum number of nodes and the enabling epoch MetricMaxNodesChangeEnableEpoch = "erd_max_nodes_change_enable_epoch" @@ -1234,5 +1240,6 @@ const ( FixRelayedBaseCostFlag core.EnableEpochFlag = "FixRelayedBaseCostFlag" MultiESDTNFTTransferAndExecuteByUserFlag core.EnableEpochFlag = "MultiESDTNFTTransferAndExecuteByUserFlag" FixRelayedMoveBalanceToNonPayableSCFlag core.EnableEpochFlag = "FixRelayedMoveBalanceToNonPayableSCFlag" + RelayedTransactionsV3Flag core.EnableEpochFlag = "RelayedTransactionsV3Flag" // all new flags must be added to createAllFlagsMap method, as part of enableEpochsHandler allFlagsDefined ) diff --git a/common/enablers/enableEpochsHandler.go b/common/enablers/enableEpochsHandler.go index 65cc7762796..cf31b3ab310 100644 --- a/common/enablers/enableEpochsHandler.go +++ b/common/enablers/enableEpochsHandler.go @@ -780,6 +780,12 @@ func (handler *enableEpochsHandler) createAllFlagsMap() { }, activationEpoch: handler.enableEpochsConfig.FixRelayedMoveBalanceToNonPayableSCEnableEpoch, }, + common.RelayedTransactionsV3Flag: { + isActiveInEpoch: func(epoch uint32) bool { + return epoch >= handler.enableEpochsConfig.RelayedTransactionsV3EnableEpoch + }, + activationEpoch: handler.enableEpochsConfig.RelayedTransactionsV3EnableEpoch, + }, } } diff --git a/common/enablers/enableEpochsHandler_test.go b/common/enablers/enableEpochsHandler_test.go index d0f9191055e..a5fce844f1b 100644 --- a/common/enablers/enableEpochsHandler_test.go +++ b/common/enablers/enableEpochsHandler_test.go @@ -123,6 +123,7 @@ func createEnableEpochsConfig() config.EnableEpochs { MultiESDTNFTTransferAndExecuteByUserEnableEpoch: 106, FixRelayedMoveBalanceToNonPayableSCEnableEpoch: 107, UseGasBoundedShouldFailExecutionEnableEpoch: 108, + RelayedTransactionsV3EnableEpoch: 109, } } @@ -448,6 +449,7 @@ func TestEnableEpochsHandler_GetActivationEpoch(t *testing.T) { require.Equal(t, cfg.FixRelayedBaseCostEnableEpoch, handler.GetActivationEpoch(common.FixRelayedBaseCostFlag)) require.Equal(t, cfg.MultiESDTNFTTransferAndExecuteByUserEnableEpoch, handler.GetActivationEpoch(common.MultiESDTNFTTransferAndExecuteByUserFlag)) require.Equal(t, cfg.FixRelayedMoveBalanceToNonPayableSCEnableEpoch, handler.GetActivationEpoch(common.FixRelayedMoveBalanceToNonPayableSCFlag)) + require.Equal(t, cfg.RelayedTransactionsV3EnableEpoch, handler.GetActivationEpoch(common.RelayedTransactionsV3Flag)) } func TestEnableEpochsHandler_IsInterfaceNil(t *testing.T) { diff --git a/common/reflectcommon/structFieldsUpdate.go b/common/reflectcommon/structFieldsUpdate.go index be8671eff4f..7f8940b0400 100644 --- a/common/reflectcommon/structFieldsUpdate.go +++ b/common/reflectcommon/structFieldsUpdate.go @@ -126,7 +126,7 @@ func trySetTheNewValue(value *reflect.Value, newValue interface{}) error { case reflect.Map: mapValue := reflect.ValueOf(newValue) - return tryUpdateMapValue(value, mapValue) + return trySetMapValue(value, mapValue) default: return fmt.Errorf("unsupported type <%s> when trying to set the value '%v' of type <%s>", valueKind, newValue, reflect.TypeOf(newValue)) } @@ -141,7 +141,7 @@ func trySetSliceValue(value *reflect.Value, newValue interface{}) error { item := sliceVal.Index(i) newItem := reflect.New(value.Type().Elem()).Elem() - err := trySetStructValue(&newItem, item) + err := trySetTheNewValue(&newItem, item.Interface()) if err != nil { return err } @@ -167,7 +167,7 @@ func trySetStructValue(value *reflect.Value, newValue reflect.Value) error { } } -func tryUpdateMapValue(value *reflect.Value, newValue reflect.Value) error { +func trySetMapValue(value *reflect.Value, newValue reflect.Value) error { if value.IsNil() { value.Set(reflect.MakeMap(value.Type())) } diff --git a/common/reflectcommon/structFieldsUpdate_test.go b/common/reflectcommon/structFieldsUpdate_test.go index 44d3ae7d694..27a30ea9d00 100644 --- a/common/reflectcommon/structFieldsUpdate_test.go +++ b/common/reflectcommon/structFieldsUpdate_test.go @@ -1204,6 +1204,79 @@ func TestAdaptStructureValueBasedOnPath(t *testing.T) { require.Equal(t, "unsupported type when trying to add value in type ", err.Error()) }) + t.Run("should work and override string array", func(t *testing.T) { + t.Parallel() + + testConfig, err := loadTestConfig("../../testscommon/toml/config.toml") + require.NoError(t, err) + + expectedArray := []string{"x", "y", "z"} + + err = AdaptStructureValueBasedOnPath(testConfig, "TestArray.Strings", expectedArray) + require.NoError(t, err) + require.Equal(t, expectedArray, testConfig.TestArray.Strings) + }) + + t.Run("should work and override int array", func(t *testing.T) { + t.Parallel() + + testConfig, err := loadTestConfig("../../testscommon/toml/config.toml") + require.NoError(t, err) + + expectedArray := []int{10, 20, 30} + + err = AdaptStructureValueBasedOnPath(testConfig, "TestArray.Ints", expectedArray) + require.NoError(t, err) + require.Equal(t, expectedArray, testConfig.TestArray.Ints) + }) + + t.Run("should work and override string array from toml", func(t *testing.T) { + t.Parallel() + + testConfig, err := loadTestConfig("../../testscommon/toml/config.toml") + require.NoError(t, err) + + overrideConfig, err := loadOverrideConfig("../../testscommon/toml/overwrite.toml") + require.NoError(t, err) + + err = AdaptStructureValueBasedOnPath(testConfig, overrideConfig.OverridableConfigTomlValues[38].Path, overrideConfig.OverridableConfigTomlValues[38].Value) + require.NoError(t, err) + expectedArray := []string{"x", "y", "z"} + require.Equal(t, expectedArray, testConfig.TestArray.Strings) + }) + + t.Run("should work and override int array from toml", func(t *testing.T) { + t.Parallel() + + testConfig, err := loadTestConfig("../../testscommon/toml/config.toml") + require.NoError(t, err) + + overrideConfig, err := loadOverrideConfig("../../testscommon/toml/overwrite.toml") + require.NoError(t, err) + + err = AdaptStructureValueBasedOnPath(testConfig, overrideConfig.OverridableConfigTomlValues[39].Path, overrideConfig.OverridableConfigTomlValues[39].Value) + require.NoError(t, err) + expectedArray := []int{10, 20, 30} + require.Equal(t, expectedArray, testConfig.TestArray.Ints) + }) + + t.Run("should work and override struct of arrays from toml", func(t *testing.T) { + t.Parallel() + + testConfig, err := loadTestConfig("../../testscommon/toml/config.toml") + require.NoError(t, err) + + overrideConfig, err := loadOverrideConfig("../../testscommon/toml/overwrite.toml") + require.NoError(t, err) + expectedStringsArray := []string{"x", "y", "z"} + expectedIntsArray := []int{10, 20, 30} + + err = AdaptStructureValueBasedOnPath(testConfig, overrideConfig.OverridableConfigTomlValues[40].Path, overrideConfig.OverridableConfigTomlValues[40].Value) + require.NoError(t, err) + require.Equal(t, expectedStringsArray, testConfig.TestArray.Strings) + require.Equal(t, expectedIntsArray, testConfig.TestArray.Ints) + }) + } func loadTestConfig(filepath string) (*toml.Config, error) { diff --git a/config/epochConfig.go b/config/epochConfig.go index a7fd67c680a..9a76744dbf4 100644 --- a/config/epochConfig.go +++ b/config/epochConfig.go @@ -122,6 +122,7 @@ type EnableEpochs struct { FixRelayedBaseCostEnableEpoch uint32 MultiESDTNFTTransferAndExecuteByUserEnableEpoch uint32 FixRelayedMoveBalanceToNonPayableSCEnableEpoch uint32 + RelayedTransactionsV3EnableEpoch uint32 BLSMultiSignerEnableEpoch []MultiSignerConfig } diff --git a/config/tomlConfig_test.go b/config/tomlConfig_test.go index 39a582b1ef2..767e37e3950 100644 --- a/config/tomlConfig_test.go +++ b/config/tomlConfig_test.go @@ -884,6 +884,9 @@ func TestEnableEpochConfig(t *testing.T) { # FixRelayedMoveBalanceToNonPayableSCEnableEpoch represents the epoch when the fix for relayed move balance to non payable sc will be enabled FixRelayedMoveBalanceToNonPayableSCEnableEpoch = 102 + # RelayedTransactionsV3EnableEpoch represents the epoch when the relayed transactions v3 will be enabled + RelayedTransactionsV3EnableEpoch = 103 + # MaxNodesChangeEnableEpoch holds configuration for changing the maximum number of nodes and the enabling epoch MaxNodesChangeEnableEpoch = [ { EpochEnable = 44, MaxNumNodes = 2169, NodesToShufflePerShard = 80 }, @@ -1004,6 +1007,7 @@ func TestEnableEpochConfig(t *testing.T) { FixRelayedBaseCostEnableEpoch: 100, MultiESDTNFTTransferAndExecuteByUserEnableEpoch: 101, FixRelayedMoveBalanceToNonPayableSCEnableEpoch: 102, + RelayedTransactionsV3EnableEpoch: 103, MaxNodesChangeEnableEpoch: []MaxNodesChangeConfig{ { EpochEnable: 44, diff --git a/dataRetriever/txpool/memorytests/memory_test.go b/dataRetriever/txpool/memorytests/memory_test.go index 1359ae8fb5f..e4e7f5f68b8 100644 --- a/dataRetriever/txpool/memorytests/memory_test.go +++ b/dataRetriever/txpool/memorytests/memory_test.go @@ -53,9 +53,9 @@ func TestShardedTxPool_MemoryFootprint(t *testing.T) { journals = append(journals, runScenario(t, newScenario(100, 1, core.MegabyteSize, "1_0"), memoryAssertion{90, 100}, memoryAssertion{0, 1})) journals = append(journals, runScenario(t, newScenario(10000, 1, 10240, "1_0"), memoryAssertion{96, 128}, memoryAssertion{0, 4})) - journals = append(journals, runScenario(t, newScenario(10, 10000, 1000, "1_0"), memoryAssertion{96, 140}, memoryAssertion{16, 25})) - journals = append(journals, runScenario(t, newScenario(150000, 1, 128, "1_0"), memoryAssertion{50, 80}, memoryAssertion{30, 40})) - journals = append(journals, runScenario(t, newScenario(1, 150000, 128, "1_0"), memoryAssertion{50, 80}, memoryAssertion{30, 40})) + journals = append(journals, runScenario(t, newScenario(10, 10000, 1000, "1_0"), memoryAssertion{96, 148}, memoryAssertion{16, 32})) + journals = append(journals, runScenario(t, newScenario(150000, 1, 128, "1_0"), memoryAssertion{50, 84}, memoryAssertion{30, 48})) + journals = append(journals, runScenario(t, newScenario(1, 150000, 128, "1_0"), memoryAssertion{50, 84}, memoryAssertion{30, 48})) for _, journal := range journals { journal.displayFootprintsSummary() diff --git a/factory/disabled/txCoordinator.go b/factory/disabled/txCoordinator.go index 9d8002fb034..a5d0dfe330a 100644 --- a/factory/disabled/txCoordinator.go +++ b/factory/disabled/txCoordinator.go @@ -25,8 +25,8 @@ func (txCoordinator *TxCoordinator) CreateReceiptsHash() ([]byte, error) { } // ComputeTransactionType does nothing as it is disabled -func (txCoordinator *TxCoordinator) ComputeTransactionType(_ data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return 0, 0 +func (txCoordinator *TxCoordinator) ComputeTransactionType(_ data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return 0, 0, false } // RequestMiniBlocksAndTransactions does nothing as it is disabled diff --git a/genesis/process/disabled/feeHandler.go b/genesis/process/disabled/feeHandler.go index 1fc34bbc2b5..0b3051cf845 100644 --- a/genesis/process/disabled/feeHandler.go +++ b/genesis/process/disabled/feeHandler.go @@ -12,6 +12,11 @@ import ( type FeeHandler struct { } +// ComputeGasUnitsFromRefundValue return 0 +func (fh *FeeHandler) ComputeGasUnitsFromRefundValue(_ data.TransactionWithFeeHandler, _ *big.Int, _ uint32) uint64 { + return 0 +} + // GasPriceModifier returns 1.0 func (fh *FeeHandler) GasPriceModifier() float64 { return 1.0 diff --git a/go.mod b/go.mod index cc5f4259b96..cd0ee839650 100644 --- a/go.mod +++ b/go.mod @@ -15,12 +15,12 @@ require ( github.com/klauspost/cpuid/v2 v2.2.5 github.com/mitchellh/mapstructure v1.5.0 github.com/multiversx/mx-chain-communication-go v1.1.1 - github.com/multiversx/mx-chain-core-go v1.2.23 + github.com/multiversx/mx-chain-core-go v1.2.24 github.com/multiversx/mx-chain-crypto-go v1.2.12 - github.com/multiversx/mx-chain-es-indexer-go v1.7.10 + github.com/multiversx/mx-chain-es-indexer-go v1.7.14 github.com/multiversx/mx-chain-logger-go v1.0.15 github.com/multiversx/mx-chain-scenario-go v1.4.4 - github.com/multiversx/mx-chain-storage-go v1.0.18 + github.com/multiversx/mx-chain-storage-go v1.0.19 github.com/multiversx/mx-chain-vm-common-go v1.5.16 github.com/multiversx/mx-chain-vm-go v1.5.37 github.com/multiversx/mx-chain-vm-v1_2-go v1.2.68 diff --git a/go.sum b/go.sum index b4ebd7201e0..ea2dab5a446 100644 --- a/go.sum +++ b/go.sum @@ -387,18 +387,18 @@ github.com/multiversx/concurrent-map v0.1.4 h1:hdnbM8VE4b0KYJaGY5yJS2aNIW9TFFsUY github.com/multiversx/concurrent-map v0.1.4/go.mod h1:8cWFRJDOrWHOTNSqgYCUvwT7c7eFQ4U2vKMOp4A/9+o= github.com/multiversx/mx-chain-communication-go v1.1.1 h1:y4DoQeQOJTaSUsRzczQFazf8JYQmInddypApqA3AkwM= github.com/multiversx/mx-chain-communication-go v1.1.1/go.mod h1:WK6bP4pGEHGDDna/AYRIMtl6G9OA0NByI1Lw8PmOnRM= -github.com/multiversx/mx-chain-core-go v1.2.23 h1:8WlCGqJHR2HQ0vN4feJwb7W4VrCwBGIzPPHunOOg5Wc= -github.com/multiversx/mx-chain-core-go v1.2.23/go.mod h1:B5zU4MFyJezmEzCsAHE9YNULmGCm2zbPHvl9hazNxmE= +github.com/multiversx/mx-chain-core-go v1.2.24 h1:O0X7N9GfNVUCE9fukXA+dvfCRRjViYn88zOaE7feUog= +github.com/multiversx/mx-chain-core-go v1.2.24/go.mod h1:B5zU4MFyJezmEzCsAHE9YNULmGCm2zbPHvl9hazNxmE= github.com/multiversx/mx-chain-crypto-go v1.2.12 h1:zWip7rpUS4CGthJxfKn5MZfMfYPjVjIiCID6uX5BSOk= github.com/multiversx/mx-chain-crypto-go v1.2.12/go.mod h1:HzcPpCm1zanNct/6h2rIh+MFrlXbjA5C8+uMyXj3LI4= -github.com/multiversx/mx-chain-es-indexer-go v1.7.10 h1:Umi7WN8h4BOXLw7CM3VgvaWkLGef7nXtaPIGbjBCT3U= -github.com/multiversx/mx-chain-es-indexer-go v1.7.10/go.mod h1:oGcRK2E3Syv6vRTszWrrb/TqD8akq0yeoMr1wPPiTO4= +github.com/multiversx/mx-chain-es-indexer-go v1.7.14 h1:V4fuubEUYskWCLQIkbuoB0WHoKyldLQRq/fllIzW1CU= +github.com/multiversx/mx-chain-es-indexer-go v1.7.14/go.mod h1:5Sr49FjWWzZ3/WcC3jzln8TlMSNToCIT9Lqy6P7i7bs= github.com/multiversx/mx-chain-logger-go v1.0.15 h1:HlNdK8etyJyL9NQ+6mIXyKPEBo+wRqOwi3n+m2QIHXc= github.com/multiversx/mx-chain-logger-go v1.0.15/go.mod h1:t3PRKaWB1M+i6gUfD27KXgzLJJC+mAQiN+FLlL1yoGQ= github.com/multiversx/mx-chain-scenario-go v1.4.4 h1:DVE2V+FPeyD/yWoC+KEfPK3jsFzHeruelESfpTlf460= github.com/multiversx/mx-chain-scenario-go v1.4.4/go.mod h1:kI+TWR3oIEgUkbwkHCPo2CQ3VjIge+ezGTibiSGwMxo= -github.com/multiversx/mx-chain-storage-go v1.0.18 h1:DA33o5COEjnCKclCeCvzXXI0zIgFp2QqZK32UTVvDes= -github.com/multiversx/mx-chain-storage-go v1.0.18/go.mod h1:eFDEOrG7Wiyk5I/ObpwcN2eoBlOnnfeEMTvTer1cymk= +github.com/multiversx/mx-chain-storage-go v1.0.19 h1:2R35MoSXcuNJOFmV5xEhcXqiEGZw6AYGy9R8J9KH66Q= +github.com/multiversx/mx-chain-storage-go v1.0.19/go.mod h1:Pb/BuVmiFqO66DSZO16KFkSUeom94x3e3Q9IloBvkYI= github.com/multiversx/mx-chain-vm-common-go v1.5.16 h1:g1SqYjxl7K66Y1O/q6tvDJ37fzpzlxCSfRzSm/woQQY= github.com/multiversx/mx-chain-vm-common-go v1.5.16/go.mod h1:1rSkXreUZNXyPTTdhj47M+Fy62yjxbu3aAsXEtKN3UY= github.com/multiversx/mx-chain-vm-go v1.5.37 h1:Iy3KCvM+DOq1f9UPA7uYK/rI3ZbBOXc2CVNO2/vm5zw= diff --git a/integrationTests/chainSimulator/mempool/mempool_test.go b/integrationTests/chainSimulator/mempool/mempool_test.go new file mode 100644 index 00000000000..704be8e40fb --- /dev/null +++ b/integrationTests/chainSimulator/mempool/mempool_test.go @@ -0,0 +1,331 @@ +package mempool + +import ( + "math/big" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/config" + "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" + "github.com/multiversx/mx-chain-go/storage" + "github.com/stretchr/testify/require" +) + +func TestMempoolWithChainSimulator_Selection(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + numSenders := 10000 + numTransactionsPerSender := 3 + shard := 0 + + simulator := startChainSimulator(t, func(cfg *config.Configs) {}) + defer simulator.Close() + + participants := createParticipants(t, simulator, numSenders) + noncesTracker := newNoncesTracker() + + transactions := make([]*transaction.Transaction, 0, numSenders*numTransactionsPerSender) + + for i := 0; i < numSenders; i++ { + sender := participants.sendersByShard[shard][i] + receiver := participants.receiverByShard[shard] + + for j := 0; j < numTransactionsPerSender; j++ { + tx := &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(sender), + Value: oneQuarterOfEGLD, + SndAddr: sender.Bytes, + RcvAddr: receiver.Bytes, + Data: []byte{}, + GasLimit: 50_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + } + + transactions = append(transactions, tx) + } + } + + sendTransactions(t, simulator, transactions) + time.Sleep(durationWaitAfterSendMany) + require.Equal(t, 30_000, getNumTransactionsInPool(simulator, shard)) + + selectedTransactions, gas := selectTransactions(t, simulator, shard) + require.Equal(t, 30_000, len(selectedTransactions)) + require.Equal(t, 50_000*30_000, int(gas)) + + err := simulator.GenerateBlocks(1) + require.Nil(t, err) + require.Equal(t, 27_756, getNumTransactionsInCurrentBlock(simulator, shard)) + + selectedTransactions, gas = selectTransactions(t, simulator, shard) + require.Equal(t, 30_000-27_756, len(selectedTransactions)) + require.Equal(t, 50_000*(30_000-27_756), int(gas)) +} + +func TestMempoolWithChainSimulator_Selection_WhenUsersHaveZeroBalance_WithRelayedV3(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + shard := 0 + + simulator := startChainSimulator(t, func(cfg *config.Configs) {}) + defer simulator.Close() + + err := simulator.GenerateBlocksUntilEpochIsReached(2) + require.NoError(t, err) + + relayer, err := simulator.GenerateAndMintWalletAddress(uint32(shard), oneEGLD) + require.NoError(t, err) + + receiver, err := simulator.GenerateAndMintWalletAddress(uint32(shard), big.NewInt(0)) + require.NoError(t, err) + + alice, err := simulator.GenerateAndMintWalletAddress(uint32(shard), big.NewInt(0)) + require.NoError(t, err) + + bob, err := simulator.GenerateAndMintWalletAddress(uint32(shard), big.NewInt(0)) + require.NoError(t, err) + + err = simulator.GenerateBlocks(1) + require.Nil(t, err) + + noncesTracker := newNoncesTracker() + transactions := make([]*transaction.Transaction, 0) + + // Transfer (executable, invalid) from Alice (relayed) + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(alice), + Value: oneQuarterOfEGLD, + SndAddr: alice.Bytes, + RcvAddr: receiver.Bytes, + RelayerAddr: relayer.Bytes, + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_002, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + // Contract call from Bob (relayed) + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(bob), + Value: big.NewInt(0), + SndAddr: bob.Bytes, + RcvAddr: receiver.Bytes, + RelayerAddr: relayer.Bytes, + Data: []byte("hello"), + GasLimit: 100_000 + 5*1500, + GasPrice: 1_000_000_001, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + sendTransactions(t, simulator, transactions) + time.Sleep(durationWaitAfterSendSome) + require.Equal(t, 2, getNumTransactionsInPool(simulator, shard)) + + selectedTransactions, _ := selectTransactions(t, simulator, shard) + require.Equal(t, 2, len(selectedTransactions)) + require.Equal(t, alice.Bytes, selectedTransactions[0].Tx.GetSndAddr()) + require.Equal(t, bob.Bytes, selectedTransactions[1].Tx.GetSndAddr()) + + err = simulator.GenerateBlocks(1) + require.Nil(t, err) + require.Equal(t, 2, getNumTransactionsInCurrentBlock(simulator, shard)) + + require.Equal(t, "invalid", getTransaction(t, simulator, shard, selectedTransactions[0].TxHash).Status.String()) + require.Equal(t, "success", getTransaction(t, simulator, shard, selectedTransactions[1].TxHash).Status.String()) +} + +func TestMempoolWithChainSimulator_Selection_WhenInsufficientBalanceForFee_WithRelayedV3(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + numSenders := 3 + shard := 0 + + simulator := startChainSimulator(t, func(cfg *config.Configs) {}) + defer simulator.Close() + + err := simulator.GenerateBlocksUntilEpochIsReached(2) + require.NoError(t, err) + + participants := createParticipants(t, simulator, numSenders) + noncesTracker := newNoncesTracker() + + alice := participants.sendersByShard[shard][0] + bob := participants.sendersByShard[shard][1] + carol := participants.sendersByShard[shard][2] + relayer := participants.relayerByShard[shard] + receiver := participants.receiverByShard[shard] + + transactions := make([]*transaction.Transaction, 0) + + // Consume most of relayer's balance. Keep an amount that is enough for the fee of two simple transfer transactions. + currentRelayerBalance := int64(1000000000000000000) + feeForTransfer := int64(50_000 * 1_000_000_004) + feeForRelayingTransactionsOfAliceAndBob := int64(100_000*1_000_000_003 + 100_000*1_000_000_002) + + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(relayer), + Value: big.NewInt(currentRelayerBalance - feeForTransfer - feeForRelayingTransactionsOfAliceAndBob), + SndAddr: relayer.Bytes, + RcvAddr: receiver.Bytes, + Data: []byte{}, + GasLimit: 50_000, + GasPrice: 1_000_000_004, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + }) + + // Transfer from Alice (relayed) + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(alice), + Value: oneQuarterOfEGLD, + SndAddr: alice.Bytes, + RcvAddr: receiver.Bytes, + RelayerAddr: relayer.Bytes, + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_003, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + // Transfer from Bob (relayed) + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(bob), + Value: oneQuarterOfEGLD, + SndAddr: bob.Bytes, + RcvAddr: receiver.Bytes, + RelayerAddr: relayer.Bytes, + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_002, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + // Transfer from Carol (relayed) - this one should not be selected due to insufficient balance (of the relayer) + transactions = append(transactions, &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(carol), + Value: oneQuarterOfEGLD, + SndAddr: carol.Bytes, + RcvAddr: receiver.Bytes, + RelayerAddr: relayer.Bytes, + Data: []byte{}, + GasLimit: 100_000, + GasPrice: 1_000_000_001, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + RelayerSignature: []byte("signature"), + }) + + sendTransactions(t, simulator, transactions) + time.Sleep(durationWaitAfterSendSome) + require.Equal(t, 4, getNumTransactionsInPool(simulator, shard)) + + selectedTransactions, _ := selectTransactions(t, simulator, shard) + require.Equal(t, 3, len(selectedTransactions)) + require.Equal(t, relayer.Bytes, selectedTransactions[0].Tx.GetSndAddr()) + require.Equal(t, alice.Bytes, selectedTransactions[1].Tx.GetSndAddr()) + require.Equal(t, bob.Bytes, selectedTransactions[2].Tx.GetSndAddr()) +} + +func TestMempoolWithChainSimulator_Eviction(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + numSenders := 10000 + numTransactionsPerSender := 30 + shard := 0 + + simulator := startChainSimulator(t, func(cfg *config.Configs) {}) + defer simulator.Close() + + participants := createParticipants(t, simulator, numSenders) + noncesTracker := newNoncesTracker() + + transactions := make([]*transaction.Transaction, 0, numSenders*numTransactionsPerSender) + + for i := 0; i < numSenders; i++ { + sender := participants.sendersByShard[shard][i] + receiver := participants.receiverByShard[shard] + + for j := 0; j < numTransactionsPerSender; j++ { + tx := &transaction.Transaction{ + Nonce: noncesTracker.getThenIncrementNonce(sender), + Value: oneQuarterOfEGLD, + SndAddr: sender.Bytes, + RcvAddr: receiver.Bytes, + Data: []byte{}, + GasLimit: 50_000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + } + + transactions = append(transactions, tx) + } + } + + sendTransactions(t, simulator, transactions) + time.Sleep(durationWaitAfterSendMany) + require.Equal(t, 300_000, getNumTransactionsInPool(simulator, shard)) + + // Send one more transaction (fill up the mempool) + sendTransaction(t, simulator, &transaction.Transaction{ + Nonce: 42, + Value: oneEGLD, + SndAddr: participants.sendersByShard[shard][7].Bytes, + RcvAddr: participants.receiverByShard[shard].Bytes, + Data: []byte{}, + GasLimit: 50000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + }) + + time.Sleep(durationWaitAfterSendSome) + require.Equal(t, 300_001, getNumTransactionsInPool(simulator, shard)) + + // Send one more transaction to trigger eviction + sendTransaction(t, simulator, &transaction.Transaction{ + Nonce: 42, + Value: oneEGLD, + SndAddr: participants.sendersByShard[shard][7].Bytes, + RcvAddr: participants.receiverByShard[shard].Bytes, + Data: []byte{}, + GasLimit: 50000, + GasPrice: 1_000_000_000, + ChainID: []byte(configs.ChainID), + Version: 2, + Signature: []byte("signature"), + }) + + time.Sleep(2 * time.Second) + + expectedNumTransactionsInPool := 300_000 + 1 + 1 - int(storage.TxPoolSourceMeNumItemsToPreemptivelyEvict) + require.Equal(t, expectedNumTransactionsInPool, getNumTransactionsInPool(simulator, shard)) +} diff --git a/integrationTests/chainSimulator/mempool/testutils_test.go b/integrationTests/chainSimulator/mempool/testutils_test.go new file mode 100644 index 00000000000..3d4a0afd5f7 --- /dev/null +++ b/integrationTests/chainSimulator/mempool/testutils_test.go @@ -0,0 +1,193 @@ +package mempool + +import ( + "encoding/hex" + "math/big" + "strconv" + "testing" + "time" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/config" + testsChainSimulator "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" + "github.com/multiversx/mx-chain-go/node/chainSimulator" + "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" + "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/block/preprocess" + "github.com/multiversx/mx-chain-go/storage/txcache" + "github.com/multiversx/mx-chain-go/testscommon" + "github.com/stretchr/testify/require" +) + +var ( + oneEGLD = big.NewInt(1000000000000000000) + oneQuarterOfEGLD = big.NewInt(250000000000000000) + durationWaitAfterSendMany = 1500 * time.Millisecond + durationWaitAfterSendSome = 50 * time.Millisecond +) + +func startChainSimulator(t *testing.T, alterConfigsFunction func(cfg *config.Configs)) testsChainSimulator.ChainSimulator { + simulator, err := chainSimulator.NewChainSimulator(chainSimulator.ArgsChainSimulator{ + BypassTxSignatureCheck: true, + TempDir: t.TempDir(), + PathToInitialConfig: "../../../cmd/node/config/", + NumOfShards: 1, + GenesisTimestamp: time.Now().Unix(), + RoundDurationInMillis: uint64(4000), + RoundsPerEpoch: core.OptionalUint64{ + HasValue: true, + Value: 10, + }, + ApiInterface: api.NewNoApiInterface(), + MinNodesPerShard: 1, + MetaChainMinNodes: 1, + NumNodesWaitingListMeta: 0, + NumNodesWaitingListShard: 0, + AlterConfigsFunction: alterConfigsFunction, + }) + require.NoError(t, err) + require.NotNil(t, simulator) + + err = simulator.GenerateBlocksUntilEpochIsReached(1) + require.NoError(t, err) + + return simulator +} + +type participantsHolder struct { + sendersByShard map[int][]dtos.WalletAddress + relayerByShard map[int]dtos.WalletAddress + receiverByShard map[int]dtos.WalletAddress +} + +func newParticipantsHolder() *participantsHolder { + return &participantsHolder{ + sendersByShard: make(map[int][]dtos.WalletAddress), + relayerByShard: make(map[int]dtos.WalletAddress), + receiverByShard: make(map[int]dtos.WalletAddress), + } +} + +func createParticipants(t *testing.T, simulator testsChainSimulator.ChainSimulator, numSendersPerShard int) *participantsHolder { + numShards := int(simulator.GetNodeHandler(0).GetShardCoordinator().NumberOfShards()) + participants := newParticipantsHolder() + + for shard := 0; shard < numShards; shard++ { + senders := make([]dtos.WalletAddress, 0, numSendersPerShard) + + for i := 0; i < numSendersPerShard; i++ { + sender, err := simulator.GenerateAndMintWalletAddress(uint32(shard), oneEGLD) + require.NoError(t, err) + + senders = append(senders, sender) + } + + relayer, err := simulator.GenerateAndMintWalletAddress(uint32(shard), oneEGLD) + require.NoError(t, err) + + receiver, err := simulator.GenerateAndMintWalletAddress(uint32(shard), big.NewInt(0)) + require.NoError(t, err) + + participants.sendersByShard[shard] = senders + participants.relayerByShard[shard] = relayer + participants.receiverByShard[shard] = receiver + } + + err := simulator.GenerateBlocks(1) + require.Nil(t, err) + + return participants +} + +type noncesTracker struct { + nonceByAddress map[string]uint64 +} + +func newNoncesTracker() *noncesTracker { + return &noncesTracker{ + nonceByAddress: make(map[string]uint64), + } +} + +func (tracker *noncesTracker) getThenIncrementNonce(address dtos.WalletAddress) uint64 { + nonce, ok := tracker.nonceByAddress[address.Bech32] + if !ok { + tracker.nonceByAddress[address.Bech32] = 0 + } + + tracker.nonceByAddress[address.Bech32]++ + return nonce +} + +func sendTransactions(t *testing.T, simulator testsChainSimulator.ChainSimulator, transactions []*transaction.Transaction) { + transactionsBySenderShard := make(map[int][]*transaction.Transaction) + shardCoordinator := simulator.GetNodeHandler(0).GetShardCoordinator() + + for _, tx := range transactions { + shard := int(shardCoordinator.ComputeId(tx.SndAddr)) + transactionsBySenderShard[shard] = append(transactionsBySenderShard[shard], tx) + } + + for shard, transactionsFromShard := range transactionsBySenderShard { + node := simulator.GetNodeHandler(uint32(shard)) + + for _, tx := range transactionsFromShard { + err := node.GetFacadeHandler().ValidateTransaction(tx) + require.NoError(t, err) + } + + numSent, err := node.GetFacadeHandler().SendBulkTransactions(transactionsFromShard) + + require.NoError(t, err) + require.Equal(t, len(transactionsFromShard), int(numSent)) + } +} + +func sendTransaction(t *testing.T, simulator testsChainSimulator.ChainSimulator, tx *transaction.Transaction) { + sendTransactions(t, simulator, []*transaction.Transaction{tx}) +} + +func selectTransactions(t *testing.T, simulator testsChainSimulator.ChainSimulator, shard int) ([]*txcache.WrappedTransaction, uint64) { + shardAsString := strconv.Itoa(shard) + node := simulator.GetNodeHandler(uint32(shard)) + accountsAdapter := node.GetStateComponents().AccountsAdapter() + poolsHolder := node.GetDataComponents().Datapool().Transactions() + + selectionSession, err := preprocess.NewSelectionSession(preprocess.ArgsSelectionSession{ + AccountsAdapter: accountsAdapter, + TransactionsProcessor: &testscommon.TxProcessorStub{}, + }) + require.NoError(t, err) + + mempool := poolsHolder.ShardDataStore(shardAsString).(*txcache.TxCache) + + selectedTransactions, gas := mempool.SelectTransactions( + selectionSession, + process.TxCacheSelectionGasRequested, + process.TxCacheSelectionMaxNumTxs, + process.TxCacheSelectionLoopMaximumDuration, + ) + + return selectedTransactions, gas +} + +func getNumTransactionsInPool(simulator testsChainSimulator.ChainSimulator, shard int) int { + node := simulator.GetNodeHandler(uint32(shard)) + poolsHolder := node.GetDataComponents().Datapool().Transactions() + return int(poolsHolder.GetCounts().GetTotal()) +} + +func getNumTransactionsInCurrentBlock(simulator testsChainSimulator.ChainSimulator, shard int) int { + node := simulator.GetNodeHandler(uint32(shard)) + currentBlock := node.GetDataComponents().Blockchain().GetCurrentBlockHeader() + return int(currentBlock.GetTxCount()) +} + +func getTransaction(t *testing.T, simulator testsChainSimulator.ChainSimulator, shard int, hash []byte) *transaction.ApiTransactionResult { + hashAsHex := hex.EncodeToString(hash) + transaction, err := simulator.GetNodeHandler(uint32(shard)).GetFacadeHandler().GetTransaction(hashAsHex, true) + require.NoError(t, err) + return transaction +} diff --git a/integrationTests/chainSimulator/relayedTx/relayedTx_test.go b/integrationTests/chainSimulator/relayedTx/relayedTx_test.go index f7c0f74649b..b7426c63f5f 100644 --- a/integrationTests/chainSimulator/relayedTx/relayedTx_test.go +++ b/integrationTests/chainSimulator/relayedTx/relayedTx_test.go @@ -1,15 +1,18 @@ package relayedTx import ( + "crypto/rand" "encoding/hex" "encoding/json" "math/big" + "strconv" "strings" "testing" "time" "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/config" testsChainSimulator "github.com/multiversx/mx-chain-go/integrationTests/chainSimulator" "github.com/multiversx/mx-chain-go/integrationTests/vm/wasm" @@ -17,6 +20,11 @@ import ( "github.com/multiversx/mx-chain-go/node/chainSimulator/components/api" "github.com/multiversx/mx-chain-go/node/chainSimulator/configs" "github.com/multiversx/mx-chain-go/node/chainSimulator/dtos" + chainSimulatorProcess "github.com/multiversx/mx-chain-go/node/chainSimulator/process" + "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/sharding" + "github.com/multiversx/mx-chain-go/vm" + logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/require" ) @@ -25,27 +33,717 @@ const ( minGasPrice = 1_000_000_000 minGasLimit = 50_000 gasPerDataByte = 1_500 + deductionFactor = 100 txVersion = 2 - mockTxSignature = "sig" + mockTxSignature = "ssig" + mockRelayerTxSignature = "rsig" maxNumOfBlocksToGenerateWhenExecutingTx = 10 roundsPerEpoch = 30 + guardAccountCost = 250_000 + extraGasLimitForGuarded = minGasLimit ) var ( oneEGLD = big.NewInt(1000000000000000000) ) +func TestRelayedV3WithChainSimulator(t *testing.T) { + t.Run("sender == relayer move balance should consume fee", testRelayedV3RelayedBySenderMoveBalance()) + t.Run("receiver == relayer move balance should consume fee", testRelayedV3RelayedByReceiverMoveBalance()) + t.Run("successful intra shard move balance", testRelayedV3MoveBalance(0, 0, false, false)) + t.Run("successful intra shard guarded move balance", testRelayedV3MoveBalance(0, 0, false, true)) + t.Run("successful intra shard move balance with extra gas", testRelayedV3MoveBalance(0, 0, true, false)) + t.Run("successful cross shard move balance", testRelayedV3MoveBalance(0, 1, false, false)) + t.Run("successful cross shard guarded move balance", testRelayedV3MoveBalance(0, 1, false, true)) + t.Run("successful cross shard move balance with extra gas", testRelayedV3MoveBalance(0, 1, true, false)) + t.Run("intra shard move balance, lower nonce", testRelayedV3MoveBalanceLowerNonce(0, 0)) + t.Run("cross shard move balance, lower nonce", testRelayedV3MoveBalanceLowerNonce(0, 1)) + t.Run("intra shard move balance, invalid gas", testRelayedV3MoveInvalidGasLimit(0, 0)) + t.Run("cross shard move balance, invalid gas", testRelayedV3MoveInvalidGasLimit(0, 1)) + + t.Run("successful intra shard sc call with refunds, existing sender", testRelayedV3ScCall(0, 0, true, false)) + t.Run("successful intra shard sc call with refunds, existing sender, relayed by sender", testRelayedV3ScCall(0, 0, true, true)) + t.Run("successful intra shard sc call with refunds, new sender", testRelayedV3ScCall(0, 0, false, false)) + t.Run("successful cross shard sc call with refunds, existing sender", testRelayedV3ScCall(0, 1, true, false)) + t.Run("successful cross shard sc call with refunds, existing sender, relayed by sender", testRelayedV3ScCall(0, 1, true, true)) + t.Run("successful cross shard sc call with refunds, new sender", testRelayedV3ScCall(0, 1, false, false)) + t.Run("intra shard sc call, invalid gas", testRelayedV3ScCallInvalidGasLimit(0, 0)) + t.Run("cross shard sc call, invalid gas", testRelayedV3ScCallInvalidGasLimit(0, 1)) + t.Run("intra shard sc call, invalid method", testRelayedV3ScCallInvalidMethod(0, 0)) + t.Run("cross shard sc call, invalid method", testRelayedV3ScCallInvalidMethod(0, 1)) + + t.Run("create new delegation contract", testRelayedV3MetaInteraction()) +} + +func testRelayedV3MoveBalance( + relayerShard uint32, + destinationShard uint32, + extraGas bool, + guardedTx bool, +) func(t *testing.T) { + return func(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + providedActivationEpoch := uint32(1) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.FixRelayedBaseCostEnableEpoch = providedActivationEpoch + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3EnableEpoch = providedActivationEpoch + } + + cs := startChainSimulator(t, alterConfigsFunc) + defer cs.Close() + + initialBalance := big.NewInt(0).Mul(oneEGLD, big.NewInt(10)) + relayer, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + sender, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + receiver, err := cs.GenerateAndMintWalletAddress(destinationShard, big.NewInt(0)) + require.NoError(t, err) + + guardian, err := cs.GenerateAndMintWalletAddress(0, initialBalance) + require.NoError(t, err) + + // generate one block so the minting has effect + err = cs.GenerateBlocks(1) + require.NoError(t, err) + + senderNonce := uint64(0) + if guardedTx { + // Set guardian for sender + setGuardianTxData := "SetGuardian@" + hex.EncodeToString(guardian.Bytes) + "@" + hex.EncodeToString([]byte("uuid")) + setGuardianGasLimit := minGasLimit + 1500*len(setGuardianTxData) + guardAccountCost + setGuardianTx := generateTransaction(sender.Bytes, senderNonce, sender.Bytes, big.NewInt(0), setGuardianTxData, uint64(setGuardianGasLimit)) + _, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(setGuardianTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + senderNonce++ + + // fast-forward until the guardian becomes active + err = cs.GenerateBlocks(roundsPerEpoch * 20) + require.NoError(t, err) + + // guard account + guardAccountTxData := "GuardAccount" + guardAccountGasLimit := minGasLimit + 1500*len(guardAccountTxData) + guardAccountCost + guardAccountTx := generateTransaction(sender.Bytes, senderNonce, sender.Bytes, big.NewInt(0), guardAccountTxData, uint64(guardAccountGasLimit)) + _, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(guardAccountTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + senderNonce++ + } + + senderBalanceBefore := getBalance(t, cs, sender) + + gasLimit := minGasLimit * 2 + extraGasLimit := 0 + if extraGas { + extraGasLimit = minGasLimit + } + if guardedTx { + gasLimit += extraGasLimitForGuarded + } + relayedTx := generateRelayedV3Transaction(sender.Bytes, senderNonce, receiver.Bytes, relayer.Bytes, oneEGLD, "", uint64(gasLimit+extraGasLimit)) + + if guardedTx { + relayedTx.GuardianAddr = guardian.Bytes + relayedTx.GuardianSignature = []byte(mockTxSignature) + relayedTx.Options = 2 + } + + result, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + + isIntraShard := relayerShard == destinationShard + if isIntraShard { + require.NoError(t, cs.GenerateBlocks(maxNumOfBlocksToGenerateWhenExecutingTx)) + } + + // check fee fields + initiallyPaidFee, fee, gasUsed := computeTxGasAndFeeBasedOnRefund(result, big.NewInt(0), true, guardedTx) + require.Equal(t, initiallyPaidFee.String(), result.InitiallyPaidFee) + require.Equal(t, fee.String(), result.Fee) + require.Equal(t, gasUsed, result.GasUsed) + + // check relayer balance + relayerBalanceAfter := getBalance(t, cs, relayer) + relayerFee := big.NewInt(0).Sub(initialBalance, relayerBalanceAfter) + require.Equal(t, fee.String(), relayerFee.String()) + + // check sender balance + senderBalanceAfter := getBalance(t, cs, sender) + senderBalanceDiff := big.NewInt(0).Sub(senderBalanceBefore, senderBalanceAfter) + require.Equal(t, oneEGLD.String(), senderBalanceDiff.String()) + + // check receiver balance + receiverBalanceAfter := getBalance(t, cs, receiver) + require.Equal(t, oneEGLD.String(), receiverBalanceAfter.String()) + + // check scrs, should be none + require.Zero(t, len(result.SmartContractResults)) + + // check intra shard logs, should be none + require.Nil(t, result.Logs) + + if extraGas && isIntraShard { + require.NotNil(t, result.Receipt) + } + } +} + +func testRelayedV3MoveBalanceLowerNonce( + relayerShard uint32, + receiverShard uint32, +) func(t *testing.T) { + return func(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + providedActivationEpoch := uint32(1) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.FixRelayedBaseCostEnableEpoch = providedActivationEpoch + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3EnableEpoch = providedActivationEpoch + } + + cs := startChainSimulator(t, alterConfigsFunc) + defer cs.Close() + + initialBalance := big.NewInt(0).Mul(oneEGLD, big.NewInt(10)) + relayer, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + sender, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + receiver, err := cs.GenerateAndMintWalletAddress(receiverShard, big.NewInt(0)) + require.NoError(t, err) + + // generate one block so the minting has effect + err = cs.GenerateBlocks(1) + require.NoError(t, err) + + gasLimit := minGasLimit * 2 + relayedTx := generateRelayedV3Transaction(sender.Bytes, 0, receiver.Bytes, relayer.Bytes, oneEGLD, "", uint64(gasLimit)) + + _, err = cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + + // send same tx again, lower nonce + result, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.Contains(t, err.Error(), process.ErrWrongTransaction.Error()) + require.Nil(t, result) + } +} + +func testRelayedV3MoveInvalidGasLimit( + relayerShard uint32, + receiverShard uint32, +) func(t *testing.T) { + return func(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + providedActivationEpoch := uint32(1) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.FixRelayedBaseCostEnableEpoch = providedActivationEpoch + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3EnableEpoch = providedActivationEpoch + } + + cs := startChainSimulator(t, alterConfigsFunc) + defer cs.Close() + + initialBalance := big.NewInt(0).Mul(oneEGLD, big.NewInt(10)) + relayer, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + sender, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + receiver, err := cs.GenerateAndMintWalletAddress(receiverShard, big.NewInt(0)) + require.NoError(t, err) + + // generate one block so the minting has effect + err = cs.GenerateBlocks(1) + require.NoError(t, err) + + gasLimit := minGasLimit + relayedTx := generateRelayedV3Transaction(sender.Bytes, 0, receiver.Bytes, relayer.Bytes, oneEGLD, "", uint64(gasLimit)) + + result, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.Contains(t, err.Error(), process.ErrInsufficientGasLimitInTx.Error()) + require.Nil(t, result) + } +} + +func testRelayedV3ScCall( + relayerShard uint32, + ownerShard uint32, + existingSenderWithBalance bool, + relayedBySender bool, +) func(t *testing.T) { + return func(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + providedActivationEpoch := uint32(1) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.FixRelayedBaseCostEnableEpoch = providedActivationEpoch + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3EnableEpoch = providedActivationEpoch + } + + cs := startChainSimulator(t, alterConfigsFunc) + defer cs.Close() + + initialBalance := big.NewInt(0).Mul(oneEGLD, big.NewInt(10)) + relayer, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + relayerInitialBalance := initialBalance + + sender, senderInitialBalance := prepareSender(t, cs, existingSenderWithBalance, relayerShard, initialBalance) + if relayedBySender { + relayer = sender + relayerInitialBalance = senderInitialBalance + } + + owner, err := cs.GenerateAndMintWalletAddress(ownerShard, initialBalance) + require.NoError(t, err) + + // generate one block so the minting has effect + err = cs.GenerateBlocks(1) + require.NoError(t, err) + + resultDeploy, scAddressBytes := deployAdder(t, cs, owner, 0) + refundDeploy := getRefundValue(resultDeploy.SmartContractResults) + + // send relayed tx + txDataAdd := "add@" + hex.EncodeToString(big.NewInt(1).Bytes()) + gasLimit := uint64(3000000) + relayedTx := generateRelayedV3Transaction(sender.Bytes, 0, scAddressBytes, relayer.Bytes, big.NewInt(0), txDataAdd, gasLimit) + + result, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + + // if cross shard, generate few more blocks for eventual refunds to be executed + if relayerShard != ownerShard { + require.NoError(t, cs.GenerateBlocks(maxNumOfBlocksToGenerateWhenExecutingTx)) + } + + checkSum(t, cs.GetNodeHandler(ownerShard), scAddressBytes, owner.Bytes, 1) + + refundValue := getRefundValue(result.SmartContractResults) + require.NotZero(t, refundValue.Uint64()) + + // check fee fields + initiallyPaidFee, fee, gasUsed := computeTxGasAndFeeBasedOnRefund(result, refundValue, false, false) + require.Equal(t, initiallyPaidFee.String(), result.InitiallyPaidFee) + require.Equal(t, fee.String(), result.Fee) + require.Equal(t, gasUsed, result.GasUsed) + + // check relayer balance + relayerBalanceAfter := getBalance(t, cs, relayer) + relayerFee := big.NewInt(0).Sub(relayerInitialBalance, relayerBalanceAfter) + require.Equal(t, fee.String(), relayerFee.String()) + + // check sender balance, only if the tx was not relayed by sender + if !relayedBySender { + senderBalanceAfter := getBalance(t, cs, sender) + require.Equal(t, senderInitialBalance.String(), senderBalanceAfter.String()) + } + + // check owner balance + _, feeDeploy, _ := computeTxGasAndFeeBasedOnRefund(resultDeploy, refundDeploy, false, false) + ownerBalanceAfter := getBalance(t, cs, owner) + ownerFee := big.NewInt(0).Sub(initialBalance, ownerBalanceAfter) + require.Equal(t, feeDeploy.String(), ownerFee.String()) + + // check scrs + require.Equal(t, 1, len(result.SmartContractResults)) + for _, scr := range result.SmartContractResults { + checkSCRSucceeded(t, cs, scr) + } + } +} + +func testRelayedV3RelayedBySenderMoveBalance() func(t *testing.T) { + return func(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + providedActivationEpoch := uint32(1) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.FixRelayedBaseCostEnableEpoch = providedActivationEpoch + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3EnableEpoch = providedActivationEpoch + } + + cs := startChainSimulator(t, alterConfigsFunc) + defer cs.Close() + + initialBalance := big.NewInt(0).Mul(oneEGLD, big.NewInt(10)) + + sender, err := cs.GenerateAndMintWalletAddress(0, initialBalance) + require.NoError(t, err) + + // generate one block so the minting has effect + err = cs.GenerateBlocks(1) + require.NoError(t, err) + + senderNonce := uint64(0) + senderBalanceBefore := getBalance(t, cs, sender) + + gasLimit := minGasLimit * 2 + relayedTx := generateRelayedV3Transaction(sender.Bytes, senderNonce, sender.Bytes, sender.Bytes, big.NewInt(0), "", uint64(gasLimit)) + + result, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + + // check fee fields + initiallyPaidFee, fee, gasUsed := computeTxGasAndFeeBasedOnRefund(result, big.NewInt(0), true, false) + require.Equal(t, initiallyPaidFee.String(), result.InitiallyPaidFee) + require.Equal(t, fee.String(), result.Fee) + require.Equal(t, gasUsed, result.GasUsed) + + // check sender balance + expectedFee := core.SafeMul(uint64(gasLimit), uint64(minGasPrice)) + senderBalanceAfter := getBalance(t, cs, sender) + senderBalanceDiff := big.NewInt(0).Sub(senderBalanceBefore, senderBalanceAfter) + require.Equal(t, expectedFee.String(), senderBalanceDiff.String()) + + // check scrs, should be none + require.Zero(t, len(result.SmartContractResults)) + + // check intra shard logs, should be none + require.Nil(t, result.Logs) + } +} + +func testRelayedV3RelayedByReceiverMoveBalance() func(t *testing.T) { + return func(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + providedActivationEpoch := uint32(1) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.FixRelayedBaseCostEnableEpoch = providedActivationEpoch + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3EnableEpoch = providedActivationEpoch + } + + cs := startChainSimulator(t, alterConfigsFunc) + defer cs.Close() + + initialBalance := big.NewInt(0).Mul(oneEGLD, big.NewInt(10)) + + sender, err := cs.GenerateAndMintWalletAddress(0, initialBalance) + require.NoError(t, err) + + receiver, err := cs.GenerateAndMintWalletAddress(0, initialBalance) + require.NoError(t, err) + + // generate one block so the minting has effect + err = cs.GenerateBlocks(1) + require.NoError(t, err) + + senderNonce := uint64(0) + receiverBalanceBefore := getBalance(t, cs, receiver) + + gasLimit := minGasLimit * 2 + relayedTx := generateRelayedV3Transaction(sender.Bytes, senderNonce, receiver.Bytes, receiver.Bytes, big.NewInt(0), "", uint64(gasLimit)) + + result, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + + // check fee fields + initiallyPaidFee, fee, gasUsed := computeTxGasAndFeeBasedOnRefund(result, big.NewInt(0), true, false) + require.Equal(t, initiallyPaidFee.String(), result.InitiallyPaidFee) + require.Equal(t, fee.String(), result.Fee) + require.Equal(t, gasUsed, result.GasUsed) + + // check sender balance + senderBalanceAfter := getBalance(t, cs, sender) + require.Equal(t, senderBalanceAfter.String(), initialBalance.String()) + + // check receiver balance + expectedFee := core.SafeMul(uint64(gasLimit), uint64(minGasPrice)) + receiverBalanceAfter := getBalance(t, cs, receiver) + receiverBalanceDiff := big.NewInt(0).Sub(receiverBalanceBefore, receiverBalanceAfter) + require.Equal(t, receiverBalanceDiff.String(), expectedFee.String()) + + // check scrs, should be none + require.Zero(t, len(result.SmartContractResults)) + + // check intra shard logs, should be none + require.Nil(t, result.Logs) + } +} + +func prepareSender( + t *testing.T, + cs testsChainSimulator.ChainSimulator, + existingSenderWithBalance bool, + shard uint32, + initialBalance *big.Int, +) (dtos.WalletAddress, *big.Int) { + if existingSenderWithBalance { + sender, err := cs.GenerateAndMintWalletAddress(shard, initialBalance) + require.NoError(t, err) + + return sender, initialBalance + } + + shardC := cs.GetNodeHandler(shard).GetShardCoordinator() + pkConv := cs.GetNodeHandler(shard).GetCoreComponents().AddressPubKeyConverter() + newAddress := generateAddressInShard(shardC, pkConv.Len()) + return dtos.WalletAddress{ + Bech32: pkConv.SilentEncode(newAddress, logger.GetOrCreate("tmp")), + Bytes: newAddress, + }, big.NewInt(0) +} + +func generateAddressInShard(shardCoordinator sharding.Coordinator, len int) []byte { + for { + buff := generateAddress(len) + shardID := shardCoordinator.ComputeId(buff) + if shardID == shardCoordinator.SelfId() { + return buff + } + } +} + +func generateAddress(len int) []byte { + buff := make([]byte, len) + _, _ = rand.Read(buff) + + return buff +} + +func testRelayedV3ScCallInvalidGasLimit( + relayerShard uint32, + ownerShard uint32, +) func(t *testing.T) { + return func(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + providedActivationEpoch := uint32(1) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.FixRelayedBaseCostEnableEpoch = providedActivationEpoch + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3EnableEpoch = providedActivationEpoch + } + + cs := startChainSimulator(t, alterConfigsFunc) + defer cs.Close() + + initialBalance := big.NewInt(0).Mul(oneEGLD, big.NewInt(10)) + relayer, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + sender, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + owner, err := cs.GenerateAndMintWalletAddress(ownerShard, initialBalance) + require.NoError(t, err) + + // generate one block so the minting has effect + err = cs.GenerateBlocks(1) + require.NoError(t, err) + + _, scAddressBytes := deployAdder(t, cs, owner, 0) + + // send relayed tx with less gas limit + txDataAdd := "add@" + hex.EncodeToString(big.NewInt(1).Bytes()) + gasLimit := gasPerDataByte*len(txDataAdd) + minGasLimit + minGasLimit + relayedTx := generateRelayedV3Transaction(sender.Bytes, 0, scAddressBytes, relayer.Bytes, big.NewInt(0), txDataAdd, uint64(gasLimit)) + + result, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + + require.NotNil(t, result.Logs) + require.Equal(t, 2, len(result.Logs.Events)) + for _, event := range result.Logs.Events { + if event.Identifier == core.SignalErrorOperation { + continue + } + + require.Equal(t, 1, len(event.AdditionalData)) + require.Contains(t, string(event.AdditionalData[0]), "[not enough gas]") + } + + refundValue := getRefundValue(result.SmartContractResults) + require.Zero(t, refundValue.Uint64()) + + // check fee fields, should consume full gas + initiallyPaidFee, fee, gasUsed := computeTxGasAndFeeBasedOnRefund(result, refundValue, false, false) + require.Equal(t, initiallyPaidFee.String(), result.InitiallyPaidFee) + require.Equal(t, fee.String(), result.Fee) + require.Equal(t, result.InitiallyPaidFee, result.Fee) + require.Equal(t, gasUsed, result.GasUsed) + require.Equal(t, relayedTx.GasLimit, result.GasUsed) + + // check relayer balance + relayerBalanceAfter := getBalance(t, cs, relayer) + relayerFee := big.NewInt(0).Sub(initialBalance, relayerBalanceAfter) + require.Equal(t, fee.String(), relayerFee.String()) + + // check sender balance + senderBalanceAfter := getBalance(t, cs, sender) + require.Equal(t, initialBalance.String(), senderBalanceAfter.String()) + } +} + +func testRelayedV3ScCallInvalidMethod( + relayerShard uint32, + ownerShard uint32, +) func(t *testing.T) { + return func(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + providedActivationEpoch := uint32(1) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.FixRelayedBaseCostEnableEpoch = providedActivationEpoch + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3EnableEpoch = providedActivationEpoch + } + + cs := startChainSimulator(t, alterConfigsFunc) + defer cs.Close() + + initialBalance := big.NewInt(0).Mul(oneEGLD, big.NewInt(10)) + relayer, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + sender, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + owner, err := cs.GenerateAndMintWalletAddress(ownerShard, initialBalance) + require.NoError(t, err) + + // generate one block so the minting has effect + err = cs.GenerateBlocks(1) + require.NoError(t, err) + + _, scAddressBytes := deployAdder(t, cs, owner, 0) + + // send relayed tx with invalid value + txDataAdd := "invalid" + gasLimit := uint64(3000000) + relayedTx := generateRelayedV3Transaction(sender.Bytes, 0, scAddressBytes, relayer.Bytes, big.NewInt(0), txDataAdd, uint64(gasLimit)) + + result, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + + require.NotNil(t, result.Logs) + require.Equal(t, 2, len(result.Logs.Events)) + for _, event := range result.Logs.Events { + if event.Identifier == core.SignalErrorOperation { + continue + } + + require.Equal(t, 1, len(event.AdditionalData)) + require.Contains(t, string(event.AdditionalData[0]), "[invalid function (not found)]") + } + + refundValue := getRefundValue(result.SmartContractResults) + require.Zero(t, refundValue.Uint64()) // no refund, tx failed + + // check fee fields, should consume full gas + initiallyPaidFee, fee, _ := computeTxGasAndFeeBasedOnRefund(result, refundValue, false, false) + require.Equal(t, initiallyPaidFee.String(), result.InitiallyPaidFee) + + // check relayer balance + relayerBalanceAfter := getBalance(t, cs, relayer) + relayerFee := big.NewInt(0).Sub(initialBalance, relayerBalanceAfter) + require.Equal(t, fee.String(), relayerFee.String()) + + // check sender balance + senderBalanceAfter := getBalance(t, cs, sender) + require.Equal(t, initialBalance.String(), senderBalanceAfter.String()) + } +} + +func testRelayedV3MetaInteraction() func(t *testing.T) { + return func(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + providedActivationEpoch := uint32(1) + alterConfigsFunc := func(cfg *config.Configs) { + cfg.EpochConfig.EnableEpochs.FixRelayedBaseCostEnableEpoch = providedActivationEpoch + cfg.EpochConfig.EnableEpochs.RelayedTransactionsV3EnableEpoch = providedActivationEpoch + } + + cs := startChainSimulator(t, alterConfigsFunc) + defer cs.Close() + + relayerShard := uint32(0) + + initialBalance := big.NewInt(0).Mul(oneEGLD, big.NewInt(10000)) + relayer, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + sender, err := cs.GenerateAndMintWalletAddress(relayerShard, initialBalance) + require.NoError(t, err) + + // generate one block so the minting has effect + err = cs.GenerateBlocks(1) + require.NoError(t, err) + + // send createNewDelegationContract transaction + txData := "createNewDelegationContract@00@00" + gasLimit := uint64(60000000) + value := big.NewInt(0).Mul(oneEGLD, big.NewInt(1250)) + relayedTx := generateRelayedV3Transaction(sender.Bytes, 0, vm.DelegationManagerSCAddress, relayer.Bytes, value, txData, gasLimit) + + relayerBefore := getBalance(t, cs, relayer) + senderBefore := getBalance(t, cs, sender) + + result, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(relayedTx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + + require.NoError(t, cs.GenerateBlocks(maxNumOfBlocksToGenerateWhenExecutingTx)) + + relayerAfter := getBalance(t, cs, relayer) + senderAfter := getBalance(t, cs, sender) + + // check consumed fees + refund := getRefundValue(result.SmartContractResults) + consumedFee := big.NewInt(0).Sub(relayerBefore, relayerAfter) + + gasForFullPrice := uint64(len(txData)*gasPerDataByte + minGasLimit + minGasLimit) + gasForDeductedPrice := gasLimit - gasForFullPrice + deductedGasPrice := uint64(minGasPrice / deductionFactor) + initialFee := gasForFullPrice*minGasPrice + gasForDeductedPrice*deductedGasPrice + initialFeeInt := big.NewInt(0).SetUint64(initialFee) + expectedConsumedFee := big.NewInt(0).Sub(initialFeeInt, refund) + + gasUsed := gasForFullPrice + gasForDeductedPrice - refund.Uint64()/deductedGasPrice + require.Equal(t, expectedConsumedFee.String(), consumedFee.String()) + require.Equal(t, value.String(), big.NewInt(0).Sub(senderBefore, senderAfter).String(), "sender should have consumed the value only") + require.Equal(t, initialFeeInt.String(), result.InitiallyPaidFee) + require.Equal(t, expectedConsumedFee.String(), result.Fee) + require.Equal(t, gasUsed, result.GasUsed) + } +} + func TestFixRelayedMoveBalanceWithChainSimulator(t *testing.T) { if testing.Short() { t.Skip("this is not a short test") } - expectedFeeScCallBefore := "815294920000000" - expectedFeeScCallAfter := "873704920000000" + expectedFeeScCallBefore := "827294920000000" + expectedFeeScCallAfter := "885704920000000" t.Run("sc call", testFixRelayedMoveBalanceWithChainSimulatorScCall(expectedFeeScCallBefore, expectedFeeScCallAfter)) - expectedFeeMoveBalanceBefore := "797500000000000" // 498 * 1500 + 50000 + 5000 - expectedFeeMoveBalanceAfter := "847000000000000" // 498 * 1500 + 50000 + 50000 + expectedFeeMoveBalanceBefore := "809500000000000" // 506 * 1500 + 50000 + 500 + expectedFeeMoveBalanceAfter := "859000000000000" // 506 * 1500 + 50000 + 50000 t.Run("move balance", testFixRelayedMoveBalanceWithChainSimulatorMoveBalance(expectedFeeMoveBalanceBefore, expectedFeeMoveBalanceAfter)) } @@ -76,7 +774,7 @@ func testFixRelayedMoveBalanceWithChainSimulatorScCall( require.NoError(t, err) ownerNonce := uint64(0) - scAddressBytes := deployAdder(t, cs, owner, ownerNonce) + _, scAddressBytes := deployAdder(t, cs, owner, ownerNonce) // fast-forward until epoch 4 err = cs.GenerateBlocksUntilEpochIsReached(int32(4)) @@ -257,6 +955,37 @@ func TestRelayedTransactionFeeField(t *testing.T) { }) } +func TestRegularMoveBalanceWithRefundReceipt(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + cs := startChainSimulator(t, func(cfg *config.Configs) {}) + defer cs.Close() + + initialBalance := big.NewInt(0).Mul(oneEGLD, big.NewInt(10)) + + sender, err := cs.GenerateAndMintWalletAddress(0, initialBalance) + require.NoError(t, err) + + // generate one block so the minting has effect + err = cs.GenerateBlocks(1) + require.NoError(t, err) + + senderNonce := uint64(0) + + extraGas := uint64(minGasLimit) + gasLimit := minGasLimit + extraGas + tx := generateTransaction(sender.Bytes, senderNonce, sender.Bytes, oneEGLD, "", gasLimit) + + result, err := cs.SendTxAndGenerateBlockTilTxIsExecuted(tx, maxNumOfBlocksToGenerateWhenExecutingTx) + require.NoError(t, err) + + require.NotNil(t, result.Receipt) + expectedGasRefunded := core.SafeMul(extraGas, minGasPrice/deductionFactor) + require.Equal(t, expectedGasRefunded.String(), result.Receipt.Value.String()) +} + func startChainSimulator( t *testing.T, alterConfigsFunction func(cfg *config.Configs), @@ -291,6 +1020,13 @@ func startChainSimulator( return cs } +func generateRelayedV3Transaction(sender []byte, nonce uint64, receiver []byte, relayer []byte, value *big.Int, data string, gasLimit uint64) *transaction.Transaction { + tx := generateTransaction(sender, nonce, receiver, value, data, gasLimit) + tx.RelayerSignature = []byte(mockRelayerTxSignature) + tx.RelayerAddr = relayer + return tx +} + func generateTransaction(sender []byte, nonce uint64, receiver []byte, value *big.Int, data string, gasLimit uint64) *transaction.Transaction { return &transaction.Transaction{ Nonce: nonce, @@ -325,7 +1061,7 @@ func deployAdder( cs testsChainSimulator.ChainSimulator, owner dtos.WalletAddress, ownerNonce uint64, -) []byte { +) (*transaction.ApiTransactionResult, []byte) { pkConv := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter() err := cs.GenerateBlocks(1) @@ -342,5 +1078,99 @@ func deployAdder( scAddress := result.Logs.Events[0].Address scAddressBytes, _ := pkConv.Decode(scAddress) - return scAddressBytes + return result, scAddressBytes +} + +func checkSum( + t *testing.T, + nodeHandler chainSimulatorProcess.NodeHandler, + scAddress []byte, + callerAddress []byte, + expectedSum int, +) { + scQuery := &process.SCQuery{ + ScAddress: scAddress, + FuncName: "getSum", + CallerAddr: callerAddress, + CallValue: big.NewInt(0), + } + result, _, err := nodeHandler.GetFacadeHandler().ExecuteSCQuery(scQuery) + require.Nil(t, err) + require.Equal(t, "ok", result.ReturnCode) + + sum, err := strconv.Atoi(hex.EncodeToString(result.ReturnData[0])) + require.NoError(t, err) + + require.Equal(t, expectedSum, sum) +} + +func getRefundValue(scrs []*transaction.ApiSmartContractResult) *big.Int { + for _, scr := range scrs { + if scr.IsRefund { + return scr.Value + } + } + + return big.NewInt(0) +} + +func computeTxGasAndFeeBasedOnRefund( + result *transaction.ApiTransactionResult, + refund *big.Int, + isMoveBalance bool, + guardedTx bool, +) (*big.Int, *big.Int, uint64) { + deductedGasPrice := uint64(minGasPrice / deductionFactor) + + initialTx := result.Tx + gasForFullPrice := uint64(minGasLimit + gasPerDataByte*len(initialTx.GetData())) + if guardedTx { + gasForFullPrice += extraGasLimitForGuarded + } + + if common.IsRelayedTxV3(initialTx) { + gasForFullPrice += uint64(minGasLimit) // relayer fee + } + gasForDeductedPrice := initialTx.GetGasLimit() - gasForFullPrice + + initialFee := gasForFullPrice*minGasPrice + gasForDeductedPrice*deductedGasPrice + finalFee := initialFee - refund.Uint64() + + gasRefunded := refund.Uint64() / deductedGasPrice + gasConsumed := gasForFullPrice + gasForDeductedPrice - gasRefunded + + if isMoveBalance { + return big.NewInt(0).SetUint64(initialFee), big.NewInt(0).SetUint64(gasForFullPrice * minGasPrice), gasForFullPrice + } + + return big.NewInt(0).SetUint64(initialFee), big.NewInt(0).SetUint64(finalFee), gasConsumed +} + +func checkSCRSucceeded( + t *testing.T, + cs testsChainSimulator.ChainSimulator, + scr *transaction.ApiSmartContractResult, +) { + pkConv := cs.GetNodeHandler(0).GetCoreComponents().AddressPubKeyConverter() + shardC := cs.GetNodeHandler(0).GetShardCoordinator() + addr, err := pkConv.Decode(scr.RcvAddr) + require.NoError(t, err) + + senderShard := shardC.ComputeId(addr) + tx, err := cs.GetNodeHandler(senderShard).GetFacadeHandler().GetTransaction(scr.Hash, true) + require.NoError(t, err) + require.Equal(t, transaction.TxStatusSuccess, tx.Status) + + if tx.ReturnMessage == core.GasRefundForRelayerMessage { + return + } + + require.GreaterOrEqual(t, len(tx.Logs.Events), 1) + for _, event := range tx.Logs.Events { + if event.Identifier == core.WriteLogIdentifier { + continue + } + + require.Equal(t, core.CompletedTxEventIdentifier, event.Identifier) + } } diff --git a/integrationTests/mock/transactionCoordinatorMock.go b/integrationTests/mock/transactionCoordinatorMock.go index c002c52cc0f..29414c117da 100644 --- a/integrationTests/mock/transactionCoordinatorMock.go +++ b/integrationTests/mock/transactionCoordinatorMock.go @@ -12,7 +12,7 @@ import ( // TransactionCoordinatorMock - type TransactionCoordinatorMock struct { - ComputeTransactionTypeCalled func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) + ComputeTransactionTypeCalled func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) RequestMiniBlocksAndTransactionsCalled func(header data.HeaderHandler) RequestBlockTransactionsCalled func(body *block.Body) IsDataPreparedForProcessingCalled func(haveTime func() time.Duration) error @@ -55,9 +55,9 @@ func (tcm *TransactionCoordinatorMock) CreateReceiptsHash() ([]byte, error) { } // ComputeTransactionType - -func (tcm *TransactionCoordinatorMock) ComputeTransactionType(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { +func (tcm *TransactionCoordinatorMock) ComputeTransactionType(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { if tcm.ComputeTransactionTypeCalled == nil { - return process.MoveBalance, process.MoveBalance + return process.MoveBalance, process.MoveBalance, false } return tcm.ComputeTransactionTypeCalled(tx) diff --git a/integrationTests/multiShard/relayedTx/common.go b/integrationTests/multiShard/relayedTx/common.go index a9098c6c668..c440b574d8c 100644 --- a/integrationTests/multiShard/relayedTx/common.go +++ b/integrationTests/multiShard/relayedTx/common.go @@ -2,7 +2,6 @@ package relayedTx import ( "encoding/hex" - "fmt" "math/big" "github.com/multiversx/mx-chain-core-go/core" @@ -12,8 +11,11 @@ import ( "github.com/multiversx/mx-chain-go/integrationTests" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/state" + logger "github.com/multiversx/mx-chain-logger-go" ) +var log = logger.GetOrCreate("relayedtests") + // CreateGeneralSetupForRelayTxTest will create the general setup for relayed transactions func CreateGeneralSetupForRelayTxTest(baseCostFixEnabled bool) ([]*integrationTests.TestProcessorNode, []int, []*integrationTests.TestWalletAccount, *integrationTests.TestWalletAccount) { initialVal := big.NewInt(10000000000) @@ -94,7 +96,7 @@ func CreateAndSendRelayedAndUserTx( _, err := txDispatcherNode.SendTransaction(relayedTx) if err != nil { - fmt.Println(err.Error()) + log.Error("CreateAndSendRelayedAndUserTx.SendTransaction", "error", err) } return relayedTx, userTx @@ -117,12 +119,34 @@ func CreateAndSendRelayedAndUserTxV2( _, err := txDispatcherNode.SendTransaction(relayedTx) if err != nil { - fmt.Println(err.Error()) + log.Error("CreateAndSendRelayedAndUserTxV2.SendTransaction", "error", err) } return relayedTx, userTx } +// CreateAndSendRelayedAndUserTxV3 will create and send a relayed user transaction v3 +func CreateAndSendRelayedAndUserTxV3( + nodes []*integrationTests.TestProcessorNode, + relayer *integrationTests.TestWalletAccount, + player *integrationTests.TestWalletAccount, + rcvAddr []byte, + value *big.Int, + gasLimit uint64, + txData []byte, +) (*transaction.Transaction, *transaction.Transaction) { + txDispatcherNode := getNodeWithinSameShardAsPlayer(nodes, relayer.Address) + + relayedTx := createRelayedTxV3(txDispatcherNode.EconomicsData, relayer, player, rcvAddr, value, gasLimit, txData) + + _, err := txDispatcherNode.SendTransaction(relayedTx) + if err != nil { + log.Error("CreateAndSendRelayedAndUserTxV3.SendTransaction", "error", err) + } + + return relayedTx, relayedTx +} + func createUserTx( player *integrationTests.TestWalletAccount, rcvAddr []byte, @@ -212,6 +236,40 @@ func createRelayedTxV2( return tx } +func createRelayedTxV3( + economicsFee process.FeeHandler, + relayer *integrationTests.TestWalletAccount, + player *integrationTests.TestWalletAccount, + rcvAddr []byte, + value *big.Int, + gasLimit uint64, + txData []byte, +) *transaction.Transaction { + tx := &transaction.Transaction{ + Nonce: player.Nonce, + Value: big.NewInt(0).Set(value), + RcvAddr: rcvAddr, + SndAddr: player.Address, + GasPrice: integrationTests.MinTxGasPrice, + GasLimit: gasLimit + integrationTests.MinTxGasLimit, + Data: txData, + ChainID: integrationTests.ChainID, + Version: integrationTests.MinTransactionVersion, + RelayerAddr: relayer.Address, + } + txBuff, _ := tx.GetDataForSigning(integrationTests.TestAddressPubkeyConverter, integrationTests.TestTxSignMarshalizer, integrationTests.TestTxSignHasher) + tx.Signature, _ = player.SingleSigner.Sign(player.SkTxSign, txBuff) + tx.RelayerSignature, _ = relayer.SingleSigner.Sign(relayer.SkTxSign, txBuff) + + player.Nonce++ + player.Balance.Sub(player.Balance, value) + + txFee := economicsFee.ComputeTxFee(tx) + relayer.Balance.Sub(relayer.Balance, txFee) + + return tx +} + func createAndSendSimpleTransaction( nodes []*integrationTests.TestProcessorNode, player *integrationTests.TestWalletAccount, @@ -225,7 +283,7 @@ func createAndSendSimpleTransaction( userTx := createUserTx(player, rcvAddr, value, gasLimit, txData) _, err := txDispatcherNode.SendTransaction(userTx) if err != nil { - fmt.Println(err.Error()) + log.Error("createAndSendSimpleTransaction.SendTransaction", "error", err) } } diff --git a/integrationTests/multiShard/relayedTx/relayedTx_test.go b/integrationTests/multiShard/relayedTx/relayedTx_test.go index 70df89b466c..c815a5b5eac 100644 --- a/integrationTests/multiShard/relayedTx/relayedTx_test.go +++ b/integrationTests/multiShard/relayedTx/relayedTx_test.go @@ -33,20 +33,24 @@ type createAndSendRelayedAndUserTxFuncType = func( func TestRelayedTransactionInMultiShardEnvironmentWithNormalTx(t *testing.T) { t.Run("relayed v1", testRelayedTransactionInMultiShardEnvironmentWithNormalTx(CreateAndSendRelayedAndUserTx, false)) + t.Run("relayed v3", testRelayedTransactionInMultiShardEnvironmentWithNormalTx(CreateAndSendRelayedAndUserTxV3, true)) } func TestRelayedTransactionInMultiShardEnvironmentWithSmartContractTX(t *testing.T) { t.Run("relayed v1", testRelayedTransactionInMultiShardEnvironmentWithSmartContractTX(CreateAndSendRelayedAndUserTx, false)) t.Run("relayed v2", testRelayedTransactionInMultiShardEnvironmentWithSmartContractTX(CreateAndSendRelayedAndUserTxV2, false)) + t.Run("relayed v3", testRelayedTransactionInMultiShardEnvironmentWithSmartContractTX(CreateAndSendRelayedAndUserTxV3, true)) } func TestRelayedTransactionInMultiShardEnvironmentWithESDTTX(t *testing.T) { t.Run("relayed v1", testRelayedTransactionInMultiShardEnvironmentWithESDTTX(CreateAndSendRelayedAndUserTx, false)) t.Run("relayed v2", testRelayedTransactionInMultiShardEnvironmentWithESDTTX(CreateAndSendRelayedAndUserTxV2, false)) + t.Run("relayed v3", testRelayedTransactionInMultiShardEnvironmentWithESDTTX(CreateAndSendRelayedAndUserTxV3, true)) } func TestRelayedTransactionInMultiShardEnvironmentWithAttestationContract(t *testing.T) { t.Run("relayed v1", testRelayedTransactionInMultiShardEnvironmentWithAttestationContract(CreateAndSendRelayedAndUserTx, false)) + t.Run("relayed v3", testRelayedTransactionInMultiShardEnvironmentWithAttestationContract(CreateAndSendRelayedAndUserTxV3, true)) } func testRelayedTransactionInMultiShardEnvironmentWithNormalTx( @@ -138,6 +142,8 @@ func testRelayedTransactionInMultiShardEnvironmentWithSmartContractTX( receiverAddress1 := []byte("12345678901234567890123456789012") receiverAddress2 := []byte("12345678901234567890123456789011") + integrationTests.MintAllPlayers(nodes, players, big.NewInt(1)) + ownerNode := nodes[0] initialSupply := "00" + hex.EncodeToString(big.NewInt(100000000000).Bytes()) scCode := wasm.GetSCCode("../../vm/wasm/testdata/erc20-c-03/wrc20_wasm.wasm") diff --git a/integrationTests/testProcessorNode.go b/integrationTests/testProcessorNode.go index 80ba584c6b4..14c7a6a1ba2 100644 --- a/integrationTests/testProcessorNode.go +++ b/integrationTests/testProcessorNode.go @@ -2593,22 +2593,29 @@ func (tpn *TestProcessorNode) SendTransaction(tx *dataTransaction.Transaction) ( if len(tx.GuardianAddr) == TestAddressPubkeyConverter.Len() { guardianAddress = TestAddressPubkeyConverter.SilentEncode(tx.GuardianAddr, log) } + + relayerAddress := "" + if len(tx.RelayerAddr) == TestAddressPubkeyConverter.Len() { + relayerAddress = TestAddressPubkeyConverter.SilentEncode(tx.RelayerAddr, log) + } createTxArgs := &external.ArgsCreateTransaction{ - Nonce: tx.Nonce, - Value: tx.Value.String(), - Receiver: encodedRcvAddr, - ReceiverUsername: nil, - Sender: encodedSndAddr, - SenderUsername: nil, - GasPrice: tx.GasPrice, - GasLimit: tx.GasLimit, - DataField: tx.Data, - SignatureHex: hex.EncodeToString(tx.Signature), - ChainID: string(tx.ChainID), - Version: tx.Version, - Options: tx.Options, - Guardian: guardianAddress, - GuardianSigHex: hex.EncodeToString(tx.GuardianSignature), + Nonce: tx.Nonce, + Value: tx.Value.String(), + Receiver: encodedRcvAddr, + ReceiverUsername: nil, + Sender: encodedSndAddr, + SenderUsername: nil, + GasPrice: tx.GasPrice, + GasLimit: tx.GasLimit, + DataField: tx.Data, + SignatureHex: hex.EncodeToString(tx.Signature), + ChainID: string(tx.ChainID), + Version: tx.Version, + Options: tx.Options, + Guardian: guardianAddress, + GuardianSigHex: hex.EncodeToString(tx.GuardianSignature), + Relayer: relayerAddress, + RelayerSignatureHex: hex.EncodeToString(tx.RelayerSignature), } tx, txHash, err := tpn.Node.CreateTransaction(createTxArgs) if err != nil { diff --git a/node/chainSimulator/chainSimulator.go b/node/chainSimulator/chainSimulator.go index 742d040c8c8..0d3d4d25e6a 100644 --- a/node/chainSimulator/chainSimulator.go +++ b/node/chainSimulator/chainSimulator.go @@ -298,15 +298,18 @@ func (s *simulator) incrementRoundOnAllValidators() { // ForceChangeOfEpoch will force the change of current epoch // This method will call the epoch change trigger and generate block till a new epoch is reached func (s *simulator) ForceChangeOfEpoch() error { + s.mutex.Lock() log.Info("force change of epoch") for shardID, node := range s.nodes { err := node.ForceChangeOfEpoch() if err != nil { + s.mutex.Unlock() return fmt.Errorf("force change of epoch shardID-%d: error-%w", shardID, err) } } epoch := s.nodes[core.MetachainShardId].GetProcessComponents().EpochStartTrigger().Epoch() + s.mutex.Unlock() return s.GenerateBlocksUntilEpochIsReached(int32(epoch + 1)) } diff --git a/node/chainSimulator/components/nodeFacade.go b/node/chainSimulator/components/nodeFacade.go index d62814fdf03..934807c0659 100644 --- a/node/chainSimulator/components/nodeFacade.go +++ b/node/chainSimulator/components/nodeFacade.go @@ -177,6 +177,7 @@ func (node *testOnlyProcessingNode) createMetrics(configs config.Configs) error metrics.SaveUint64Metric(node.StatusCoreComponents.AppStatusHandler(), common.MetricMinGasPrice, node.CoreComponentsHolder.EconomicsData().MinGasPrice()) metrics.SaveUint64Metric(node.StatusCoreComponents.AppStatusHandler(), common.MetricMinGasLimit, node.CoreComponentsHolder.EconomicsData().MinGasLimit()) metrics.SaveUint64Metric(node.StatusCoreComponents.AppStatusHandler(), common.MetricExtraGasLimitGuardedTx, node.CoreComponentsHolder.EconomicsData().ExtraGasLimitGuardedTx()) + metrics.SaveUint64Metric(node.StatusCoreComponents.AppStatusHandler(), common.MetricExtraGasLimitRelayedTx, node.CoreComponentsHolder.EconomicsData().MinGasLimit()) metrics.SaveStringMetric(node.StatusCoreComponents.AppStatusHandler(), common.MetricRewardsTopUpGradientPoint, node.CoreComponentsHolder.EconomicsData().RewardsTopUpGradientPoint().String()) metrics.SaveStringMetric(node.StatusCoreComponents.AppStatusHandler(), common.MetricTopUpFactor, fmt.Sprintf("%g", node.CoreComponentsHolder.EconomicsData().RewardsTopUpFactor())) metrics.SaveStringMetric(node.StatusCoreComponents.AppStatusHandler(), common.MetricGasPriceModifier, fmt.Sprintf("%g", node.CoreComponentsHolder.EconomicsData().GasPriceModifier())) diff --git a/node/chainSimulator/components/syncedMessenger.go b/node/chainSimulator/components/syncedMessenger.go index cc437d02038..09786c45842 100644 --- a/node/chainSimulator/components/syncedMessenger.go +++ b/node/chainSimulator/components/syncedMessenger.go @@ -80,13 +80,21 @@ func (messenger *syncedMessenger) receive(fromConnectedPeer core.PeerID, message handlers := messenger.topics[message.Topic()] messenger.mutOperation.RUnlock() + wg := &sync.WaitGroup{} + wg.Add(len(handlers)) for _, handler := range handlers { - err := handler.ProcessReceivedMessage(message, fromConnectedPeer, messenger) - if err != nil { - log.Trace("received message syncedMessenger", - "error", err, "topic", message.Topic(), "from connected peer", fromConnectedPeer.Pretty()) - } + // this is needed to process all received messages on multiple go routines + go func(proc p2p.MessageProcessor, p2pMessage p2p.MessageP2P, peer core.PeerID, localWG *sync.WaitGroup) { + err := proc.ProcessReceivedMessage(p2pMessage, peer, messenger) + if err != nil { + log.Trace("received message syncedMessenger", "error", err, "topic", p2pMessage.Topic(), "from connected peer", peer.Pretty()) + } + + localWG.Done() + }(handler, message, fromConnectedPeer, wg) } + + wg.Wait() } // ProcessReceivedMessage does nothing and returns nil diff --git a/node/external/blockAPI/baseBlock.go b/node/external/blockAPI/baseBlock.go index 63bfe673f37..df637f338d6 100644 --- a/node/external/blockAPI/baseBlock.go +++ b/node/external/blockAPI/baseBlock.go @@ -125,6 +125,18 @@ func (bap *baseAPIBlockProcessor) getAndAttachTxsToMb( firstProcessed := mbHeader.GetIndexOfFirstTxProcessed() lastProcessed := mbHeader.GetIndexOfLastTxProcessed() + + // When options.ForHyperblock is true, there are two scenarios: + // 1 - If not all transactions were executed, no transactions will be returned. + // 2 - If all transactions were executed, all transactions starting from index 0 will be returned. + if options.ForHyperblock { + allTxsWereExecuted := lastProcessed == int32(len(miniBlock.TxHashes)-1) + if !allTxsWereExecuted { + return nil + } + firstProcessed = 0 + } + return bap.getAndAttachTxsToMbByEpoch(miniblockHash, miniBlock, header, apiMiniblock, firstProcessed, lastProcessed, options) } diff --git a/node/external/dtos.go b/node/external/dtos.go index f884d8d32c9..b1789054f5e 100644 --- a/node/external/dtos.go +++ b/node/external/dtos.go @@ -2,19 +2,21 @@ package external // ArgsCreateTransaction defines arguments for creating a transaction type ArgsCreateTransaction struct { - Nonce uint64 - Value string - Receiver string - ReceiverUsername []byte - Sender string - SenderUsername []byte - GasPrice uint64 - GasLimit uint64 - DataField []byte - SignatureHex string - ChainID string - Version uint32 - Options uint32 - Guardian string - GuardianSigHex string + Nonce uint64 + Value string + Receiver string + ReceiverUsername []byte + Sender string + SenderUsername []byte + GasPrice uint64 + GasLimit uint64 + DataField []byte + SignatureHex string + ChainID string + Version uint32 + Options uint32 + Guardian string + GuardianSigHex string + Relayer string + RelayerSignatureHex string } diff --git a/node/external/transactionAPI/apiTransactionProcessor.go b/node/external/transactionAPI/apiTransactionProcessor.go index a12d5efb974..f15a096ea5b 100644 --- a/node/external/transactionAPI/apiTransactionProcessor.go +++ b/node/external/transactionAPI/apiTransactionProcessor.go @@ -183,7 +183,7 @@ func (atp *apiTransactionProcessor) PopulateComputedFields(tx *transaction.ApiTr } func (atp *apiTransactionProcessor) populateComputedFieldsProcessingType(tx *transaction.ApiTransactionResult) { - typeOnSource, typeOnDestination := atp.txTypeHandler.ComputeTransactionType(tx.Tx) + typeOnSource, typeOnDestination, _ := atp.txTypeHandler.ComputeTransactionType(tx.Tx) tx.ProcessingTypeOnSource = typeOnSource.String() tx.ProcessingTypeOnDestination = typeOnDestination.String() } @@ -199,9 +199,9 @@ func (atp *apiTransactionProcessor) populateComputedFieldInitiallyPaidFee(tx *tr tx.InitiallyPaidFee = fee.String() isFeeFixActive := atp.enableEpochsHandler.IsFlagEnabledInEpoch(common.FixRelayedBaseCostFlag, tx.Epoch) - isRelayedAfterFix := tx.IsRelayed && isFeeFixActive + _, fee, isRelayedV1V2 := atp.gasUsedAndFeeProcessor.getFeeOfRelayedV1V2(tx) + isRelayedAfterFix := tx.IsRelayed && isFeeFixActive && isRelayedV1V2 if isRelayedAfterFix { - _, fee, _ = atp.gasUsedAndFeeProcessor.getFeeOfRelayed(tx) tx.InitiallyPaidFee = fee.String() } } @@ -394,12 +394,12 @@ func (atp *apiTransactionProcessor) getFieldGettersForTx(wrappedTx *txcache.Wrap rcvUsernameField: wrappedTx.Tx.GetRcvUserName(), dataField: wrappedTx.Tx.GetData(), valueField: getTxValue(wrappedTx), - senderShardID: wrappedTx.SenderShardID, - receiverShardID: wrappedTx.ReceiverShardID, + senderShardID: atp.shardCoordinator.ComputeId(wrappedTx.Tx.GetSndAddr()), + receiverShardID: atp.shardCoordinator.ComputeId(wrappedTx.Tx.GetRcvAddr()), } guardedTx, isGuardedTx := wrappedTx.Tx.(data.GuardedTransactionHandler) - if isGuardedTx { + if isGuardedTx && len(guardedTx.GetGuardianAddr()) > 0 { fieldGetters[signatureField] = hex.EncodeToString(guardedTx.GetSignature()) if len(guardedTx.GetGuardianAddr()) > 0 { @@ -408,6 +408,13 @@ func (atp *apiTransactionProcessor) getFieldGettersForTx(wrappedTx *txcache.Wrap } } + relayedTx, ok := wrappedTx.Tx.(data.RelayedTransactionHandler) + if ok && len(relayedTx.GetRelayerAddr()) > 0 { + fieldGetters[signatureField] = hex.EncodeToString(relayedTx.GetSignature()) + fieldGetters[relayerField] = atp.addressPubKeyConverter.SilentEncode(relayedTx.GetRelayerAddr(), log) + fieldGetters[relayerSignatureField] = hex.EncodeToString(relayedTx.GetRelayerSignature()) + } + return fieldGetters } diff --git a/node/external/transactionAPI/apiTransactionProcessor_test.go b/node/external/transactionAPI/apiTransactionProcessor_test.go index 40357484880..d7bd038bd7f 100644 --- a/node/external/transactionAPI/apiTransactionProcessor_test.go +++ b/node/external/transactionAPI/apiTransactionProcessor_test.go @@ -936,6 +936,9 @@ func TestApiTransactionProcessor_GetTransactionsPoolForSender(t *testing.T) { NumberOfShardsCalled: func() uint32 { return 1 }, + ComputeIdCalled: func(address []byte) uint32 { + return 1 // force to return different from 0 + }, } atp, err := NewAPITransactionProcessor(args) require.NoError(t, err) @@ -948,7 +951,17 @@ func TestApiTransactionProcessor_GetTransactionsPoolForSender(t *testing.T) { for i, tx := range res.Transactions { require.Equal(t, expectedHashes[i], tx.TxFields[hashField]) require.Equal(t, expectedValues[i], tx.TxFields[valueField]) - require.Equal(t, sender, tx.TxFields["sender"]) + require.Equal(t, sender, tx.TxFields[senderField]) + require.Equal(t, uint32(1), tx.TxFields[senderShardID]) + require.Equal(t, uint32(1), tx.TxFields[senderShardID]) + } + + res, err = atp.GetTransactionsPoolForSender(sender, "sender,value") // no hash, should be by default + require.NoError(t, err) + for i, tx := range res.Transactions { + require.Equal(t, expectedHashes[i], tx.TxFields[hashField]) + require.Equal(t, expectedValues[i], tx.TxFields[valueField]) + require.Equal(t, sender, tx.TxFields[senderField]) } // if no tx is found in pool for a sender, it isn't an error, but return empty slice @@ -1307,8 +1320,8 @@ func TestApiTransactionProcessor_GetTransactionPopulatesComputedFields(t *testin }) t.Run("ProcessingType", func(t *testing.T) { - txTypeHandler.ComputeTransactionTypeCalled = func(data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.MoveBalance, process.SCDeployment + txTypeHandler.ComputeTransactionTypeCalled = func(data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.SCDeployment, false } dataPool.Transactions().AddData([]byte{0, 2}, &transaction.Transaction{Nonce: 7, SndAddr: []byte("alice"), RcvAddr: []byte("bob")}, 42, "1") @@ -1319,10 +1332,23 @@ func TestApiTransactionProcessor_GetTransactionPopulatesComputedFields(t *testin require.Equal(t, process.SCDeployment.String(), tx.ProcessingTypeOnDestination) }) + t.Run("ProcessingType (with relayed v3)", func(t *testing.T) { + txTypeHandler.ComputeTransactionTypeCalled = func(data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.SCDeployment, true + } + + dataPool.Transactions().AddData([]byte{0, 3}, &transaction.Transaction{Nonce: 7, SndAddr: []byte("alice"), RcvAddr: []byte("bob")}, 42, "1") + tx, err := processor.GetTransaction("0003", true) + + require.Nil(t, err) + require.Equal(t, process.MoveBalance.String(), tx.ProcessingTypeOnSource) + require.Equal(t, process.SCDeployment.String(), tx.ProcessingTypeOnDestination) + }) + t.Run("IsRefund (false)", func(t *testing.T) { scr := &smartContractResult.SmartContractResult{GasLimit: 0, Data: []byte("@ok"), Value: big.NewInt(0)} - dataPool.UnsignedTransactions().AddData([]byte{0, 3}, scr, 42, "foo") - tx, err := processor.GetTransaction("0003", true) + dataPool.UnsignedTransactions().AddData([]byte{0, 4}, scr, 42, "foo") + tx, err := processor.GetTransaction("0004", true) require.Nil(t, err) require.Equal(t, false, tx.IsRefund) @@ -1330,8 +1356,8 @@ func TestApiTransactionProcessor_GetTransactionPopulatesComputedFields(t *testin t.Run("IsRefund (true)", func(t *testing.T) { scr := &smartContractResult.SmartContractResult{GasLimit: 0, Data: []byte("@6f6b"), Value: big.NewInt(500)} - dataPool.UnsignedTransactions().AddData([]byte{0, 4}, scr, 42, "foo") - tx, err := processor.GetTransaction("0004", true) + dataPool.UnsignedTransactions().AddData([]byte{0, 5}, scr, 42, "foo") + tx, err := processor.GetTransaction("0005", true) require.Nil(t, err) require.Equal(t, true, tx.IsRefund) @@ -1351,8 +1377,8 @@ func TestApiTransactionProcessor_PopulateComputedFields(t *testing.T) { require.Nil(t, err) require.NotNil(t, processor) - txTypeHandler.ComputeTransactionTypeCalled = func(data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.MoveBalance, process.SCDeployment + txTypeHandler.ComputeTransactionTypeCalled = func(data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.SCDeployment, false } feeComputer.ComputeTransactionFeeCalled = func(tx *transaction.ApiTransactionResult) *big.Int { diff --git a/node/external/transactionAPI/fieldsHandler.go b/node/external/transactionAPI/fieldsHandler.go index 4f837968cb7..f631de38a82 100644 --- a/node/external/transactionAPI/fieldsHandler.go +++ b/node/external/transactionAPI/fieldsHandler.go @@ -19,6 +19,8 @@ const ( guardianSignatureField = "guardiansignature" senderShardID = "sendershard" receiverShardID = "receivershard" + relayerField = "relayer" + relayerSignatureField = "relayersignature" wildCard = "*" separator = "," @@ -38,8 +40,11 @@ func newFieldsHandler(parameters string) fieldsHandler { } parameters = strings.ToLower(parameters) + fieldsMap := sliceToMap(strings.Split(parameters, separator)) + fieldsMap[hashField] = struct{}{} // hashField should always be returned + return fieldsHandler{ - fieldsMap: sliceToMap(strings.Split(parameters, separator)), + fieldsMap: fieldsMap, } } diff --git a/node/external/transactionAPI/fieldsHandler_test.go b/node/external/transactionAPI/fieldsHandler_test.go index fab3b3a41d9..75b3ae6f81a 100644 --- a/node/external/transactionAPI/fieldsHandler_test.go +++ b/node/external/transactionAPI/fieldsHandler_test.go @@ -20,9 +20,11 @@ func Test_newFieldsHandler(t *testing.T) { for _, field := range splitFields { require.True(t, fh.IsFieldSet(field), fmt.Sprintf("field %s is not set", field)) } + require.True(t, fh.IsFieldSet(hashField), "hashField should have been returned by default") fh = newFieldsHandler("*") for _, field := range splitFields { require.True(t, fh.IsFieldSet(field)) } + require.True(t, fh.IsFieldSet(hashField), "hashField should have been returned by default") } diff --git a/node/external/transactionAPI/gasUsedAndFeeProcessor.go b/node/external/transactionAPI/gasUsedAndFeeProcessor.go index c4cd9578394..7bbb197c69f 100644 --- a/node/external/transactionAPI/gasUsedAndFeeProcessor.go +++ b/node/external/transactionAPI/gasUsedAndFeeProcessor.go @@ -50,33 +50,49 @@ func (gfp *gasUsedAndFeeProcessor) computeAndAttachGasUsedAndFee(tx *transaction tx.Fee = tx.InitiallyPaidFee } - userTx, initialTotalFee, isRelayed := gfp.getFeeOfRelayed(tx) - isRelayedAfterFix := isRelayed && isFeeFixActive + userTx, initialTotalFee, isRelayedV1V2 := gfp.getFeeOfRelayedV1V2(tx) + isRelayedAfterFix := isRelayedV1V2 && isFeeFixActive if isRelayedAfterFix { tx.InitiallyPaidFee = initialTotalFee.String() tx.Fee = initialTotalFee.String() tx.GasUsed = big.NewInt(0).Div(initialTotalFee, big.NewInt(0).SetUint64(tx.GasPrice)).Uint64() } + isRelayedV3 := common.IsValidRelayedTxV3(tx.Tx) hasRefundForSender := false + totalRefunds := big.NewInt(0) for _, scr := range tx.SmartContractResults { - if !scr.IsRefund || scr.RcvAddr != tx.Sender { + if !scr.IsRefund { + continue + } + if !isRelayedV3 && scr.RcvAddr != tx.Sender { + continue + } + if isRelayedV3 && scr.RcvAddr != tx.RelayerAddress { continue } - gfp.setGasUsedAndFeeBaseOnRefundValue(tx, userTx, scr.Value) hasRefundForSender = true - break + totalRefunds.Add(totalRefunds, scr.Value) + } + + if totalRefunds.Cmp(big.NewInt(0)) > 0 { + gfp.setGasUsedAndFeeBaseOnRefundValue(tx, userTx, totalRefunds) } gfp.prepareTxWithResultsBasedOnLogs(tx, userTx, hasRefundForSender) } -func (gfp *gasUsedAndFeeProcessor) getFeeOfRelayed(tx *transaction.ApiTransactionResult) (*transaction.ApiTransactionResult, *big.Int, bool) { +func (gfp *gasUsedAndFeeProcessor) getFeeOfRelayedV1V2(tx *transaction.ApiTransactionResult) (*transaction.ApiTransactionResult, *big.Int, bool) { if !tx.IsRelayed { return nil, nil, false } + isRelayedV3 := common.IsValidRelayedTxV3(tx.Tx) + if isRelayedV3 { + return nil, nil, false + } + if len(tx.Data) == 0 { return nil, nil, false } @@ -173,7 +189,10 @@ func (gfp *gasUsedAndFeeProcessor) setGasUsedAndFeeBaseOnRefundValue( userTx *transaction.ApiTransactionResult, refund *big.Int, ) { - if !check.IfNilReflect(userTx) && gfp.enableEpochsHandler.IsFlagEnabledInEpoch(common.FixRelayedBaseCostFlag, tx.Epoch) { + isRelayedV3 := len(tx.RelayerAddress) == len(tx.Sender) && + len(tx.RelayerSignature) == len(tx.Signature) + isValidUserTxAfterBaseCostActivation := !check.IfNilReflect(userTx) && gfp.enableEpochsHandler.IsFlagEnabledInEpoch(common.FixRelayedBaseCostFlag, tx.Epoch) + if isValidUserTxAfterBaseCostActivation && !isRelayedV3 { gasUsed, fee := gfp.feeComputer.ComputeGasUsedAndFeeBasedOnRefundValue(userTx, refund) gasUsedRelayedTx := gfp.feeComputer.ComputeGasLimit(tx) feeRelayedTx := gfp.feeComputer.ComputeTxFeeBasedOnGasUsed(tx, gasUsedRelayedTx) diff --git a/node/external/transactionAPI/gasUsedAndFeeProcessor_test.go b/node/external/transactionAPI/gasUsedAndFeeProcessor_test.go index 8c5c80826c0..b81ad1e03b9 100644 --- a/node/external/transactionAPI/gasUsedAndFeeProcessor_test.go +++ b/node/external/transactionAPI/gasUsedAndFeeProcessor_test.go @@ -1,6 +1,7 @@ package transactionAPI import ( + "encoding/hex" "math/big" "testing" @@ -257,13 +258,14 @@ func TestNFTTransferWithScCall(t *testing.T) { func TestComputeAndAttachGasUsedAndFeeTransactionWithMultipleScrWithRefund(t *testing.T) { t.Parallel() - feeComp, _ := fee.NewFeeComputer(createEconomicsData(&enableEpochsHandlerMock.EnableEpochsHandlerStub{ + eeh := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { return flag == common.GasPriceModifierFlag || flag == common.PenalizedTooMuchGasFlag || flag == common.FixRelayedBaseCostFlag }, - })) + } + feeComp, _ := fee.NewFeeComputer(createEconomicsData(eeh)) computer := fee.NewTestFeeComputer(feeComp) gasUsedAndFeeProc := newGasUsedAndFeeProcessor( @@ -271,7 +273,7 @@ func TestComputeAndAttachGasUsedAndFeeTransactionWithMultipleScrWithRefund(t *te pubKeyConverter, &testscommon.ArgumentParserMock{}, &testscommon.MarshallerStub{}, - enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), + eeh, ) txWithSRefundSCR := &transaction.ApiTransactionResult{} @@ -394,3 +396,56 @@ func TestComputeAndAttachGasUsedAndFeeRelayedV1CreateNewDelegationContractWithRe require.Equal(t, "1878500000000000", txWithSRefundSCR.Fee) require.Equal(t, "2177505000000000", txWithSRefundSCR.InitiallyPaidFee) } + +func TestComputeAndAttachGasUsedAndFeeRelayedV3WithMultipleRefunds(t *testing.T) { + t.Parallel() + + eeh := &enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return flag == common.GasPriceModifierFlag || + flag == common.PenalizedTooMuchGasFlag || + flag == common.FixRelayedBaseCostFlag || + flag == common.RelayedTransactionsV3Flag + }, + } + feeComp, _ := fee.NewFeeComputer(createEconomicsData(eeh)) + computer := fee.NewTestFeeComputer(feeComp) + + gasUsedAndFeeProc := newGasUsedAndFeeProcessor( + computer, + pubKeyConverter, + &testscommon.ArgumentParserMock{}, + &testscommon.MarshallerStub{}, + eeh, + ) + + txWithRefunds := &transaction.ApiTransactionResult{} + err := core.LoadJsonFile(txWithRefunds, "testData/relayedV3WithMultipleRefunds.json") + require.NoError(t, err) + + txWithRefunds.Fee = "" + txWithRefunds.GasUsed = 0 + + snd, _ := pubKeyConverter.Decode(txWithRefunds.Sender) + rcv, _ := pubKeyConverter.Decode(txWithRefunds.Receiver) + rel, _ := pubKeyConverter.Decode(txWithRefunds.RelayerAddress) + val, _ := big.NewInt(0).SetString(txWithRefunds.Value, 10) + sig, _ := hex.DecodeString(txWithRefunds.Signature) + relayerSig, _ := hex.DecodeString(txWithRefunds.RelayerSignature) + txWithRefunds.Tx = &transaction.Transaction{ + Nonce: txWithRefunds.Nonce, + Value: val, + RcvAddr: rcv, + SndAddr: snd, + RelayerAddr: rel, + GasPrice: txWithRefunds.GasPrice, + GasLimit: txWithRefunds.GasLimit, + Data: txWithRefunds.Data, + Signature: sig, + RelayerSignature: relayerSig, + } + + gasUsedAndFeeProc.computeAndAttachGasUsedAndFee(txWithRefunds) + require.Equal(t, uint64(4220447), txWithRefunds.GasUsed) + require.Equal(t, "289704470000000", txWithRefunds.Fee) +} diff --git a/node/external/transactionAPI/testData/relayedV3WithMultipleRefunds.json b/node/external/transactionAPI/testData/relayedV3WithMultipleRefunds.json new file mode 100644 index 00000000000..26d3005c9d8 --- /dev/null +++ b/node/external/transactionAPI/testData/relayedV3WithMultipleRefunds.json @@ -0,0 +1,159 @@ +{ + "type": "normal", + "processingTypeOnSource": "SCInvoking", + "processingTypeOnDestination": "SCInvoking", + "value": "0", + "receiver": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3", + "sender": "erd1at9keal0jfhamc67ulq4csmchh33eek87yf5hhzcvlw8e5qlx8zq5hjwjl", + "gasPrice": 1000000000, + "gasLimit": 15000000, + "gasUsed": 14536537, + "data": "Zm9yd2FyZEAwMUAwMDAwMDAwMDAwMDAwMDAwMDUwMGMxMzVlMjc2NmM3MDcyMTA2ZjIzYjAzNWIzODUxZDYzZDdmNjIxYzY5NmRhQDAwQDYxNjQ2NDQwMzAzMUAwMDdmZmZmZg==", + "signature": "645d88221a50bbf5173a9a46a70308f0c272d9b4701fe935ae2565147a32b484c4ea695bcaa102a299877f7e7699bb7990c491a601ae636851f5485dd59fe10b", + "smartContractResults": [ + { + "hash": "16b05474872d51840ca9270d8790fc65d50f5d076bb491388344f5095f6f0df0", + "nonce": 24, + "value": 4634630000000, + "receiver": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3", + "prevTxHash": "8fe5133b33d315392006d11dbeb2ea9c0d5f1eb0856aaf3044e9f55118656843", + "originalTxHash": "8fe5133b33d315392006d11dbeb2ea9c0d5f1eb0856aaf3044e9f55118656843", + "gasLimit": 0, + "gasPrice": 1000000000, + "callType": 0, + "returnMessage": "gas refund for relayer", + "originalSender": "erd1at9keal0jfhamc67ulq4csmchh33eek87yf5hhzcvlw8e5qlx8zq5hjwjl", + "logs": { + "address": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "events": [ + { + "address": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "identifier": "completedTxEvent", + "topics": [ + "j+UTOzPTFTkgBtEdvrLqnA1fHrCFaq8wROn1URhlaEM=" + ], + "data": null, + "additionalData": null + } + ] + }, + "operation": "transfer", + "isRefund": true + }, + { + "hash": "9090261203cf20e3b51e4c921ce27fa595fcca3f37c83543c7a4a57733139f4d", + "nonce": 1, + "value": 103160900000000, + "receiver": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "sender": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3", + "prevTxHash": "a74d26756c58c522637b3c73b299828901533c93d5ccd75d6ec6a3410755d3e4", + "originalTxHash": "8fe5133b33d315392006d11dbeb2ea9c0d5f1eb0856aaf3044e9f55118656843", + "gasLimit": 0, + "gasPrice": 1000000000, + "callType": 0, + "returnMessage": "gas refund for relayer", + "originalSender": "erd1at9keal0jfhamc67ulq4csmchh33eek87yf5hhzcvlw8e5qlx8zq5hjwjl", + "logs": { + "address": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "events": [ + { + "address": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "identifier": "completedTxEvent", + "topics": [ + "p00mdWxYxSJjezxzspmCiQFTPJPVzNddbsajQQdV0+Q=" + ], + "data": null, + "additionalData": null + } + ] + }, + "operation": "transfer", + "isRefund": true + }, + { + "hash": "5fb1c0d5d04b80331839de417523dff45a1be9489d4d84f9c5535314e0542525", + "nonce": 0, + "value": 0, + "receiver": "erd1qqqqqqqqqqqqqpgqcy67yanvwpepqmerkq6m8pgav0tlvgwxjmdq4hukxw", + "sender": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3", + "relayerAddress": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "relayedValue": 0, + "data": "add@01@21fdaf01f965469c1bebf13c3c49a18f0dde9d8e380cec78cea065246fabb2d9@8fe5133b33d315392006d11dbeb2ea9c0d5f1eb0856aaf3044e9f55118656843@4185d4", + "prevTxHash": "8fe5133b33d315392006d11dbeb2ea9c0d5f1eb0856aaf3044e9f55118656843", + "originalTxHash": "8fe5133b33d315392006d11dbeb2ea9c0d5f1eb0856aaf3044e9f55118656843", + "gasLimit": 12682707, + "gasPrice": 1000000000, + "callType": 1, + "originalSender": "erd1at9keal0jfhamc67ulq4csmchh33eek87yf5hhzcvlw8e5qlx8zq5hjwjl", + "operation": "transfer", + "function": "add" + }, + { + "hash": "a74d26756c58c522637b3c73b299828901533c93d5ccd75d6ec6a3410755d3e4", + "nonce": 0, + "value": 0, + "receiver": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3", + "sender": "erd1qqqqqqqqqqqqqpgqcy67yanvwpepqmerkq6m8pgav0tlvgwxjmdq4hukxw", + "relayerAddress": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "relayedValue": 0, + "data": "@00@95a836710c0857f995e670bf3aa944263473534c8c5f4b7959c938045f62023b@21fdaf01f965469c1bebf13c3c49a18f0dde9d8e380cec78cea065246fabb2d9@8fe5133b33d315392006d11dbeb2ea9c0d5f1eb0856aaf3044e9f55118656843@00", + "prevTxHash": "5fb1c0d5d04b80331839de417523dff45a1be9489d4d84f9c5535314e0542525", + "originalTxHash": "8fe5133b33d315392006d11dbeb2ea9c0d5f1eb0856aaf3044e9f55118656843", + "gasLimit": 11512215, + "gasPrice": 1000000000, + "callType": 2, + "originalSender": "erd1at9keal0jfhamc67ulq4csmchh33eek87yf5hhzcvlw8e5qlx8zq5hjwjl", + "logs": { + "address": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3", + "events": [ + { + "address": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3", + "identifier": "writeLog", + "topics": [ + "AAAAAAAAAAAFAO2OJalO+oN6rg5ZMRLPuwG0SHVQaeE=" + ], + "data": "QDZmNmJAMDBjYTc3YmFjNEAwNjBjY2U1NQ==", + "additionalData": [ + "QDZmNmJAMDBjYTc3YmFjNEAwNjBjY2U1NQ==" + ] + } + ] + }, + "operation": "transfer" + } + ], + "logs": { + "address": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3", + "events": [ + { + "address": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3", + "identifier": "transferValueOnly", + "topics": [ + "", + "AAAAAAAAAAAFAME14nZscHIQbyOwNbOFHWPX9iHGlto=" + ], + "data": "QXN5bmNDYWxs", + "additionalData": [ + "QXN5bmNDYWxs", + "YWRk", + "AQ==" + ] + }, + { + "address": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3", + "identifier": "writeLog", + "topics": [ + "6sts9++Sb93jXufBXEN4veMc5sfxE0vcWGfcfNAfMcQ=" + ], + "data": "QDZmNmI=", + "additionalData": [ + "QDZmNmI=" + ] + } + ] + }, + "isRelayed": true, + "relayerAddress": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "relayerSignature": "dfb60f7d13c024335ec9586b7af579ed47da002a23313272069711bceebddc2d9216b7cc1395c37a1c328288fcc774d5cbeb610db2900f76647307d35a593e08" +} diff --git a/node/external/transactionAPI/unmarshaller.go b/node/external/transactionAPI/unmarshaller.go index c9526217f4f..24d8961885b 100644 --- a/node/external/transactionAPI/unmarshaller.go +++ b/node/external/transactionAPI/unmarshaller.go @@ -101,7 +101,11 @@ func (tu *txUnmarshaller) unmarshalTransaction(txBytes []byte, txType transactio } apiTx.ReceiversShardIDs = res.ReceiversShardID - apiTx.IsRelayed = res.IsRelayed + + hasValidRelayer := len(apiTx.RelayerAddress) == len(apiTx.Sender) && len(apiTx.RelayerAddress) > 0 + hasValidRelayerSignature := len(apiTx.RelayerSignature) == len(apiTx.Signature) && len(apiTx.RelayerSignature) > 0 + isRelayedV3 := hasValidRelayer && hasValidRelayerSignature + apiTx.IsRelayed = res.IsRelayed || isRelayedV3 return apiTx, nil } @@ -132,6 +136,12 @@ func (tu *txUnmarshaller) prepareNormalTx(tx *transaction.Transaction) *transact apiTx.GuardianAddr = tu.addressPubKeyConverter.SilentEncode(tx.GuardianAddr, log) apiTx.GuardianSignature = hex.EncodeToString(tx.GuardianSignature) } + if len(tx.RelayerAddr) > 0 { + apiTx.RelayerAddress = tu.addressPubKeyConverter.SilentEncode(tx.RelayerAddr, log) + } + if len(tx.RelayerSignature) > 0 { + apiTx.RelayerSignature = hex.EncodeToString(tx.RelayerSignature) + } return apiTx } @@ -162,6 +172,12 @@ func (tu *txUnmarshaller) prepareInvalidTx(tx *transaction.Transaction) *transac apiTx.GuardianAddr = tu.addressPubKeyConverter.SilentEncode(tx.GuardianAddr, log) apiTx.GuardianSignature = hex.EncodeToString(tx.GuardianSignature) } + if len(tx.RelayerAddr) > 0 { + apiTx.RelayerAddress = tu.addressPubKeyConverter.SilentEncode(tx.RelayerAddr, log) + } + if len(tx.RelayerSignature) > 0 { + apiTx.RelayerSignature = hex.EncodeToString(tx.RelayerSignature) + } return apiTx } diff --git a/node/metrics/metrics.go b/node/metrics/metrics.go index 7d828d26394..5c74f42eacd 100644 --- a/node/metrics/metrics.go +++ b/node/metrics/metrics.go @@ -202,6 +202,7 @@ func InitConfigMetrics( appStatusHandler.SetUInt64Value(common.MetricCryptoOpcodesV2EnableEpoch, uint64(enableEpochs.CryptoOpcodesV2EnableEpoch)) appStatusHandler.SetUInt64Value(common.MetricMultiESDTNFTTransferAndExecuteByUserEnableEpoch, uint64(enableEpochs.MultiESDTNFTTransferAndExecuteByUserEnableEpoch)) appStatusHandler.SetUInt64Value(common.MetricFixRelayedMoveBalanceToNonPayableSCEnableEpoch, uint64(enableEpochs.FixRelayedMoveBalanceToNonPayableSCEnableEpoch)) + appStatusHandler.SetUInt64Value(common.MetricRelayedTransactionsV3EnableEpoch, uint64(enableEpochs.RelayedTransactionsV3EnableEpoch)) for i, nodesChangeConfig := range enableEpochs.MaxNodesChangeEnableEpoch { epochEnable := fmt.Sprintf("%s%d%s", common.MetricMaxNodesChangeEnableEpoch, i, common.EpochEnableSuffix) diff --git a/node/metrics/metrics_test.go b/node/metrics/metrics_test.go index 1aa3bd7be2e..3ffe2cf7a5a 100644 --- a/node/metrics/metrics_test.go +++ b/node/metrics/metrics_test.go @@ -211,6 +211,7 @@ func TestInitConfigMetrics(t *testing.T) { FixRelayedBaseCostEnableEpoch: 104, MultiESDTNFTTransferAndExecuteByUserEnableEpoch: 105, FixRelayedMoveBalanceToNonPayableSCEnableEpoch: 106, + RelayedTransactionsV3EnableEpoch: 107, MaxNodesChangeEnableEpoch: []config.MaxNodesChangeConfig{ { EpochEnable: 0, @@ -332,6 +333,7 @@ func TestInitConfigMetrics(t *testing.T) { "erd_fix_relayed_base_cost_enable_epoch": uint32(104), "erd_multi_esdt_transfer_execute_by_user_enable_epoch": uint32(105), "erd_fix_relayed_move_balance_to_non_payable_sc_enable_epoch": uint32(106), + "erd_relayed_transactions_v3_enable_epoch": uint32(107), "erd_max_nodes_change_enable_epoch": nil, "erd_total_supply": "12345", "erd_hysteresis": "0.100000", diff --git a/node/node.go b/node/node.go index 09ccebfa761..38b00841d2a 100644 --- a/node/node.go +++ b/node/node.go @@ -736,7 +736,7 @@ func (n *Node) ValidateTransaction(tx *transaction.Transaction) error { if errors.Is(err, process.ErrAccountNotFound) { return fmt.Errorf("%w for address %s", process.ErrInsufficientFunds, - n.coreComponents.AddressPubKeyConverter().SilentEncode(tx.SndAddr, log), + n.extractAddressFromError(err), ) } @@ -811,6 +811,7 @@ func (n *Node) commonTransactionValidation( enableSignWithTxHash, n.coreComponents.TxSignHasher(), n.coreComponents.TxVersionChecker(), + n.coreComponents.EnableEpochsHandler(), ) if err != nil { return nil, nil, err @@ -859,6 +860,9 @@ func (n *Node) CreateTransaction(txArgs *external.ArgsCreateTransaction) (*trans if len(txArgs.GuardianSigHex) > n.addressSignatureHexSize { return nil, nil, fmt.Errorf("%w for guardian signature", ErrInvalidSignatureLength) } + if len(txArgs.RelayerSignatureHex) > n.addressSignatureHexSize { + return nil, nil, fmt.Errorf("%w for relayer signature", ErrInvalidSignatureLength) + } if uint32(len(txArgs.Receiver)) > n.coreComponents.EncodedAddressLen() { return nil, nil, fmt.Errorf("%w for receiver", ErrInvalidAddressLength) @@ -869,6 +873,9 @@ func (n *Node) CreateTransaction(txArgs *external.ArgsCreateTransaction) (*trans if uint32(len(txArgs.Guardian)) > n.coreComponents.EncodedAddressLen() { return nil, nil, fmt.Errorf("%w for guardian", ErrInvalidAddressLength) } + if uint32(len(txArgs.Relayer)) > n.coreComponents.EncodedAddressLen() { + return nil, nil, fmt.Errorf("%w for relayer", ErrInvalidAddressLength) + } if len(txArgs.SenderUsername) > core.MaxUserNameLength { return nil, nil, ErrInvalidSenderUsernameLength } @@ -925,6 +932,20 @@ func (n *Node) CreateTransaction(txArgs *external.ArgsCreateTransaction) (*trans return nil, nil, err } } + if len(txArgs.Relayer) > 0 { + relayerAddress, errDecode := addrPubKeyConverter.Decode(txArgs.Relayer) + if errDecode != nil { + return nil, nil, fmt.Errorf("%w while decoding relayer address", errDecode) + } + tx.RelayerAddr = relayerAddress + } + if len(txArgs.RelayerSignatureHex) > 0 { + relayerSigBytes, errDecodeString := hex.DecodeString(txArgs.RelayerSignatureHex) + if errDecodeString != nil { + return nil, nil, fmt.Errorf("%w while decoding relayer signature", errDecodeString) + } + tx.RelayerSignature = relayerSigBytes + } var txHash []byte txHash, err = core.CalculateHash(n.coreComponents.InternalMarshalizer(), n.coreComponents.Hasher(), tx) @@ -1538,6 +1559,22 @@ func (n *Node) getKeyBytes(key string) ([]byte, error) { return hex.DecodeString(key) } +func (n *Node) extractAddressFromError(err error) string { + if !strings.Contains(err.Error(), "for address") { + return "" + } + + errWords := strings.Split(err.Error(), " ") + for _, word := range errWords { + _, errDecode := n.coreComponents.AddressPubKeyConverter().Decode(word) + if errDecode == nil { + return word + } + } + + return "" +} + // IsInterfaceNil returns true if there is no value under the interface func (n *Node) IsInterfaceNil() bool { return n == nil diff --git a/node/nodeRunner.go b/node/nodeRunner.go index 1837c78b427..749d70ffd2a 100644 --- a/node/nodeRunner.go +++ b/node/nodeRunner.go @@ -845,6 +845,7 @@ func (nr *nodeRunner) createMetrics( metrics.SaveUint64Metric(statusCoreComponents.AppStatusHandler(), common.MetricMinGasPrice, coreComponents.EconomicsData().MinGasPrice()) metrics.SaveUint64Metric(statusCoreComponents.AppStatusHandler(), common.MetricMinGasLimit, coreComponents.EconomicsData().MinGasLimit()) metrics.SaveUint64Metric(statusCoreComponents.AppStatusHandler(), common.MetricExtraGasLimitGuardedTx, coreComponents.EconomicsData().ExtraGasLimitGuardedTx()) + metrics.SaveUint64Metric(statusCoreComponents.AppStatusHandler(), common.MetricExtraGasLimitRelayedTx, coreComponents.EconomicsData().MinGasLimit()) metrics.SaveStringMetric(statusCoreComponents.AppStatusHandler(), common.MetricRewardsTopUpGradientPoint, coreComponents.EconomicsData().RewardsTopUpGradientPoint().String()) metrics.SaveStringMetric(statusCoreComponents.AppStatusHandler(), common.MetricTopUpFactor, fmt.Sprintf("%g", coreComponents.EconomicsData().RewardsTopUpFactor())) metrics.SaveStringMetric(statusCoreComponents.AppStatusHandler(), common.MetricGasPriceModifier, fmt.Sprintf("%g", coreComponents.EconomicsData().GasPriceModifier())) diff --git a/node/node_test.go b/node/node_test.go index a39c1499e21..7c516e18a93 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -3039,15 +3039,63 @@ func TestValidateTransaction_ShouldAdaptAccountNotFoundError(t *testing.T) { node.WithCryptoComponents(getDefaultCryptoComponents()), ) - tx := &transaction.Transaction{ - SndAddr: bytes.Repeat([]byte("1"), 32), - RcvAddr: bytes.Repeat([]byte("1"), 32), - Value: big.NewInt(37), - Signature: []byte("signature"), - ChainID: []byte("chainID"), - } - err := n.ValidateTransaction(tx) - require.Equal(t, "insufficient funds for address erd1xycnzvf3xycnzvf3xycnzvf3xycnzvf3xycnzvf3xycnzvf3xycspcqad6", err.Error()) + t.Run("normal tx", func(t *testing.T) { + tx := &transaction.Transaction{ + SndAddr: bytes.Repeat([]byte("1"), 32), + RcvAddr: bytes.Repeat([]byte("1"), 32), + Value: big.NewInt(37), + Signature: []byte("signature"), + ChainID: []byte("chainID"), + } + + err := n.ValidateTransaction(tx) + require.Equal(t, "insufficient funds for address erd1xycnzvf3xycnzvf3xycnzvf3xycnzvf3xycnzvf3xycnzvf3xycspcqad6", err.Error()) + }) + t.Run("relayed tx v3, no funds for sender", func(t *testing.T) { + tx := &transaction.Transaction{ + SndAddr: bytes.Repeat([]byte("1"), 32), + RcvAddr: bytes.Repeat([]byte("1"), 32), + Value: big.NewInt(37), + Signature: []byte("sSignature"), + RelayerAddr: bytes.Repeat([]byte("2"), 32), + RelayerSignature: []byte("rSignature"), + ChainID: []byte("chainID"), + } + err := n.ValidateTransaction(tx) + require.Equal(t, "insufficient funds for address erd1xycnzvf3xycnzvf3xycnzvf3xycnzvf3xycnzvf3xycnzvf3xycspcqad6", err.Error()) + }) + t.Run("relayed tx v3, no funds for relayer", func(t *testing.T) { + tx := &transaction.Transaction{ + SndAddr: bytes.Repeat([]byte("1"), 32), + RcvAddr: bytes.Repeat([]byte("1"), 32), + Value: big.NewInt(37), + Signature: []byte("sSignature"), + RelayerAddr: bytes.Repeat([]byte("2"), 32), + RelayerSignature: []byte("rSignature"), + ChainID: []byte("chainID"), + } + + stateComp := getDefaultStateComponents() + stateComp.AccountsAPI = &stateMock.AccountsStub{ + GetExistingAccountCalled: func(addressContainer []byte) (vmcommon.AccountHandler, error) { + if bytes.Equal(addressContainer, tx.SndAddr) { + return &stateMock.UserAccountStub{}, nil + } + + return nil, errors.New("account not found") + }, + } + nLocal, _ := node.NewNode( + node.WithCoreComponents(getDefaultCoreComponents()), + node.WithBootstrapComponents(getDefaultBootstrapComponents()), + node.WithProcessComponents(getDefaultProcessComponents()), + node.WithStateComponents(stateComp), + node.WithCryptoComponents(getDefaultCryptoComponents()), + ) + + err := nLocal.ValidateTransaction(tx) + require.Equal(t, "insufficient funds for address erd1xgeryv3jxgeryv3jxgeryv3jxgeryv3jxgeryv3jxgeryv3jxgeqvw86cj", err.Error()) + }) } func TestCreateShardedStores_NilShardCoordinatorShouldError(t *testing.T) { @@ -5294,7 +5342,7 @@ func getDefaultCoreComponents() *nodeMockFactory.CoreComponentsMock { StartTime: time.Time{}, EpochChangeNotifier: &epochNotifier.EpochNotifierStub{}, TxVersionCheckHandler: versioning.NewTxVersionChecker(0), - EnableEpochsHandlerField: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, + EnableEpochsHandlerField: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.RelayedTransactionsV3Flag), } } diff --git a/outport/mock/economicsDataMock.go b/outport/mock/economicsDataMock.go index cf9cf4dc848..36da6e9a7d4 100644 --- a/outport/mock/economicsDataMock.go +++ b/outport/mock/economicsDataMock.go @@ -20,6 +20,11 @@ const ( type EconomicsHandlerMock struct { } +// ComputeGasUnitsFromRefundValue - +func (e *EconomicsHandlerMock) ComputeGasUnitsFromRefundValue(_ coreData.TransactionWithFeeHandler, _ *big.Int, _ uint32) uint64 { + return 0 +} + // MaxGasLimitPerBlock - func (e *EconomicsHandlerMock) MaxGasLimitPerBlock(_ uint32) uint64 { return 0 diff --git a/outport/process/alteredaccounts/tokensProcessor.go b/outport/process/alteredaccounts/tokensProcessor.go index 687c543bcdf..672a29fc7af 100644 --- a/outport/process/alteredaccounts/tokensProcessor.go +++ b/outport/process/alteredaccounts/tokensProcessor.go @@ -137,7 +137,7 @@ func (tp *tokensProcessor) processMultiTransferEvent(event data.EventHandler, ma if string(tokenID) == vmcommon.EGLDIdentifier { tp.processNativeEGLDTransferWithMultiTransfer(destinationAddress, markedAlteredAccounts) - return + continue } // process event for the sender address diff --git a/outport/process/alteredaccounts/tokensProcessor_test.go b/outport/process/alteredaccounts/tokensProcessor_test.go index af737a1de94..2ac172b88bf 100644 --- a/outport/process/alteredaccounts/tokensProcessor_test.go +++ b/outport/process/alteredaccounts/tokensProcessor_test.go @@ -97,3 +97,46 @@ func TestTokenProcessorProcessEventMultiTransferV2WithEGLD(t *testing.T) { } require.Equal(t, markedAccount2, markedAccounts["receiver"]) } + +func TestTokenProcessorProcessEventMultiTransferV2WithEGLDAndMoreTokens(t *testing.T) { + t.Parallel() + + tp := newTokensProcessor(&mock.ShardCoordinatorStub{}) + + markedAccounts := make(map[string]*markedAlteredAccount) + tp.processEvent(&transaction.Event{ + Identifier: []byte(core.BuiltInFunctionMultiESDTNFTTransfer), + Address: []byte("addr"), + Topics: [][]byte{[]byte("token1"), big.NewInt(0).Bytes(), []byte("2"), []byte(vmcommon.EGLDIdentifier), big.NewInt(0).Bytes(), []byte("3"), []byte("token2"), big.NewInt(0).Bytes(), []byte("2"), []byte("receiver")}, + }, markedAccounts) + + require.Equal(t, 2, len(markedAccounts)) + markedAccount1 := &markedAlteredAccount{ + tokens: map[string]*markedAlteredAccountToken{ + "token1": { + identifier: "token1", + nonce: 0, + }, + "token2": { + identifier: "token2", + nonce: 0, + }, + }, + } + require.Equal(t, markedAccount1, markedAccounts["addr"]) + + markedAccount2 := &markedAlteredAccount{ + balanceChanged: true, + tokens: map[string]*markedAlteredAccountToken{ + "token1": { + identifier: "token1", + nonce: 0, + }, + "token2": { + identifier: "token2", + nonce: 0, + }, + }, + } + require.Equal(t, markedAccount2, markedAccounts["receiver"]) +} diff --git a/outport/process/interface.go b/outport/process/interface.go index bec97f362b3..0c8b332aa31 100644 --- a/outport/process/interface.go +++ b/outport/process/interface.go @@ -32,6 +32,7 @@ type GasConsumedProvider interface { // EconomicsDataHandler defines the functionality needed for economics data type EconomicsDataHandler interface { + ComputeGasUnitsFromRefundValue(tx data.TransactionWithFeeHandler, refundValue *big.Int, epoch uint32) uint64 ComputeGasUsedAndFeeBasedOnRefundValue(tx data.TransactionWithFeeHandler, refundValue *big.Int) (uint64, *big.Int) ComputeTxFeeBasedOnGasUsed(tx data.TransactionWithFeeHandler, gasUsed uint64) *big.Int ComputeTxFee(tx data.TransactionWithFeeHandler) *big.Int diff --git a/outport/process/transactionsfee/interface.go b/outport/process/transactionsfee/interface.go index 53042467442..551ee59d1e2 100644 --- a/outport/process/transactionsfee/interface.go +++ b/outport/process/transactionsfee/interface.go @@ -11,6 +11,7 @@ import ( // FeesProcessorHandler defines the interface for the transaction fees processor type FeesProcessorHandler interface { ComputeGasUsedAndFeeBasedOnRefundValue(tx data.TransactionWithFeeHandler, refundValue *big.Int) (uint64, *big.Int) + ComputeGasUnitsFromRefundValue(tx data.TransactionWithFeeHandler, refundValue *big.Int, epoch uint32) uint64 ComputeTxFeeBasedOnGasUsed(tx data.TransactionWithFeeHandler, gasUsed uint64) *big.Int ComputeTxFee(tx data.TransactionWithFeeHandler) *big.Int ComputeGasLimit(tx data.TransactionWithFeeHandler) uint64 diff --git a/outport/process/transactionsfee/transactionChecker.go b/outport/process/transactionsfee/transactionChecker.go index 546fdd9f432..28c2658a9a4 100644 --- a/outport/process/transactionsfee/transactionChecker.go +++ b/outport/process/transactionsfee/transactionChecker.go @@ -9,6 +9,7 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" + "github.com/multiversx/mx-chain-go/common" vmcommon "github.com/multiversx/mx-chain-vm-common-go" ) @@ -47,11 +48,25 @@ func isSCRForSenderWithRefund(scr *smartContractResult.SmartContractResult, txHa } func isRefundForRelayed(dbScResult *smartContractResult.SmartContractResult, tx data.TransactionHandler) bool { + isRelayedV3 := common.IsRelayedTxV3(tx) isForRelayed := string(dbScResult.ReturnMessage) == core.GasRefundForRelayerMessage isForSender := bytes.Equal(dbScResult.RcvAddr, tx.GetSndAddr()) + isForRelayerV3 := isForRelayerOfV3(dbScResult, tx) differentHash := !bytes.Equal(dbScResult.OriginalTxHash, dbScResult.PrevTxHash) - return isForRelayed && isForSender && differentHash + isRefundForRelayedV1V2 := isForRelayed && isForSender && differentHash && !isRelayedV3 + isRefundForRelayedV3 := isForRelayed && isForRelayerV3 && isRelayedV3 + + return isRefundForRelayedV1V2 || isRefundForRelayedV3 +} + +func isForRelayerOfV3(scr *smartContractResult.SmartContractResult, tx data.TransactionHandler) bool { + relayedTx, isRelayedV3 := tx.(data.RelayedTransactionHandler) + if !isRelayedV3 { + return false + } + + return bytes.Equal(relayedTx.GetRelayerAddr(), scr.RcvAddr) } func isDataOk(data []byte) bool { diff --git a/outport/process/transactionsfee/transactionsFeeProcessor.go b/outport/process/transactionsfee/transactionsFeeProcessor.go index 30bcf0fca76..6cbfb0ddbcf 100644 --- a/outport/process/transactionsfee/transactionsFeeProcessor.go +++ b/outport/process/transactionsfee/transactionsFeeProcessor.go @@ -103,7 +103,7 @@ func (tep *transactionsFeeProcessor) PutFeeAndGasUsed(pool *outportcore.Transact txsWithResultsMap := prepareTransactionsAndScrs(pool) tep.prepareNormalTxs(txsWithResultsMap, epoch) - return tep.prepareScrsNoTx(txsWithResultsMap) + return tep.prepareScrsNoTx(txsWithResultsMap, epoch) } func (tep *transactionsFeeProcessor) prepareInvalidTxs(pool *outportcore.TransactionPool) { @@ -136,13 +136,12 @@ func (tep *transactionsFeeProcessor) prepareNormalTxs(transactionsAndScrs *trans feeInfo.SetFee(initialPaidFee) } - userTx, totalFee, isRelayed := tep.getFeeOfRelayed(txWithResult) - isRelayedAfterFix := isRelayed && isFeeFixActive + userTx, totalFee, isRelayedV1V2 := tep.getFeeOfRelayedV1V2(txWithResult) + isRelayedAfterFix := isRelayedV1V2 && isFeeFixActive if isRelayedAfterFix { feeInfo.SetFee(totalFee) feeInfo.SetInitialPaidFee(totalFee) feeInfo.SetGasUsed(big.NewInt(0).Div(totalFee, big.NewInt(0).SetUint64(txHandler.GetGasPrice())).Uint64()) - } tep.prepareTxWithResults(txHashHex, txWithResult, userTx, epoch) @@ -156,6 +155,7 @@ func (tep *transactionsFeeProcessor) prepareTxWithResults( epoch uint32, ) { hasRefund := false + totalRefunds := big.NewInt(0) for _, scrHandler := range txWithResults.scrs { scr, ok := scrHandler.GetTxHandler().(*smartContractResult.SmartContractResult) if !ok { @@ -163,16 +163,24 @@ func (tep *transactionsFeeProcessor) prepareTxWithResults( } if isSCRForSenderWithRefund(scr, txHashHex, txWithResults.GetTxHandler()) || isRefundForRelayed(scr, txWithResults.GetTxHandler()) { - tep.setGasUsedAndFeeBasedOnRefundValue(txWithResults, userTx, scr.Value, epoch) hasRefund = true - break + totalRefunds.Add(totalRefunds, scr.Value) } } + if totalRefunds.Cmp(big.NewInt(0)) > 0 { + tep.setGasUsedAndFeeBasedOnRefundValue(txWithResults, userTx, totalRefunds, epoch) + + } + tep.prepareTxWithResultsBasedOnLogs(txHashHex, txWithResults, userTx, hasRefund, epoch) } -func (tep *transactionsFeeProcessor) getFeeOfRelayed(tx *transactionWithResults) (data.TransactionHandler, *big.Int, bool) { +func (tep *transactionsFeeProcessor) getFeeOfRelayedV1V2(tx *transactionWithResults) (data.TransactionHandler, *big.Int, bool) { + if common.IsValidRelayedTxV3(tx.GetTxHandler()) { + return nil, nil, false + } + if len(tx.GetTxHandler().GetData()) == 0 { return nil, nil, false } @@ -271,7 +279,10 @@ func (tep *transactionsFeeProcessor) setGasUsedAndFeeBasedOnRefundValue( refund *big.Int, epoch uint32, ) { - if !check.IfNil(userTx) && tep.enableEpochsHandler.IsFlagEnabledInEpoch(common.FixRelayedBaseCostFlag, epoch) { + txWithResults.GetFeeInfo().SetHadRefund() + + isValidUserTxAfterBaseCostActivation := !check.IfNil(userTx) && tep.enableEpochsHandler.IsFlagEnabledInEpoch(common.FixRelayedBaseCostFlag, epoch) + if isValidUserTxAfterBaseCostActivation && !common.IsValidRelayedTxV3(txWithResults.GetTxHandler()) { gasUsed, fee := tep.txFeeCalculator.ComputeGasUsedAndFeeBasedOnRefundValue(userTx, refund) tx := txWithResults.GetTxHandler() @@ -289,7 +300,7 @@ func (tep *transactionsFeeProcessor) setGasUsedAndFeeBasedOnRefundValue( txWithResults.GetFeeInfo().SetFee(fee) } -func (tep *transactionsFeeProcessor) prepareScrsNoTx(transactionsAndScrs *transactionsAndScrsHolder) error { +func (tep *transactionsFeeProcessor) prepareScrsNoTx(transactionsAndScrs *transactionsAndScrsHolder, epoch uint32) error { for _, scrHandler := range transactionsAndScrs.scrsNoTx { scr, ok := scrHandler.GetTxHandler().(*smartContractResult.SmartContractResult) if !ok { @@ -311,18 +322,31 @@ func (tep *transactionsFeeProcessor) prepareScrsNoTx(transactionsAndScrs *transa continue } + isRelayedV3 := common.IsValidRelayedTxV3(txFromStorage) isForInitialTxSender := bytes.Equal(scr.RcvAddr, txFromStorage.SndAddr) - if !isForInitialTxSender { + isForRelayerV3 := bytes.Equal(scr.RcvAddr, txFromStorage.RelayerAddr) + shouldSkipRelayedV3 := isRelayedV3 && !isForRelayerV3 + shouldSkipTx := !isRelayedV3 && !isForInitialTxSender || shouldSkipRelayedV3 + if shouldSkipTx { continue } userTx := tep.getUserTxOfRelayed(txFromStorage) if check.IfNil(userTx) { + // relayed v3 and other txs + if isRelayedV3 { + gasUnits := tep.txFeeCalculator.ComputeGasUnitsFromRefundValue(txFromStorage, scr.Value, epoch) + scrHandler.GetFeeInfo().SetGasRefunded(gasUnits) + scrHandler.GetFeeInfo().SetFee(scr.Value) + continue + } + gasUsed, fee := tep.txFeeCalculator.ComputeGasUsedAndFeeBasedOnRefundValue(txFromStorage, scr.Value) scrHandler.GetFeeInfo().SetGasUsed(gasUsed) scrHandler.GetFeeInfo().SetFee(fee) } else { + // relayed v1 and v2 gasUsed, fee := tep.txFeeCalculator.ComputeGasUsedAndFeeBasedOnRefundValue(userTx, scr.Value) gasUsedRelayedTx := tep.txFeeCalculator.ComputeGasLimit(txFromStorage) diff --git a/outport/process/transactionsfee/transactionsFeeProcessor_test.go b/outport/process/transactionsfee/transactionsFeeProcessor_test.go index 6f0e0f94c35..2aa399a26fe 100644 --- a/outport/process/transactionsfee/transactionsFeeProcessor_test.go +++ b/outport/process/transactionsfee/transactionsFeeProcessor_test.go @@ -10,10 +10,13 @@ import ( outportcore "github.com/multiversx/mx-chain-core-go/data/outport" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/outport/mock" "github.com/multiversx/mx-chain-go/process" + "github.com/multiversx/mx-chain-go/process/economics" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" + "github.com/multiversx/mx-chain-go/testscommon/epochNotifier" "github.com/multiversx/mx-chain-go/testscommon/genericMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" logger "github.com/multiversx/mx-chain-logger-go" @@ -22,6 +25,18 @@ import ( var pubKeyConverter, _ = pubkeyConverter.NewBech32PubkeyConverter(32, "erd") +func createEconomicsData(enableEpochsHandler common.EnableEpochsHandler) process.EconomicsDataHandler { + economicsConfig := testscommon.GetEconomicsConfig() + economicsData, _ := economics.NewEconomicsData(economics.ArgsNewEconomicsData{ + Economics: &economicsConfig, + EnableEpochsHandler: enableEpochsHandler, + TxVersionChecker: &testscommon.TxVersionCheckerStub{}, + EpochNotifier: &epochNotifier.EpochNotifierStub{}, + }) + + return economicsData +} + func prepareMockArg() ArgTransactionsFeeProcessor { return ArgTransactionsFeeProcessor{ Marshaller: marshallerMock.MarshalizerMock{}, @@ -597,3 +612,66 @@ func TestMoveBalanceWithSignalError(t *testing.T) { require.Nil(t, err) require.Equal(t, uint64(225_500), initialTx.GetFeeInfo().GetGasUsed()) } + +func TestPutFeeAndGasUsedRelayedTxV3(t *testing.T) { + t.Parallel() + + txHash := []byte("relayedTxV3") + scrWithRefund := []byte("scrWithRefund") + refundValueBig, _ := big.NewInt(0).SetString("37105580000000", 10) + initialTx := &outportcore.TxInfo{ + Transaction: &transaction.Transaction{ + Nonce: 9, + SndAddr: []byte("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), + RcvAddr: []byte("erd1qqqqqqqqqqqqqpgq2nfn5uxjjkjlrzad3jrak8p3p30v79pseddsm73zpw"), + RelayerAddr: []byte("erd1at9keal0jfhamc67ulq4csmchh33eek87yf5hhzcvlw8e5qlx8zq5hjwjl"), + GasLimit: 5000000, + GasPrice: 1000000000, + Data: []byte("add@01"), + Value: big.NewInt(0), + }, + FeeInfo: &outportcore.FeeInfo{Fee: big.NewInt(0)}, + } + + pool := &outportcore.TransactionPool{ + Transactions: map[string]*outportcore.TxInfo{ + hex.EncodeToString(txHash): initialTx, + }, + SmartContractResults: map[string]*outportcore.SCRInfo{ + hex.EncodeToString(scrWithRefund): { + SmartContractResult: &smartContractResult.SmartContractResult{ + Nonce: 10, + GasPrice: 1000000000, + GasLimit: 0, + Value: refundValueBig, + SndAddr: []byte("erd1qqqqqqqqqqqqqpgq2nfn5uxjjkjlrzad3jrak8p3p30v79pseddsm73zpw"), + RcvAddr: []byte("erd1at9keal0jfhamc67ulq4csmchh33eek87yf5hhzcvlw8e5qlx8zq5hjwjl"), + Data: []byte(""), + PrevTxHash: txHash, + OriginalTxHash: txHash, + ReturnMessage: []byte("gas refund for relayer"), + }, + FeeInfo: &outportcore.FeeInfo{ + Fee: big.NewInt(0), + }, + }, + }, + } + + arg := prepareMockArg() + arg.TxFeeCalculator = createEconomicsData(&enableEpochsHandlerMock.EnableEpochsHandlerStub{ + IsFlagEnabledInEpochCalled: func(flag core.EnableEpochFlag, epoch uint32) bool { + return true + }, + }) + txsFeeProc, err := NewTransactionsFeeProcessor(arg) + require.NotNil(t, txsFeeProc) + require.Nil(t, err) + + err = txsFeeProc.PutFeeAndGasUsed(pool, 0) + require.Nil(t, err) + require.Equal(t, big.NewInt(120804420000000), initialTx.GetFeeInfo().GetFee()) + require.Equal(t, uint64(1289442), initialTx.GetFeeInfo().GetGasUsed()) + require.Equal(t, "157910000000000", initialTx.GetFeeInfo().GetInitialPaidFee().String()) + require.True(t, initialTx.GetFeeInfo().HadRefund) +} diff --git a/process/block/preprocess/gasComputation.go b/process/block/preprocess/gasComputation.go index 628c6de455f..f4e6a82b4a9 100644 --- a/process/block/preprocess/gasComputation.go +++ b/process/block/preprocess/gasComputation.go @@ -374,7 +374,7 @@ func (gc *gasComputation) ComputeGasProvidedByTx( return txHandler.GetGasLimit(), txHandler.GetGasLimit(), nil } - txTypeSndShard, txTypeDstShard := gc.txTypeHandler.ComputeTransactionType(txHandler) + txTypeSndShard, txTypeDstShard, _ := gc.txTypeHandler.ComputeTransactionType(txHandler) isSCCall := txTypeDstShard == process.SCDeployment || txTypeDstShard == process.SCInvoking || txTypeDstShard == process.BuiltInFunctionCall @@ -403,7 +403,7 @@ func (gc *gasComputation) computeGasProvidedByTxV1( ) (uint64, uint64, error) { moveBalanceConsumption := gc.economicsFee.ComputeGasLimit(txHandler) - txTypeInShard, _ := gc.txTypeHandler.ComputeTransactionType(txHandler) + txTypeInShard, _, _ := gc.txTypeHandler.ComputeTransactionType(txHandler) isSCCall := txTypeInShard == process.SCDeployment || txTypeInShard == process.SCInvoking || txTypeInShard == process.BuiltInFunctionCall || diff --git a/process/block/preprocess/gasComputation_test.go b/process/block/preprocess/gasComputation_test.go index b59d8b45bf1..f60a7455fa6 100644 --- a/process/block/preprocess/gasComputation_test.go +++ b/process/block/preprocess/gasComputation_test.go @@ -214,8 +214,8 @@ func TestComputeGasProvidedByTx_ShouldWorkWhenTxReceiverAddressIsASmartContractI }, }, &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }}, createEnableEpochsHandler(), ) @@ -237,8 +237,8 @@ func TestComputeGasProvidedByTx_ShouldWorkWhenTxReceiverAddressIsASmartContractC }, }, &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.MoveBalance, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.SCInvoking, false }}, createEnableEpochsHandler(), ) @@ -260,8 +260,8 @@ func TestComputeGasProvidedByTx_ShouldReturnZeroIf0GasLimit(t *testing.T) { }, }, &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.MoveBalance, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.SCInvoking, false }}, createEnableEpochsHandler(), ) @@ -283,8 +283,8 @@ func TestComputeGasProvidedByTx_ShouldReturnGasLimitIfLessThanMoveBalance(t *tes }, }, &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.MoveBalance, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.SCInvoking, false }}, createEnableEpochsHandler(), ) @@ -306,8 +306,8 @@ func TestComputeGasProvidedByTx_ShouldReturnGasLimitWhenRelayed(t *testing.T) { }, }, &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.RelayedTx, process.RelayedTx + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.RelayedTx, process.RelayedTx, false }}, createEnableEpochsHandler(), ) @@ -329,8 +329,8 @@ func TestComputeGasProvidedByTx_ShouldReturnGasLimitWhenRelayedV2(t *testing.T) }, }, &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.RelayedTxV2, process.RelayedTxV2 + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.RelayedTxV2, process.RelayedTxV2, false }}, createEnableEpochsHandler(), ) @@ -413,11 +413,11 @@ func TestComputeGasProvidedByMiniBlock_ShouldWork(t *testing.T) { }, }, &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { if core.IsSmartContractAddress(tx.GetRcvAddr()) { - return process.MoveBalance, process.SCInvoking + return process.MoveBalance, process.SCInvoking, false } - return process.MoveBalance, process.MoveBalance + return process.MoveBalance, process.MoveBalance, false }}, createEnableEpochsHandler(), ) @@ -453,11 +453,11 @@ func TestComputeGasProvidedByMiniBlock_ShouldWorkV1(t *testing.T) { }, }, &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { if core.IsSmartContractAddress(tx.GetRcvAddr()) { - return process.SCInvoking, process.SCInvoking + return process.SCInvoking, process.SCInvoking, false } - return process.MoveBalance, process.MoveBalance + return process.MoveBalance, process.MoveBalance, false }}, enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) @@ -513,8 +513,8 @@ func TestComputeGasProvidedByTx_ShouldWorkWhenTxReceiverAddressIsASmartContractI }, }, &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }}, createEnableEpochsHandler(), ) @@ -536,8 +536,8 @@ func TestComputeGasProvidedByTx_ShouldWorkWhenTxReceiverAddressIsASmartContractC }, }, &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }}, enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) diff --git a/process/block/preprocess/selectionSession.go b/process/block/preprocess/selectionSession.go index c5c16a56cde..7e3b35687a1 100644 --- a/process/block/preprocess/selectionSession.go +++ b/process/block/preprocess/selectionSession.go @@ -21,22 +21,24 @@ type selectionSession struct { ephemeralAccountsCache map[string]vmcommon.AccountHandler } -type argsSelectionSession struct { - accountsAdapter state.AccountsAdapter - transactionsProcessor process.TransactionProcessor +// ArgsSelectionSession holds the arguments for creating a new selection session. +type ArgsSelectionSession struct { + AccountsAdapter state.AccountsAdapter + TransactionsProcessor process.TransactionProcessor } -func newSelectionSession(args argsSelectionSession) (*selectionSession, error) { - if check.IfNil(args.accountsAdapter) { +// NewSelectionSession creates a new selection session. +func NewSelectionSession(args ArgsSelectionSession) (*selectionSession, error) { + if check.IfNil(args.AccountsAdapter) { return nil, process.ErrNilAccountsAdapter } - if check.IfNil(args.transactionsProcessor) { + if check.IfNil(args.TransactionsProcessor) { return nil, process.ErrNilTxProcessor } return &selectionSession{ - accountsAdapter: args.accountsAdapter, - transactionsProcessor: args.transactionsProcessor, + accountsAdapter: args.AccountsAdapter, + transactionsProcessor: args.TransactionsProcessor, ephemeralAccountsCache: make(map[string]vmcommon.AccountHandler), }, nil } diff --git a/process/block/preprocess/selectionSession_test.go b/process/block/preprocess/selectionSession_test.go index fc46d5ced15..939da698cd1 100644 --- a/process/block/preprocess/selectionSession_test.go +++ b/process/block/preprocess/selectionSession_test.go @@ -17,23 +17,23 @@ import ( func TestNewSelectionSession(t *testing.T) { t.Parallel() - session, err := newSelectionSession(argsSelectionSession{ - accountsAdapter: nil, - transactionsProcessor: &testscommon.TxProcessorStub{}, + session, err := NewSelectionSession(ArgsSelectionSession{ + AccountsAdapter: nil, + TransactionsProcessor: &testscommon.TxProcessorStub{}, }) require.Nil(t, session) require.ErrorIs(t, err, process.ErrNilAccountsAdapter) - session, err = newSelectionSession(argsSelectionSession{ - accountsAdapter: &stateMock.AccountsStub{}, - transactionsProcessor: nil, + session, err = NewSelectionSession(ArgsSelectionSession{ + AccountsAdapter: &stateMock.AccountsStub{}, + TransactionsProcessor: nil, }) require.Nil(t, session) require.ErrorIs(t, err, process.ErrNilTxProcessor) - session, err = newSelectionSession(argsSelectionSession{ - accountsAdapter: &stateMock.AccountsStub{}, - transactionsProcessor: &testscommon.TxProcessorStub{}, + session, err = NewSelectionSession(ArgsSelectionSession{ + AccountsAdapter: &stateMock.AccountsStub{}, + TransactionsProcessor: &testscommon.TxProcessorStub{}, }) require.NoError(t, err) require.NotNil(t, session) @@ -66,9 +66,9 @@ func TestSelectionSession_GetAccountState(t *testing.T) { return nil, fmt.Errorf("account not found: %s", address) } - session, err := newSelectionSession(argsSelectionSession{ - accountsAdapter: accounts, - transactionsProcessor: processor, + session, err := NewSelectionSession(ArgsSelectionSession{ + AccountsAdapter: accounts, + TransactionsProcessor: processor, }) require.NoError(t, err) require.NotNil(t, session) @@ -111,9 +111,9 @@ func TestSelectionSession_IsIncorrectlyGuarded(t *testing.T) { return nil } - session, err := newSelectionSession(argsSelectionSession{ - accountsAdapter: accounts, - transactionsProcessor: processor, + session, err := NewSelectionSession(ArgsSelectionSession{ + AccountsAdapter: accounts, + TransactionsProcessor: processor, }) require.NoError(t, err) require.NotNil(t, session) @@ -144,9 +144,9 @@ func TestSelectionSession_ephemeralAccountsCache_IsSharedAmongCalls(t *testing.T return &stateMock.UserAccountStub{}, nil } - session, err := newSelectionSession(argsSelectionSession{ - accountsAdapter: accounts, - transactionsProcessor: processor, + session, err := NewSelectionSession(ArgsSelectionSession{ + AccountsAdapter: accounts, + TransactionsProcessor: processor, }) require.NoError(t, err) require.NotNil(t, session) diff --git a/process/block/preprocess/transactions.go b/process/block/preprocess/transactions.go index 8578a97e92d..2f3fc290ef6 100644 --- a/process/block/preprocess/transactions.go +++ b/process/block/preprocess/transactions.go @@ -1411,9 +1411,9 @@ func (txs *transactions) computeSortedTxs( sortedTransactionsProvider := createSortedTransactionsProvider(txShardPool) log.Debug("computeSortedTxs.GetSortedTransactions") - session, err := newSelectionSession(argsSelectionSession{ - accountsAdapter: txs.accounts, - transactionsProcessor: txs.txProcessor, + session, err := NewSelectionSession(ArgsSelectionSession{ + AccountsAdapter: txs.accounts, + TransactionsProcessor: txs.txProcessor, }) if err != nil { return nil, nil, err diff --git a/process/block/preprocess/transactionsV2.go b/process/block/preprocess/transactionsV2.go index 1eb46683894..8d5bbbc320a 100644 --- a/process/block/preprocess/transactionsV2.go +++ b/process/block/preprocess/transactionsV2.go @@ -549,7 +549,7 @@ func (txs *transactions) getTxAndMbInfo( } numNewTxs := 1 - _, txTypeDstShard := txs.txTypeHandler.ComputeTransactionType(tx) + _, txTypeDstShard, _ := txs.txTypeHandler.ComputeTransactionType(tx) isReceiverSmartContractAddress := txTypeDstShard == process.SCDeployment || txTypeDstShard == process.SCInvoking isCrossShardScCallOrSpecialTx := receiverShardID != txs.shardCoordinator.SelfId() && (isReceiverSmartContractAddress || len(tx.RcvUserName) > 0) @@ -683,7 +683,7 @@ func (txs *transactions) shouldContinueProcessingScheduledTx( mbInfo.senderAddressToSkip = tx.GetSndAddr() - _, txTypeDstShard := txs.txTypeHandler.ComputeTransactionType(tx) + _, txTypeDstShard, _ := txs.txTypeHandler.ComputeTransactionType(tx) isReceiverSmartContractAddress := txTypeDstShard == process.SCDeployment || txTypeDstShard == process.SCInvoking if !isReceiverSmartContractAddress { return nil, nil, false diff --git a/process/block/preprocess/transactionsV2_test.go b/process/block/preprocess/transactionsV2_test.go index 9d4fb1cf686..1c86454ddda 100644 --- a/process/block/preprocess/transactionsV2_test.go +++ b/process/block/preprocess/transactionsV2_test.go @@ -66,11 +66,11 @@ func createTransactionPreprocessor() *transactions { }, EnableEpochsHandler: &enableEpochsHandlerMock.EnableEpochsHandlerStub{}, TxTypeHandler: &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { if bytes.Equal(tx.GetRcvAddr(), []byte("smart contract address")) { - return process.MoveBalance, process.SCInvoking + return process.MoveBalance, process.SCInvoking, false } - return process.MoveBalance, process.MoveBalance + return process.MoveBalance, process.MoveBalance, false }, }, ScheduledTxsExecutionHandler: &testscommon.ScheduledTxsExecutionStub{}, diff --git a/process/coordinator/process.go b/process/coordinator/process.go index 8a50d9f0b21..dbc68612649 100644 --- a/process/coordinator/process.go +++ b/process/coordinator/process.go @@ -1622,7 +1622,7 @@ func (tc *transactionCoordinator) checkGasProvidedByMiniBlockInReceiverShard( return process.ErrMissingTransaction } - _, txTypeDstShard := tc.txTypeHandler.ComputeTransactionType(txHandler) + _, txTypeDstShard, _ := tc.txTypeHandler.ComputeTransactionType(txHandler) moveBalanceGasLimit := tc.economicsFee.ComputeGasLimit(txHandler) if txTypeDstShard == process.MoveBalance { gasProvidedByTxInReceiverShard = moveBalanceGasLimit diff --git a/process/coordinator/process_test.go b/process/coordinator/process_test.go index 85eeabf1008..344a5f8bf7d 100644 --- a/process/coordinator/process_test.go +++ b/process/coordinator/process_test.go @@ -3308,8 +3308,8 @@ func TestTransactionCoordinator_CheckGasProvidedByMiniBlockInReceiverShardShould }, }, TxTypeHandler: &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.MoveBalance, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.SCInvoking, false }, }, TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, @@ -3367,8 +3367,8 @@ func TestTransactionCoordinator_CheckGasProvidedByMiniBlockInReceiverShardShould }, }, TxTypeHandler: &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.MoveBalance, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.SCInvoking, false }, }, TransactionsLogProcessor: &mock.TxLogsProcessorStub{}, diff --git a/process/coordinator/transactionType.go b/process/coordinator/transactionType.go index 32081a1ac0e..45097d89c50 100644 --- a/process/coordinator/transactionType.go +++ b/process/coordinator/transactionType.go @@ -77,59 +77,61 @@ func NewTxTypeHandler( } // ComputeTransactionType calculates the transaction type -func (tth *txTypeHandler) ComputeTransactionType(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { +func (tth *txTypeHandler) ComputeTransactionType(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { err := tth.checkTxValidity(tx) if err != nil { - return process.InvalidTransaction, process.InvalidTransaction + return process.InvalidTransaction, process.InvalidTransaction, false } + isRelayedV3 := common.IsRelayedTxV3(tx) + isEmptyAddress := tth.isDestAddressEmpty(tx) if isEmptyAddress { if len(tx.GetData()) > 0 { - return process.SCDeployment, process.SCDeployment + return process.SCDeployment, process.SCDeployment, isRelayedV3 } - return process.InvalidTransaction, process.InvalidTransaction + return process.InvalidTransaction, process.InvalidTransaction, isRelayedV3 } if len(tx.GetData()) == 0 { - return process.MoveBalance, process.MoveBalance + return process.MoveBalance, process.MoveBalance, isRelayedV3 } funcName, args := tth.getFunctionFromArguments(tx.GetData()) isBuiltInFunction := tth.isBuiltInFunctionCall(funcName) if isBuiltInFunction { if tth.isSCCallAfterBuiltIn(funcName, args, tx) { - return process.BuiltInFunctionCall, process.SCInvoking + return process.BuiltInFunctionCall, process.SCInvoking, isRelayedV3 } - return process.BuiltInFunctionCall, process.BuiltInFunctionCall + return process.BuiltInFunctionCall, process.BuiltInFunctionCall, isRelayedV3 } if isCallOfType(tx, vm.AsynchronousCallBack) { - return process.SCInvoking, process.SCInvoking + return process.SCInvoking, process.SCInvoking, isRelayedV3 } if len(funcName) == 0 { - return process.MoveBalance, process.MoveBalance + return process.MoveBalance, process.MoveBalance, isRelayedV3 } if tth.isRelayedTransactionV1(funcName) { - return process.RelayedTx, process.RelayedTx + return process.RelayedTx, process.RelayedTx, isRelayedV3 // this should never be reached with both relayed v1 and relayed v3 } if tth.isRelayedTransactionV2(funcName) { - return process.RelayedTxV2, process.RelayedTxV2 + return process.RelayedTxV2, process.RelayedTxV2, isRelayedV3 // this should never be reached with both relayed v2 and relayed v3 } isDestInSelfShard := tth.isDestAddressInSelfShard(tx.GetRcvAddr()) if isDestInSelfShard && core.IsSmartContractAddress(tx.GetRcvAddr()) { - return process.SCInvoking, process.SCInvoking + return process.SCInvoking, process.SCInvoking, isRelayedV3 } if core.IsSmartContractAddress(tx.GetRcvAddr()) { - return process.MoveBalance, process.SCInvoking + return process.MoveBalance, process.SCInvoking, isRelayedV3 } - return process.MoveBalance, process.MoveBalance + return process.MoveBalance, process.MoveBalance, isRelayedV3 } func isCallOfType(tx data.TransactionHandler, callType vm.CallType) bool { diff --git a/process/coordinator/transactionType_test.go b/process/coordinator/transactionType_test.go index 918b6069212..ef00e924141 100644 --- a/process/coordinator/transactionType_test.go +++ b/process/coordinator/transactionType_test.go @@ -124,9 +124,10 @@ func TestTxTypeHandler_ComputeTransactionTypeNil(t *testing.T) { assert.NotNil(t, tth) assert.Nil(t, err) - txTypeIn, txTypeCross := tth.ComputeTransactionType(nil) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(nil) assert.Equal(t, process.InvalidTransaction, txTypeIn) assert.Equal(t, process.InvalidTransaction, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeNilTx(t *testing.T) { @@ -145,9 +146,10 @@ func TestTxTypeHandler_ComputeTransactionTypeNilTx(t *testing.T) { tx.Value = big.NewInt(45) tx = nil - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.InvalidTransaction, txTypeIn) assert.Equal(t, process.InvalidTransaction, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeErrWrongTransaction(t *testing.T) { @@ -165,9 +167,10 @@ func TestTxTypeHandler_ComputeTransactionTypeErrWrongTransaction(t *testing.T) { tx.RcvAddr = nil tx.Value = big.NewInt(45) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.InvalidTransaction, txTypeIn) assert.Equal(t, process.InvalidTransaction, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeScDeployment(t *testing.T) { @@ -186,9 +189,10 @@ func TestTxTypeHandler_ComputeTransactionTypeScDeployment(t *testing.T) { tx.Data = []byte("data") tx.Value = big.NewInt(45) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.SCDeployment, txTypeIn) assert.Equal(t, process.SCDeployment, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeBuiltInFunctionCallNftTransfer(t *testing.T) { @@ -221,9 +225,10 @@ func TestTxTypeHandler_ComputeTransactionTypeBuiltInFunctionCallNftTransfer(t *t tx.Value = big.NewInt(45) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.BuiltInFunctionCall, txTypeIn) assert.Equal(t, process.SCInvoking, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeBuiltInFunctionCallEsdtTransfer(t *testing.T) { @@ -250,9 +255,10 @@ func TestTxTypeHandler_ComputeTransactionTypeBuiltInFunctionCallEsdtTransfer(t * "@" + hex.EncodeToString(big.NewInt(10).Bytes())) tx.Value = big.NewInt(45) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.BuiltInFunctionCall, txTypeIn) assert.Equal(t, process.BuiltInFunctionCall, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeRecv0AddressWrongTransaction(t *testing.T) { @@ -271,9 +277,10 @@ func TestTxTypeHandler_ComputeTransactionTypeRecv0AddressWrongTransaction(t *tes tx.Data = nil tx.Value = big.NewInt(45) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.InvalidTransaction, txTypeIn) assert.Equal(t, process.InvalidTransaction, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeScInvoking(t *testing.T) { @@ -292,9 +299,10 @@ func TestTxTypeHandler_ComputeTransactionTypeScInvoking(t *testing.T) { assert.NotNil(t, tth) assert.Nil(t, err) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.SCInvoking, txTypeIn) assert.Equal(t, process.SCInvoking, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeMoveBalance(t *testing.T) { @@ -318,9 +326,10 @@ func TestTxTypeHandler_ComputeTransactionTypeMoveBalance(t *testing.T) { assert.NotNil(t, tth) assert.Nil(t, err) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.MoveBalance, txTypeIn) assert.Equal(t, process.MoveBalance, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeBuiltInFunc(t *testing.T) { @@ -347,9 +356,10 @@ func TestTxTypeHandler_ComputeTransactionTypeBuiltInFunc(t *testing.T) { assert.NotNil(t, tth) assert.Nil(t, err) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.BuiltInFunctionCall, txTypeIn) assert.Equal(t, process.BuiltInFunctionCall, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeBuiltInFuncNotActiveMoveBalance(t *testing.T) { @@ -378,9 +388,10 @@ func TestTxTypeHandler_ComputeTransactionTypeBuiltInFuncNotActiveMoveBalance(t * assert.NotNil(t, tth) assert.Nil(t, err) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.MoveBalance, txTypeIn) assert.Equal(t, process.MoveBalance, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeBuiltInFuncNotActiveSCCall(t *testing.T) { @@ -409,9 +420,10 @@ func TestTxTypeHandler_ComputeTransactionTypeBuiltInFuncNotActiveSCCall(t *testi assert.NotNil(t, tth) assert.Nil(t, err) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.SCInvoking, txTypeIn) assert.Equal(t, process.SCInvoking, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeRelayedFunc(t *testing.T) { @@ -435,9 +447,10 @@ func TestTxTypeHandler_ComputeTransactionTypeRelayedFunc(t *testing.T) { assert.NotNil(t, tth) assert.Nil(t, err) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.RelayedTx, txTypeIn) assert.Equal(t, process.RelayedTx, txTypeCross) + assert.False(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeRelayedV2Func(t *testing.T) { @@ -461,9 +474,39 @@ func TestTxTypeHandler_ComputeTransactionTypeRelayedV2Func(t *testing.T) { assert.NotNil(t, tth) assert.Nil(t, err) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.RelayedTxV2, txTypeIn) assert.Equal(t, process.RelayedTxV2, txTypeCross) + assert.False(t, isRelayedV3) +} + +func TestTxTypeHandler_ComputeTransactionTypeRelayedV3(t *testing.T) { + t.Parallel() + + tx := &transaction.Transaction{} + tx.Nonce = 0 + tx.SndAddr = []byte("000") + tx.RcvAddr = []byte("001") + tx.Value = big.NewInt(45) + tx.RelayerAddr = []byte("002") + tx.Signature = []byte("ssig") + tx.RelayerSignature = []byte("rsig") + + arg := createMockArguments() + arg.PubkeyConverter = &testscommon.PubkeyConverterStub{ + LenCalled: func() int { + return len(tx.RcvAddr) + }, + } + tth, err := NewTxTypeHandler(arg) + + assert.NotNil(t, tth) + assert.Nil(t, err) + + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) + assert.Equal(t, process.MoveBalance, txTypeIn) + assert.Equal(t, process.MoveBalance, txTypeCross) + assert.True(t, isRelayedV3) } func TestTxTypeHandler_ComputeTransactionTypeForSCRCallBack(t *testing.T) { @@ -488,7 +531,8 @@ func TestTxTypeHandler_ComputeTransactionTypeForSCRCallBack(t *testing.T) { assert.NotNil(t, tth) assert.Nil(t, err) - txTypeIn, txTypeCross := tth.ComputeTransactionType(tx) + txTypeIn, txTypeCross, isRelayedV3 := tth.ComputeTransactionType(tx) assert.Equal(t, process.SCInvoking, txTypeIn) assert.Equal(t, process.SCInvoking, txTypeCross) + assert.False(t, isRelayedV3) } diff --git a/process/dataValidators/txValidator.go b/process/dataValidators/txValidator.go index 9c72be1d89a..d043f207ac0 100644 --- a/process/dataValidators/txValidator.go +++ b/process/dataValidators/txValidator.go @@ -5,6 +5,8 @@ import ( "github.com/multiversx/mx-chain-core-go/core" "github.com/multiversx/mx-chain-core-go/core/check" + "github.com/multiversx/mx-chain-core-go/data" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" "github.com/multiversx/mx-chain-go/state" @@ -74,14 +76,27 @@ func (txv *txValidator) CheckTxValidity(interceptedTx process.InterceptedTransac return nil } + // for relayed v3, we allow sender accounts that do not exist + isRelayedV3 := common.IsRelayedTxV3(interceptedTx.Transaction()) + hasValue := hasTxValue(interceptedTx) + shouldAllowMissingSenderAccount := isRelayedV3 && !hasValue accountHandler, err := txv.getSenderAccount(interceptedTx) - if err != nil { + if err != nil && !shouldAllowMissingSenderAccount { return err } return txv.checkAccount(interceptedTx, accountHandler) } +func hasTxValue(interceptedTx process.InterceptedTransactionHandler) bool { + txValue := interceptedTx.Transaction().GetValue() + if check.IfNilReflect(txValue) { + return false + } + + return txValue.Sign() > 0 +} + func (txv *txValidator) checkAccount( interceptedTx process.InterceptedTransactionHandler, accountHandler vmcommon.AccountHandler, @@ -91,24 +106,41 @@ func (txv *txValidator) checkAccount( return err } - account, err := txv.getSenderUserAccount(interceptedTx, accountHandler) + feePayerAccount, err := txv.getFeePayerAccount(interceptedTx, accountHandler) if err != nil { return err } - return txv.checkBalance(interceptedTx, account) + return txv.checkBalance(interceptedTx, feePayerAccount) } -func (txv *txValidator) getSenderUserAccount( +func (txv *txValidator) getFeePayerAccount( interceptedTx process.InterceptedTransactionHandler, accountHandler vmcommon.AccountHandler, ) (state.UserAccountHandler, error) { - senderAddress := interceptedTx.SenderAddress() - account, ok := accountHandler.(state.UserAccountHandler) + payerAddress := interceptedTx.SenderAddress() + payerAccount := accountHandler + + tx := interceptedTx.Transaction() + if common.IsRelayedTxV3(tx) { + relayedTx := tx.(data.RelayedTransactionHandler) + payerAddress = relayedTx.GetRelayerAddr() + relayerAccount, err := txv.accounts.GetExistingAccount(payerAddress) + if err != nil { + return nil, fmt.Errorf("%w for address %s and shard %d, err: %s", + process.ErrAccountNotFound, + txv.pubKeyConverter.SilentEncode(payerAddress, log), + txv.shardCoordinator.SelfId(), + err.Error(), + ) + } + payerAccount = relayerAccount + } + account, ok := payerAccount.(state.UserAccountHandler) if !ok { return nil, fmt.Errorf("%w, account is not of type *state.Account, address: %s", process.ErrWrongTypeAssertion, - txv.pubKeyConverter.SilentEncode(senderAddress, log), + txv.pubKeyConverter.SilentEncode(payerAddress, log), ) } return account, nil @@ -118,10 +150,9 @@ func (txv *txValidator) checkBalance(interceptedTx process.InterceptedTransactio accountBalance := account.GetBalance() txFee := interceptedTx.Fee() if accountBalance.Cmp(txFee) < 0 { - senderAddress := interceptedTx.SenderAddress() return fmt.Errorf("%w, for address: %s, wanted %v, have %v", process.ErrInsufficientFunds, - txv.pubKeyConverter.SilentEncode(senderAddress, log), + txv.pubKeyConverter.SilentEncode(account.AddressBytes(), log), txFee, accountBalance, ) @@ -131,7 +162,11 @@ func (txv *txValidator) checkBalance(interceptedTx process.InterceptedTransactio } func (txv *txValidator) checkNonce(interceptedTx process.InterceptedTransactionHandler, accountHandler vmcommon.AccountHandler) error { - accountNonce := accountHandler.GetNonce() + accountNonce := uint64(0) + if !check.IfNil(accountHandler) { + accountNonce = accountHandler.GetNonce() + } + txNonce := interceptedTx.Nonce() lowerNonceInTx := txNonce < accountNonce veryHighNonceInTx := txNonce > accountNonce+uint64(txv.maxNonceDeltaAllowed) diff --git a/process/dataValidators/txValidator_test.go b/process/dataValidators/txValidator_test.go index 551b18928d1..31fa230ec39 100644 --- a/process/dataValidators/txValidator_test.go +++ b/process/dataValidators/txValidator_test.go @@ -1,6 +1,7 @@ package dataValidators_test import ( + "bytes" "errors" "math/big" "strconv" @@ -269,31 +270,101 @@ func TestTxValidator_CheckTxValidityTxNonceIsTooHigh(t *testing.T) { func TestTxValidator_CheckTxValidityAccountBalanceIsLessThanTxTotalValueShouldReturnFalse(t *testing.T) { t.Parallel() - accountNonce := uint64(0) - txNonce := uint64(1) - fee := big.NewInt(1000) - accountBalance := big.NewInt(10) + t.Run("normal tx should return false for sender", func(t *testing.T) { + t.Parallel() - adb := getAccAdapter(accountNonce, accountBalance) - shardCoordinator := createMockCoordinator("_", 0) - maxNonceDeltaAllowed := 100 - txValidator, err := dataValidators.NewTxValidator( - adb, - shardCoordinator, - &testscommon.WhiteListHandlerStub{}, - testscommon.NewPubkeyConverterMock(32), - &testscommon.TxVersionCheckerStub{}, - maxNonceDeltaAllowed, - ) - assert.Nil(t, err) + accountNonce := uint64(0) + txNonce := uint64(1) + fee := big.NewInt(1000) + accountBalance := big.NewInt(10) - addressMock := []byte("address") - currentShard := uint32(0) - txValidatorHandler := getInterceptedTxHandler(currentShard, currentShard, txNonce, addressMock, fee) + providedSenderAddress := []byte("address") + adb := &stateMock.AccountsStub{} + adb.GetExistingAccountCalled = func(address []byte) (handler vmcommon.AccountHandler, e error) { + require.True(t, bytes.Equal(providedSenderAddress, address)) - result := txValidator.CheckTxValidity(txValidatorHandler) - assert.NotNil(t, result) - assert.True(t, errors.Is(result, process.ErrInsufficientFunds)) + acc, _ := accounts.NewUserAccount(address, &trie.DataTrieTrackerStub{}, &trie.TrieLeafParserStub{}) + acc.Nonce = accountNonce + acc.Balance = accountBalance + + return acc, nil + } + + shardCoordinator := createMockCoordinator("_", 0) + maxNonceDeltaAllowed := 100 + txValidator, err := dataValidators.NewTxValidator( + adb, + shardCoordinator, + &testscommon.WhiteListHandlerStub{}, + testscommon.NewPubkeyConverterMock(32), + &testscommon.TxVersionCheckerStub{}, + maxNonceDeltaAllowed, + ) + assert.Nil(t, err) + + currentShard := uint32(0) + txValidatorHandler := getInterceptedTxHandler(currentShard, currentShard, txNonce, providedSenderAddress, fee) + + result := txValidator.CheckTxValidity(txValidatorHandler) + assert.NotNil(t, result) + assert.True(t, errors.Is(result, process.ErrInsufficientFunds)) + }) + t.Run("relayed tx v3 should return false for relayer", func(t *testing.T) { + t.Parallel() + + accountNonce := uint64(0) + txNonce := uint64(1) + fee := big.NewInt(1000) + accountBalance := big.NewInt(10) + + providedRelayerAddress := []byte("relayer") + providedSenderAddress := []byte("address") + adb := &stateMock.AccountsStub{} + cnt := 0 + adb.GetExistingAccountCalled = func(address []byte) (handler vmcommon.AccountHandler, e error) { + cnt++ + if cnt == 1 { + return nil, errors.New("sender not found") + } + + require.True(t, bytes.Equal(providedRelayerAddress, address)) + + acc, _ := accounts.NewUserAccount(address, &trie.DataTrieTrackerStub{}, &trie.TrieLeafParserStub{}) + acc.Nonce = accountNonce + acc.Balance = accountBalance + + return acc, nil + } + + shardCoordinator := createMockCoordinator("_", 0) + maxNonceDeltaAllowed := 100 + txValidator, err := dataValidators.NewTxValidator( + adb, + shardCoordinator, + &testscommon.WhiteListHandlerStub{}, + testscommon.NewPubkeyConverterMock(32), + &testscommon.TxVersionCheckerStub{}, + maxNonceDeltaAllowed, + ) + assert.Nil(t, err) + + currentShard := uint32(0) + txValidatorHandler := getInterceptedTxHandler(currentShard, currentShard, txNonce, providedSenderAddress, fee) + txValidatorHandlerStub, ok := txValidatorHandler.(*mock.InterceptedTxHandlerStub) + require.True(t, ok) + txValidatorHandlerStub.TransactionCalled = func() data.TransactionHandler { + return &transaction.Transaction{ + SndAddr: providedSenderAddress, + Signature: []byte("address sig"), + RelayerAddr: providedRelayerAddress, + RelayerSignature: []byte("relayer sig"), + Value: big.NewInt(0), + } + } + result := txValidator.CheckTxValidity(txValidatorHandler) + assert.NotNil(t, result) + assert.True(t, errors.Is(result, process.ErrInsufficientFunds)) + }) } func TestTxValidator_CheckTxValidityAccountNotExitsShouldReturnFalse(t *testing.T) { diff --git a/process/disabled/txTypeHandler.go b/process/disabled/txTypeHandler.go index 302e81af555..dd405edff4d 100644 --- a/process/disabled/txTypeHandler.go +++ b/process/disabled/txTypeHandler.go @@ -17,9 +17,9 @@ func NewTxTypeHandler() *txTypeHandler { } // ComputeTransactionType always returns invalid transaction as it is disabled -func (handler *txTypeHandler) ComputeTransactionType(_ data.TransactionHandler) (process.TransactionType, process.TransactionType) { +func (handler *txTypeHandler) ComputeTransactionType(_ data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { log.Warn("disabled txTypeHandler ComputeTransactionType always returns invalid transaction") - return process.InvalidTransaction, process.InvalidTransaction + return process.InvalidTransaction, process.InvalidTransaction, false } // IsInterfaceNil returns true if there is no value under the interface diff --git a/process/economics/economicsData.go b/process/economics/economicsData.go index 5b7ce045237..84e161ef86f 100644 --- a/process/economics/economicsData.go +++ b/process/economics/economicsData.go @@ -488,15 +488,19 @@ func (ed *economicsData) ComputeGasLimit(tx data.TransactionWithFeeHandler) uint return ed.ComputeGasLimitInEpoch(tx, currentEpoch) } -// ComputeGasLimitInEpoch returns the gas limit need by the provided transaction in order to be executed in a specific epoch +// ComputeGasLimitInEpoch returns the gas limit needed by the provided transaction in order to be executed in a specific epoch func (ed *economicsData) ComputeGasLimitInEpoch(tx data.TransactionWithFeeHandler, epoch uint32) uint64 { gasLimit := ed.getMinGasLimit(epoch) dataLen := uint64(len(tx.GetData())) gasLimit += dataLen * ed.gasPerDataByte txInstance, ok := tx.(*transaction.Transaction) - if ok && ed.txVersionHandler.IsGuardedTransaction(txInstance) { - gasLimit += ed.getExtraGasLimitGuardedTx(epoch) + if ok { + if ed.txVersionHandler.IsGuardedTransaction(txInstance) { + gasLimit += ed.getExtraGasLimitGuardedTx(epoch) + } + + gasLimit += ed.getExtraGasLimitRelayedTx(txInstance, epoch) } return gasLimit @@ -575,6 +579,15 @@ func (ed *economicsData) ComputeGasLimitBasedOnBalance(tx data.TransactionWithFe return ed.ComputeGasLimitBasedOnBalanceInEpoch(tx, balance, currentEpoch) } +// ComputeGasUnitsFromRefundValue will compute the gas unit based on the refund value +func (ed *economicsData) ComputeGasUnitsFromRefundValue(tx data.TransactionWithFeeHandler, refundValue *big.Int, epoch uint32) uint64 { + gasPrice := ed.GasPriceForProcessingInEpoch(tx, epoch) + refund := big.NewInt(0).Set(refundValue) + gasUnits := refund.Div(refund, big.NewInt(int64(gasPrice))) + + return gasUnits.Uint64() +} + // ComputeGasLimitBasedOnBalanceInEpoch will compute gas limit for the given transaction based on the balance in a specific epoch func (ed *economicsData) ComputeGasLimitBasedOnBalanceInEpoch(tx data.TransactionWithFeeHandler, balance *big.Int, epoch uint32) (uint64, error) { balanceWithoutTransferValue := big.NewInt(0).Sub(balance, tx.GetValue()) @@ -605,6 +618,15 @@ func (ed *economicsData) ComputeGasLimitBasedOnBalanceInEpoch(tx data.Transactio return totalGasLimit, nil } +// getExtraGasLimitRelayedTx returns extra gas limit for relayed tx in a specific epoch +func (ed *economicsData) getExtraGasLimitRelayedTx(txInstance *transaction.Transaction, epoch uint32) uint64 { + if common.IsRelayedTxV3(txInstance) { + return ed.MinGasLimitInEpoch(epoch) + } + + return 0 +} + // IsInterfaceNil returns true if there is no value under the interface func (ed *economicsData) IsInterfaceNil() bool { return ed == nil diff --git a/process/errors.go b/process/errors.go index 4088162fd7e..2f58af09f46 100644 --- a/process/errors.go +++ b/process/errors.go @@ -1226,3 +1226,15 @@ var ErrNilSentSignatureTracker = errors.New("nil sent signature tracker") // ErrTransferAndExecuteByUserAddressesAreNil signals that transfer and execute by user addresses are nil var ErrTransferAndExecuteByUserAddressesAreNil = errors.New("transfer and execute by user addresses are nil") + +// ErrRelayedTxV3Disabled signals that relayed tx v3 are disabled +var ErrRelayedTxV3Disabled = errors.New("relayed tx v3 are disabled") + +// ErrGuardedRelayerNotAllowed signals that the provided relayer is guarded +var ErrGuardedRelayerNotAllowed = errors.New("guarded relayer not allowed") + +// ErrRelayedByGuardianNotAllowed signals that the provided guardian is also the relayer +var ErrRelayedByGuardianNotAllowed = errors.New("relayed by guardian not allowed") + +// ErrInvalidRelayedTxV3 signals that an invalid relayed tx v3 has been provided +var ErrInvalidRelayedTxV3 = errors.New("invalid relayed transaction") diff --git a/process/interceptors/factory/interceptedTxDataFactory.go b/process/interceptors/factory/interceptedTxDataFactory.go index 563997c5066..0e1a568ad53 100644 --- a/process/interceptors/factory/interceptedTxDataFactory.go +++ b/process/interceptors/factory/interceptedTxDataFactory.go @@ -130,6 +130,7 @@ func (itdf *interceptedTxDataFactory) Create(buff []byte) (process.InterceptedDa itdf.enableEpochsHandler.IsFlagEnabled(common.TransactionSignedWithTxHashFlag), itdf.txSignHasher, itdf.txVersionChecker, + itdf.enableEpochsHandler, ) } diff --git a/process/interface.go b/process/interface.go index 715c02cbf5c..e5800a54796 100644 --- a/process/interface.go +++ b/process/interface.go @@ -69,7 +69,7 @@ type SmartContractProcessorFacade interface { // TxTypeHandler is an interface to calculate the transaction type type TxTypeHandler interface { - ComputeTransactionType(tx data.TransactionHandler) (TransactionType, TransactionType) + ComputeTransactionType(tx data.TransactionHandler) (TransactionType, TransactionType, bool) IsInterfaceNil() bool } @@ -696,6 +696,7 @@ type feeHandler interface { ComputeGasUsedAndFeeBasedOnRefundValue(tx data.TransactionWithFeeHandler, refundValue *big.Int) (uint64, *big.Int) ComputeTxFeeBasedOnGasUsed(tx data.TransactionWithFeeHandler, gasUsed uint64) *big.Int ComputeGasLimitBasedOnBalance(tx data.TransactionWithFeeHandler, balance *big.Int) (uint64, error) + ComputeGasUnitsFromRefundValue(tx data.TransactionWithFeeHandler, refundValue *big.Int, epoch uint32) uint64 ComputeTxFeeInEpoch(tx data.TransactionWithFeeHandler, epoch uint32) *big.Int ComputeGasLimitInEpoch(tx data.TransactionWithFeeHandler, epoch uint32) uint64 ComputeGasUsedAndFeeBasedOnRefundValueInEpoch(tx data.TransactionWithFeeHandler, refundValue *big.Int, epoch uint32) (uint64, *big.Int) diff --git a/process/mock/multipleShardsCoordinatorMock.go b/process/mock/multipleShardsCoordinatorMock.go index bff3e16d090..27cb599cf92 100644 --- a/process/mock/multipleShardsCoordinatorMock.go +++ b/process/mock/multipleShardsCoordinatorMock.go @@ -6,6 +6,7 @@ import ( type multipleShardsCoordinatorMock struct { ComputeIdCalled func(address []byte) uint32 + SameShardCalled func(firstAddress, secondAddress []byte) bool noShards uint32 CurrentShard uint32 } @@ -44,7 +45,10 @@ func (scm *multipleShardsCoordinatorMock) SetSelfId(_ uint32) error { } // SameShard - -func (scm *multipleShardsCoordinatorMock) SameShard(_, _ []byte) bool { +func (scm *multipleShardsCoordinatorMock) SameShard(firstAddress, secondAddress []byte) bool { + if scm.SameShardCalled != nil { + return scm.SameShardCalled(firstAddress, secondAddress) + } return true } diff --git a/process/smartContract/process.go b/process/smartContract/process.go index 25031dcbf4a..8fbabd38df3 100644 --- a/process/smartContract/process.go +++ b/process/smartContract/process.go @@ -955,7 +955,7 @@ func (sc *scProcessor) doExecuteBuiltInFunction( return sc.finishSCExecution(make([]data.TransactionHandler, 0), txHash, tx, vmOutput, 0) } - _, txTypeOnDst := sc.txTypeHandler.ComputeTransactionType(tx) + _, txTypeOnDst, _ := sc.txTypeHandler.ComputeTransactionType(tx) builtInFuncGasUsed, err := sc.computeBuiltInFuncGasUsed(txTypeOnDst, vmInput.Function, vmInput.GasProvided, vmOutput.GasRemaining, check.IfNil(acntSnd)) log.LogIfError(err, "function", "ExecuteBuiltInFunction.computeBuiltInFuncGasUsed") @@ -1473,7 +1473,7 @@ func (sc *scProcessor) processIfErrorWithAddedLogs( Logs: processIfErrorLogs, }, 0) - txType, _ := sc.txTypeHandler.ComputeTransactionType(tx) + txType, _, _ := sc.txTypeHandler.ComputeTransactionType(tx) isCrossShardMoveBalance := txType == process.MoveBalance && check.IfNil(acntSnd) if isCrossShardMoveBalance && sc.enableEpochsHandler.IsFlagEnabled(common.SCDeployFlag) { // move balance was already consumed in sender shard @@ -2808,7 +2808,7 @@ func (sc *scProcessor) ProcessSmartContractResult(scr *smartContractResult.Smart gasLocked := sc.getGasLockedFromSCR(scr) - txType, _ := sc.txTypeHandler.ComputeTransactionType(scr) + txType, _, _ := sc.txTypeHandler.ComputeTransactionType(scr) switch txType { case process.MoveBalance: err = sc.processSimpleSCR(scr, txHash, dstAcc) diff --git a/process/smartContract/process_test.go b/process/smartContract/process_test.go index bc8caf169f3..b6c81113f45 100644 --- a/process/smartContract/process_test.go +++ b/process/smartContract/process_test.go @@ -3196,8 +3196,8 @@ func TestScProcessor_ProcessSmartContractResultDeploySCShouldError(t *testing.T) arguments.AccountsDB = accountsDB arguments.ShardCoordinator = shardCoordinator arguments.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCDeployment, process.SCDeployment + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCDeployment, process.SCDeployment, false }, } sc, err := NewSmartContractProcessor(arguments) @@ -3257,8 +3257,8 @@ func TestScProcessor_ProcessSmartContractResultExecuteSC(t *testing.T) { }, } arguments.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }, } sc, err := NewSmartContractProcessor(arguments) @@ -3320,8 +3320,8 @@ func TestScProcessor_ProcessSmartContractResultExecuteSCIfMetaAndBuiltIn(t *test }, } arguments.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.BuiltInFunctionCall, process.BuiltInFunctionCall + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.BuiltInFunctionCall, process.BuiltInFunctionCall, false }, } enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.SCDeployFlag) @@ -3394,8 +3394,8 @@ func TestScProcessor_ProcessRelayedSCRValueBackToRelayer(t *testing.T) { }, } arguments.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }, } sc, err := NewSmartContractProcessor(arguments) diff --git a/process/smartContract/processorV2/processV2.go b/process/smartContract/processorV2/processV2.go index 5f6c02b7d09..e0d88916a52 100644 --- a/process/smartContract/processorV2/processV2.go +++ b/process/smartContract/processorV2/processV2.go @@ -805,15 +805,17 @@ func (sc *scProcessor) deleteSCRsWithValueZeroGoingToMeta(scrs []data.Transactio } func (sc *scProcessor) saveAccounts(acntSnd, acntDst vmcommon.AccountHandler) error { - if !check.IfNil(acntSnd) { - err := sc.accounts.SaveAccount(acntSnd) - if err != nil { - return err - } + err := sc.saveAccount(acntSnd) + if err != nil { + return err } - if !check.IfNil(acntDst) { - err := sc.accounts.SaveAccount(acntDst) + return sc.saveAccount(acntDst) +} + +func (sc *scProcessor) saveAccount(account vmcommon.AccountHandler) error { + if !check.IfNil(account) { + err := sc.accounts.SaveAccount(account) if err != nil { return err } @@ -931,7 +933,7 @@ func (sc *scProcessor) doExecuteBuiltInFunctionWithoutFailureProcessing( return sc.finishSCExecution(make([]data.TransactionHandler, 0), txHash, tx, vmOutput, 0) } - _, txTypeOnDst := sc.txTypeHandler.ComputeTransactionType(tx) + _, txTypeOnDst, _ := sc.txTypeHandler.ComputeTransactionType(tx) builtInFuncGasUsed, err := sc.computeBuiltInFuncGasUsed(txTypeOnDst, vmInput.Function, vmInput.GasProvided, vmOutput.GasRemaining, check.IfNil(acntSnd)) log.LogIfError(err, "function", "ExecuteBuiltInFunction.computeBuiltInFuncGasUsed") @@ -1408,7 +1410,7 @@ func (sc *scProcessor) getOriginalTxHashIfIntraShardRelayedSCR( tx data.TransactionHandler, txHash []byte, ) []byte { - relayedSCR, isRelayed := isRelayedTx(tx) + relayedSCR, isRelayed := isRelayedSCR(tx) if !isRelayed { return txHash } @@ -1515,7 +1517,7 @@ func (sc *scProcessor) processIfErrorWithAddedLogs(acntSnd state.UserAccountHand log.Debug("scProcessor.ProcessIfError() save log", "error", ignorableError.Error()) } - txType, _ := sc.txTypeHandler.ComputeTransactionType(tx) + txType, _, _ := sc.txTypeHandler.ComputeTransactionType(tx) isCrossShardMoveBalance := txType == process.MoveBalance && check.IfNil(acntSnd) if isCrossShardMoveBalance { // move balance was already consumed in sender shard @@ -1578,7 +1580,7 @@ func (sc *scProcessor) processForRelayerWhenError( txHash []byte, returnMessage []byte, ) (*vmcommon.LogEntry, error) { - relayedSCR, isRelayed := isRelayedTx(originalTx) + relayedSCR, isRelayed := isRelayedSCR(originalTx) if !isRelayed { return nil, nil } @@ -1664,7 +1666,7 @@ func createNewLogFromSCRIfError(txHandler data.TransactionHandler) *vmcommon.Log } // transaction must be of type SCR and relayed address to be set with relayed value higher than 0 -func isRelayedTx(tx data.TransactionHandler) (*smartContractResult.SmartContractResult, bool) { +func isRelayedSCR(tx data.TransactionHandler) (*smartContractResult.SmartContractResult, bool) { relayedSCR, ok := tx.(*smartContractResult.SmartContractResult) if !ok { return nil, false @@ -1677,6 +1679,20 @@ func isRelayedTx(tx data.TransactionHandler) (*smartContractResult.SmartContract return nil, false } +func getRelayedValues(tx data.TransactionHandler) ([]byte, *big.Int) { + relayedTx, isRelayed := isRelayedSCR(tx) + if isRelayed { + return relayedTx.RelayerAddr, big.NewInt(0) + } + + if common.IsRelayedTxV3(tx) { + relayedTx := tx.(data.RelayedTransactionHandler) + return relayedTx.GetRelayerAddr(), big.NewInt(0) + } + + return nil, nil +} + // refunds the transaction values minus the relayed value to the sender account // in case of failed smart contract execution - gas is consumed, value is sent back func (sc *scProcessor) addBackTxValues( @@ -1686,7 +1702,7 @@ func (sc *scProcessor) addBackTxValues( ) error { valueForSnd := big.NewInt(0).Set(scrIfError.Value) - relayedSCR, isRelayed := isRelayedTx(originalTx) + relayedSCR, isRelayed := isRelayedSCR(originalTx) if isRelayed { valueForSnd.Sub(valueForSnd, relayedSCR.RelayedValue) if valueForSnd.Cmp(zero) < 0 { @@ -1921,14 +1937,25 @@ func (sc *scProcessor) processSCPayment(tx data.TransactionHandler, acntSnd stat return err } - cost := sc.economicsFee.ComputeTxFee(tx) - cost = cost.Add(cost, tx.GetValue()) + feePayer, err := sc.getFeePayer(tx, acntSnd) + if err != nil { + return err + } - if cost.Cmp(big.NewInt(0)) == 0 { - return nil + fee := sc.economicsFee.ComputeTxFee(tx) + if !check.IfNil(feePayer) { + err = feePayer.SubFromBalance(fee) + if err != nil { + return err + } + + err = sc.saveAccount(feePayer) + if err != nil { + return err + } } - err = acntSnd.SubFromBalance(cost) + err = acntSnd.SubFromBalance(tx.GetValue()) if err != nil { return err } @@ -1936,6 +1963,29 @@ func (sc *scProcessor) processSCPayment(tx data.TransactionHandler, acntSnd stat return nil } +func (sc *scProcessor) getFeePayer(tx data.TransactionHandler, acntSnd state.UserAccountHandler) (state.UserAccountHandler, error) { + if !common.IsRelayedTxV3(tx) { + return acntSnd, nil + } + + relayedTx, ok := tx.(data.RelayedTransactionHandler) + if !ok { + return acntSnd, nil + } + + relayerIsSender := bytes.Equal(relayedTx.GetRelayerAddr(), tx.GetSndAddr()) + if relayerIsSender { + return acntSnd, nil // do not load the same account twice + } + + account, err := sc.getAccountFromAddress(relayedTx.GetRelayerAddr()) + if err != nil { + return nil, err + } + + return account, nil +} + func (sc *scProcessor) processVMOutput( vmInput *vmcommon.VMInput, vmOutput *vmcommon.VMOutput, @@ -2257,11 +2307,7 @@ func createBaseSCR( result.CallType = vmData.DirectCall setOriginalTxHash(result, txHash, tx) - relayedTx, isRelayed := isRelayedTx(tx) - if isRelayed { - result.RelayedValue = big.NewInt(0) - result.RelayerAddr = relayedTx.RelayerAddr - } + result.RelayerAddr, result.RelayedValue = getRelayedValues(tx) return result } @@ -2299,11 +2345,8 @@ func (sc *scProcessor) createAsyncCallBackSCRFromVMOutput( OriginalSender: origScr.GetOriginalSender(), } setOriginalTxHash(scr, txHash, tx) - relayedTx, isRelayed := isRelayedTx(tx) - if isRelayed { - scr.RelayedValue = big.NewInt(0) - scr.RelayerAddr = relayedTx.RelayerAddr - } + + scr.RelayerAddr, scr.RelayedValue = getRelayedValues(tx) sc.addVMOutputResultsToSCR(vmOutput, scr) @@ -2568,29 +2611,7 @@ func (sc *scProcessor) createSCRForSenderAndRelayer( rcvAddress = tx.GetRcvAddr() } - var refundGasToRelayerSCR *smartContractResult.SmartContractResult - relayedSCR, isRelayed := isRelayedTx(tx) - shouldRefundGasToRelayerSCR := isRelayed && callType != vmData.AsynchronousCall && gasRefund.Cmp(zero) > 0 - if shouldRefundGasToRelayerSCR { - senderForRelayerRefund := tx.GetRcvAddr() - if !sc.isSelfShard(tx.GetRcvAddr()) { - senderForRelayerRefund = tx.GetSndAddr() - } - - refundGasToRelayerSCR = &smartContractResult.SmartContractResult{ - Nonce: relayedSCR.Nonce + 1, - Value: big.NewInt(0).Set(gasRefund), - RcvAddr: relayedSCR.RelayerAddr, - SndAddr: senderForRelayerRefund, - PrevTxHash: txHash, - OriginalTxHash: relayedSCR.OriginalTxHash, - GasPrice: tx.GetGasPrice(), - CallType: vmData.DirectCall, - ReturnMessage: []byte("gas refund for relayer"), - OriginalSender: relayedSCR.OriginalSender, - } - gasRemaining = 0 - } + refundGasToRelayerSCR := sc.createRefundGasToRelayerSCRIfNeeded(tx, txHash, callType, gasRefund) scTx := &smartContractResult.SmartContractResult{} scTx.Value = big.NewInt(0).Set(storageFreeRefund) @@ -2621,6 +2642,71 @@ func (sc *scProcessor) createSCRForSenderAndRelayer( return scTx, refundGasToRelayerSCR } +func (sc *scProcessor) createRefundGasToRelayerSCRIfNeeded( + tx data.TransactionHandler, + txHash []byte, + callType vmData.CallType, + gasRefund *big.Int, +) *smartContractResult.SmartContractResult { + relayedSCR, isRelayed := isRelayedSCR(tx) + shouldRefundGasToRelayerSCR := isRelayed && callType != vmData.AsynchronousCall && gasRefund.Cmp(zero) > 0 + if shouldRefundGasToRelayerSCR { + return sc.createRefundGasToRelayerSCR( + tx, + relayedSCR.Nonce+1, + relayedSCR.RelayerAddr, + relayedSCR.OriginalSender, + txHash, + relayedSCR.OriginalTxHash, + gasRefund) + } + + isRelayedV3 := common.IsRelayedTxV3(tx) + shouldRefundGasToRelayerSCR = isRelayedV3 && callType != vmData.AsynchronousCall && gasRefund.Cmp(zero) > 0 + if shouldRefundGasToRelayerSCR { + relayedTx := tx.(data.RelayedTransactionHandler) + + return sc.createRefundGasToRelayerSCR( + tx, + tx.GetNonce()+1, + relayedTx.GetRelayerAddr(), + tx.GetSndAddr(), + txHash, + txHash, + gasRefund) + } + + return nil +} + +func (sc *scProcessor) createRefundGasToRelayerSCR( + tx data.TransactionHandler, + nonce uint64, + relayerAddr []byte, + originalSender []byte, + prevTxHash []byte, + originalTxHash []byte, + refund *big.Int, +) *smartContractResult.SmartContractResult { + senderForRelayerRefund := tx.GetRcvAddr() + if !sc.isSelfShard(tx.GetRcvAddr()) { + senderForRelayerRefund = tx.GetSndAddr() + } + + return &smartContractResult.SmartContractResult{ + Nonce: nonce, + Value: big.NewInt(0).Set(refund), + RcvAddr: relayerAddr, + SndAddr: senderForRelayerRefund, + PrevTxHash: prevTxHash, + OriginalTxHash: originalTxHash, + GasPrice: tx.GetGasPrice(), + CallType: vmData.DirectCall, + ReturnMessage: []byte("gas refund for relayer"), + OriginalSender: originalSender, + } +} + func addReturnDataToSCR(vmOutput *vmcommon.VMOutput, scTx *smartContractResult.SmartContractResult) { for _, retData := range vmOutput.ReturnData { scTx.Data = append(scTx.Data, []byte("@"+hex.EncodeToString(retData))...) @@ -2720,7 +2806,7 @@ func (sc *scProcessor) ProcessSmartContractResult(scr *smartContractResult.Smart gasLocked := sc.getGasLockedFromSCR(scr) - txType, _ := sc.txTypeHandler.ComputeTransactionType(scr) + txType, _, _ := sc.txTypeHandler.ComputeTransactionType(scr) switch txType { case process.MoveBalance: err = sc.processSimpleSCR(scr, txHash, dstAcc) diff --git a/process/smartContract/processorV2/process_test.go b/process/smartContract/processorV2/process_test.go index 905c18a033d..638e096005e 100644 --- a/process/smartContract/processorV2/process_test.go +++ b/process/smartContract/processorV2/process_test.go @@ -3129,8 +3129,8 @@ func TestScProcessor_ProcessSmartContractResultDeploySCShouldError(t *testing.T) arguments.AccountsDB = accountsDB arguments.ShardCoordinator = shardCoordinator arguments.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCDeployment, process.SCDeployment + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCDeployment, process.SCDeployment, false }, } sc, err := NewSmartContractProcessorV2(arguments) @@ -3190,8 +3190,8 @@ func TestScProcessor_ProcessSmartContractResultExecuteSC(t *testing.T) { }, } arguments.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }, } sc, err := NewSmartContractProcessorV2(arguments) @@ -3253,8 +3253,8 @@ func TestScProcessor_ProcessSmartContractResultExecuteSCIfMetaAndBuiltIn(t *test }, } arguments.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.BuiltInFunctionCall, process.BuiltInFunctionCall + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.BuiltInFunctionCall, process.BuiltInFunctionCall, false }, } enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub() @@ -3327,8 +3327,8 @@ func TestScProcessor_ProcessRelayedSCRValueBackToRelayer(t *testing.T) { }, } arguments.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }, } wasSaveLogsCalled := false diff --git a/process/smartContract/processorV2/vmInputV2.go b/process/smartContract/processorV2/vmInputV2.go index 06c4c3f0ad2..81dd1f9360c 100644 --- a/process/smartContract/processorV2/vmInputV2.go +++ b/process/smartContract/processorV2/vmInputV2.go @@ -40,10 +40,7 @@ func (sc *scProcessor) initializeVMInputFromTx(vmInput *vmcommon.VMInput, tx dat vmInput.CallValue = new(big.Int).Set(tx.GetValue()) vmInput.GasPrice = tx.GetGasPrice() - relayedTx, isRelayed := isRelayedTx(tx) - if isRelayed { - vmInput.RelayerAddr = relayedTx.RelayerAddr - } + vmInput.RelayerAddr, _ = getRelayedValues(tx) vmInput.GasProvided, err = sc.prepareGasProvided(tx) if err != nil { diff --git a/process/transaction/baseProcess.go b/process/transaction/baseProcess.go index 12f29b05bb6..0433f8a0f50 100644 --- a/process/transaction/baseProcess.go +++ b/process/transaction/baseProcess.go @@ -119,6 +119,16 @@ func (txProc *baseTxProcessor) checkTxValues( acntSnd, acntDst state.UserAccountHandler, isUserTxOfRelayed bool, ) error { + + if common.IsRelayedTxV3(tx) { + relayerAccount, err := txProc.getAccountFromAddress(tx.RelayerAddr) + if err != nil { + return err + } + + return txProc.checkUserTxOfRelayedV3Values(tx, acntSnd, acntDst, relayerAccount) + } + err := txProc.VerifyGuardian(tx, acntSnd) if err != nil { return err @@ -174,6 +184,86 @@ func (txProc *baseTxProcessor) checkTxValues( return nil } +func (txProc *baseTxProcessor) checkUserTxOfRelayedV3Values( + tx *transaction.Transaction, + senderAccount state.UserAccountHandler, + destinationAccount state.UserAccountHandler, + relayerAccount state.UserAccountHandler, +) error { + err := txProc.VerifyGuardian(tx, senderAccount) + if err != nil { + return err + } + err = txProc.checkUserNames(tx, senderAccount, destinationAccount) + if err != nil { + return err + } + if check.IfNil(senderAccount) { + return nil + } + if senderAccount.GetNonce() < tx.Nonce { + return process.ErrHigherNonceInTransaction + } + if senderAccount.GetNonce() > tx.Nonce { + return process.ErrLowerNonceInTransaction + } + err = txProc.economicsFee.CheckValidityTxValues(tx) + if err != nil { + return err + } + + if tx.GasLimit < txProc.economicsFee.ComputeGasLimit(tx) { + return process.ErrNotEnoughGas + } + + if check.IfNil(relayerAccount) { + return nil + } + + txFee := txProc.economicsFee.ComputeTxFee(tx) + + if relayerAccount.GetBalance().Cmp(txFee) < 0 { + return fmt.Errorf("%w, has: %s, wanted: %s", + process.ErrInsufficientFee, + relayerAccount.GetBalance().String(), + txFee.String(), + ) + } + + if senderAccount.GetBalance().Cmp(tx.Value) < 0 { + return process.ErrInsufficientFunds + } + + return nil +} + +func (txProc *baseTxProcessor) getFeePayer( + tx *transaction.Transaction, + senderAccount state.UserAccountHandler, + destinationAccount state.UserAccountHandler, +) (state.UserAccountHandler, bool, error) { + if !common.IsRelayedTxV3(tx) { + return senderAccount, false, nil + } + + relayerIsSender := bytes.Equal(tx.RelayerAddr, tx.SndAddr) + if relayerIsSender { + return senderAccount, true, nil // do not load the same account twice + } + + relayerIsDestination := bytes.Equal(tx.RelayerAddr, tx.RcvAddr) + if relayerIsDestination { + return destinationAccount, true, nil // do not load the same account twice + } + + acntRelayer, err := txProc.getAccountFromAddress(tx.RelayerAddr) + if err != nil { + return nil, true, err + } + + return acntRelayer, true, nil +} + func (txProc *baseTxProcessor) computeInnerTxFee(tx *transaction.Transaction) *big.Int { if txProc.enableEpochsHandler.IsFlagEnabled(common.FixRelayedBaseCostFlag) { return txProc.computeInnerTxFeeAfterBaseCostFix(tx) @@ -183,7 +273,7 @@ func (txProc *baseTxProcessor) computeInnerTxFee(tx *transaction.Transaction) *b } func (txProc *baseTxProcessor) computeInnerTxFeeAfterBaseCostFix(tx *transaction.Transaction) *big.Int { - _, dstShardTxType := txProc.txTypeHandler.ComputeTransactionType(tx) + _, dstShardTxType, _ := txProc.txTypeHandler.ComputeTransactionType(tx) if dstShardTxType == process.MoveBalance { return txProc.economicsFee.ComputeMoveBalanceFee(tx) } @@ -288,6 +378,7 @@ func (txProc *baseTxProcessor) checkGuardedAccountUnguardedTxPermission(tx *tran return nil } +// VerifyGuardian does the guardian verification func (txProc *baseTxProcessor) VerifyGuardian(tx *transaction.Transaction, account state.UserAccountHandler) error { if check.IfNil(account) { return nil diff --git a/process/transaction/export_test.go b/process/transaction/export_test.go index 940633ca6b4..c6ff2791b45 100644 --- a/process/transaction/export_test.go +++ b/process/transaction/export_test.go @@ -6,6 +6,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" "github.com/multiversx/mx-chain-core-go/data/smartContractResult" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/state" vmcommon "github.com/multiversx/mx-chain-vm-common-go" @@ -55,6 +56,7 @@ func (txProc *txProcessor) ProcessUserTx( userTx *transaction.Transaction, relayedTxValue *big.Int, relayedNonce uint64, + relayerAddr []byte, originalTxHash []byte, ) (vmcommon.ReturnCode, error) { return txProc.processUserTx( @@ -62,6 +64,7 @@ func (txProc *txProcessor) ProcessUserTx( userTx, relayedTxValue, relayedNonce, + relayerAddr, originalTxHash) } @@ -100,6 +103,11 @@ func (inTx *InterceptedTransaction) CheckMaxGasPrice() error { return inTx.checkMaxGasPrice() } +// SetEnableEpochsHandler sets the internal enable epochs handler +func (inTx *InterceptedTransaction) SetEnableEpochsHandler(handler common.EnableEpochsHandler) { + inTx.enableEpochsHandler = handler +} + // ShouldIncreaseNonce calls the un-exported method shouldIncreaseNonce func (txProc *txProcessor) ShouldIncreaseNonce(executionErr error) bool { return txProc.shouldIncreaseNonce(executionErr) diff --git a/process/transaction/interceptedTransaction.go b/process/transaction/interceptedTransaction.go index 75b9a65ae7c..53722cad96f 100644 --- a/process/transaction/interceptedTransaction.go +++ b/process/transaction/interceptedTransaction.go @@ -13,6 +13,7 @@ import ( "github.com/multiversx/mx-chain-core-go/hashing" "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/sharding" logger "github.com/multiversx/mx-chain-logger-go" @@ -42,6 +43,7 @@ type InterceptedTransaction struct { sndShard uint32 isForCurrentShard bool enableSignedTxWithHash bool + enableEpochsHandler common.EnableEpochsHandler } // NewInterceptedTransaction returns a new instance of InterceptedTransaction @@ -61,6 +63,7 @@ func NewInterceptedTransaction( enableSignedTxWithHash bool, txSignHasher hashing.Hasher, txVersionChecker process.TxVersionCheckerHandler, + enableEpochsHandler common.EnableEpochsHandler, ) (*InterceptedTransaction, error) { if txBuff == nil { @@ -105,6 +108,9 @@ func NewInterceptedTransaction( if check.IfNil(txVersionChecker) { return nil, process.ErrNilTransactionVersionChecker } + if check.IfNil(enableEpochsHandler) { + return nil, process.ErrNilEnableEpochsHandler + } tx, err := createTx(protoMarshalizer, txBuff) if err != nil { @@ -127,6 +133,7 @@ func NewInterceptedTransaction( enableSignedTxWithHash: enableSignedTxWithHash, txVersionChecker: txVersionChecker, txSignHasher: txSignHasher, + enableEpochsHandler: enableEpochsHandler, } err = inTx.processFields(txBuff) @@ -189,6 +196,11 @@ func (inTx *InterceptedTransaction) CheckValidity() error { return err } + err = inTx.verifyIfRelayedTxV3(inTx.tx) + if err != nil { + return err + } + err = inTx.verifyIfRelayedTx(inTx.tx) if err != nil { return err @@ -205,8 +217,12 @@ func (inTx *InterceptedTransaction) CheckValidity() error { return nil } -func (inTx *InterceptedTransaction) checkRecursiveRelayed(userTxData []byte) error { - funcName, _, err := inTx.argsParser.ParseCallData(string(userTxData)) +func (inTx *InterceptedTransaction) checkRecursiveRelayed(userTx *transaction.Transaction) error { + if common.IsValidRelayedTxV3(userTx) { + return process.ErrRecursiveRelayedTxIsNotAllowed + } + + funcName, _, err := inTx.argsParser.ParseCallData(string(userTx.Data)) if err != nil { return nil } @@ -223,6 +239,47 @@ func isRelayedTx(funcName string) bool { core.RelayedTransactionV2 == funcName } +func (inTx *InterceptedTransaction) verifyIfRelayedTxV3(tx *transaction.Transaction) error { + if !common.IsRelayedTxV3(tx) { + return nil + } + + if !inTx.enableEpochsHandler.IsFlagEnabled(common.RelayedTransactionsV3Flag) { + return process.ErrRelayedTxV3Disabled + } + + if !common.IsValidRelayedTxV3(tx) { + return process.ErrInvalidRelayedTxV3 + } + + err := inTx.integrity(tx) + if err != nil { + return err + } + + if !inTx.coordinator.SameShard(tx.RelayerAddr, tx.SndAddr) { + return process.ErrShardIdMissmatch + } + + if bytes.Equal(tx.RelayerAddr, tx.GuardianAddr) { + return process.ErrRelayedByGuardianNotAllowed + } + + userTx := *tx + userTx.RelayerSignature = make([]byte, 0) // temporary removed signature for recursive relayed checks + err = inTx.verifyUserTx(&userTx) + if err != nil { + return fmt.Errorf("inner transaction: %w", err) + } + + err = inTx.verifyRelayerSig(tx) + if err != nil { + return err + } + + return nil +} + func (inTx *InterceptedTransaction) verifyIfRelayedTxV2(tx *transaction.Transaction) error { funcName, userTxArgs, err := inTx.argsParser.ParseCallData(string(tx.Data)) if err != nil { @@ -272,7 +329,7 @@ func (inTx *InterceptedTransaction) verifyIfRelayedTx(tx *transaction.Transactio func (inTx *InterceptedTransaction) verifyUserTx(userTx *transaction.Transaction) error { // recursive relayed transactions are not allowed - err := inTx.checkRecursiveRelayed(userTx.Data) + err := inTx.checkRecursiveRelayed(userTx) if err != nil { return fmt.Errorf("inner transaction: %w", err) } @@ -370,6 +427,21 @@ func (inTx *InterceptedTransaction) verifySig(tx *transaction.Transaction) error return inTx.singleSigner.Verify(senderPubKey, txMessageForSigVerification, tx.Signature) } +// verifyRelayerSig checks if the tx is correctly signed by relayer +func (inTx *InterceptedTransaction) verifyRelayerSig(tx *transaction.Transaction) error { + txMessageForSigVerification, err := inTx.getTxMessageForGivenTx(tx) + if err != nil { + return err + } + + relayerPubKey, err := inTx.keyGen.PublicKeyFromByteArray(tx.RelayerAddr) + if err != nil { + return err + } + + return inTx.singleSigner.Verify(relayerPubKey, txMessageForSigVerification, tx.RelayerSignature) +} + // VerifyGuardianSig verifies if the guardian signature is valid func (inTx *InterceptedTransaction) VerifyGuardianSig(tx *transaction.Transaction) error { txMessageForSigVerification, err := inTx.getTxMessageForGivenTx(tx) diff --git a/process/transaction/interceptedTransaction_test.go b/process/transaction/interceptedTransaction_test.go index f35ce467d13..7459e586241 100644 --- a/process/transaction/interceptedTransaction_test.go +++ b/process/transaction/interceptedTransaction_test.go @@ -15,6 +15,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data" dataTransaction "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-crypto-go" + "github.com/multiversx/mx-chain-go/common" "github.com/multiversx/mx-chain-go/process" "github.com/multiversx/mx-chain-go/process/interceptors" "github.com/multiversx/mx-chain-go/process/mock" @@ -22,6 +23,7 @@ import ( "github.com/multiversx/mx-chain-go/process/transaction" "github.com/multiversx/mx-chain-go/testscommon" "github.com/multiversx/mx-chain-go/testscommon/economicsmocks" + "github.com/multiversx/mx-chain-go/testscommon/enableEpochsHandlerMock" "github.com/multiversx/mx-chain-go/testscommon/hashingMocks" "github.com/multiversx/mx-chain-go/testscommon/marshallerMock" logger "github.com/multiversx/mx-chain-logger-go" @@ -36,6 +38,7 @@ var senderShard = uint32(2) var recvShard = uint32(3) var senderAddress = []byte("12345678901234567890123456789012") var recvAddress = []byte("23456789012345678901234567890123") +var relayerAddress = []byte("34567890123456789012345678901234") var sigBad = []byte("bad-signature") var sigOk = []byte("signature") @@ -114,6 +117,7 @@ func createInterceptedTxWithTxFeeHandlerAndVersionChecker(tx *dataTransaction.Tr false, &hashingMocks.HasherMock{}, txVerChecker, + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) } @@ -157,6 +161,7 @@ func createInterceptedTxFromPlainTx(tx *dataTransaction.Transaction, txFeeHandle false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(minTxVersion), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) } @@ -170,7 +175,8 @@ func createInterceptedTxFromPlainTxWithArgParser(tx *dataTransaction.Transaction shardCoordinator := mock.NewMultipleShardsCoordinatorMock() shardCoordinator.CurrentShard = 0 shardCoordinator.ComputeIdCalled = func(address []byte) uint32 { - if bytes.Equal(address, senderAddress) { + if bytes.Equal(address, senderAddress) || + bytes.Equal(address, relayerAddress) { return senderShard } if bytes.Equal(address, recvAddress) { @@ -179,6 +185,10 @@ func createInterceptedTxFromPlainTxWithArgParser(tx *dataTransaction.Transaction return shardCoordinator.CurrentShard } + shardCoordinator.SameShardCalled = func(firstAddress, secondAddress []byte) bool { + return string(firstAddress) == string(relayerAddress) && + string(secondAddress) == string(senderAddress) + } return transaction.NewInterceptedTransaction( txBuff, @@ -200,6 +210,7 @@ func createInterceptedTxFromPlainTxWithArgParser(tx *dataTransaction.Transaction false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(tx.Version), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.RelayedTransactionsV3Flag), ) } @@ -224,6 +235,7 @@ func TestNewInterceptedTransaction_NilBufferShouldErr(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -249,6 +261,7 @@ func TestNewInterceptedTransaction_NilArgsParser(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -274,6 +287,7 @@ func TestNewInterceptedTransaction_NilVersionChecker(t *testing.T) { false, &hashingMocks.HasherMock{}, nil, + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -299,6 +313,7 @@ func TestNewInterceptedTransaction_NilMarshalizerShouldErr(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -324,6 +339,7 @@ func TestNewInterceptedTransaction_NilSignMarshalizerShouldErr(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -349,6 +365,7 @@ func TestNewInterceptedTransaction_NilHasherShouldErr(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -374,6 +391,7 @@ func TestNewInterceptedTransaction_NilKeyGenShouldErr(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -399,6 +417,7 @@ func TestNewInterceptedTransaction_NilSignerShouldErr(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -424,6 +443,7 @@ func TestNewInterceptedTransaction_NilPubkeyConverterShouldErr(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -449,6 +469,7 @@ func TestNewInterceptedTransaction_NilCoordinatorShouldErr(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -474,6 +495,7 @@ func TestNewInterceptedTransaction_NilFeeHandlerShouldErr(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -499,6 +521,7 @@ func TestNewInterceptedTransaction_NilWhiteListerVerifiedTxsShouldErr(t *testing false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -524,6 +547,7 @@ func TestNewInterceptedTransaction_InvalidChainIDShouldErr(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -549,12 +573,39 @@ func TestNewInterceptedTransaction_NilTxSignHasherShouldErr(t *testing.T) { false, nil, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) assert.Equal(t, process.ErrNilHasher, err) } +func TestNewInterceptedTransaction_NilEnableEpochsHandlerShouldErr(t *testing.T) { + t.Parallel() + + txi, err := transaction.NewInterceptedTransaction( + make([]byte, 0), + &mock.MarshalizerMock{}, + &mock.MarshalizerMock{}, + &hashingMocks.HasherMock{}, + &mock.SingleSignKeyGenMock{}, + &mock.SignerMock{}, + createMockPubKeyConverter(), + mock.NewOneShardCoordinatorMock(), + &economicsmocks.EconomicsHandlerStub{}, + &testscommon.WhiteListHandlerStub{}, + &testscommon.ArgumentParserMock{}, + []byte("chainID"), + false, + &hashingMocks.HasherMock{}, + versioning.NewTxVersionChecker(1), + nil, + ) + + assert.Nil(t, txi) + assert.Equal(t, process.ErrNilEnableEpochsHandler, err) +} + func TestNewInterceptedTransaction_UnmarshalingTxFailsShouldErr(t *testing.T) { t.Parallel() @@ -580,6 +631,7 @@ func TestNewInterceptedTransaction_UnmarshalingTxFailsShouldErr(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(1), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, txi) @@ -1050,6 +1102,7 @@ func TestInterceptedTransaction_CheckValiditySignedWithHashButNotEnabled(t *test false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(minTxVersion), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) err := txi.CheckValidity() @@ -1110,6 +1163,7 @@ func TestInterceptedTransaction_CheckValiditySignedWithHashShouldWork(t *testing true, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(minTxVersion), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) err := txi.CheckValidity() @@ -1195,6 +1249,7 @@ func TestInterceptedTransaction_ScTxDeployRecvShardIdShouldBeSendersShardId(t *t false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(minTxVersion), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Nil(t, err) @@ -1334,6 +1389,7 @@ func TestInterceptedTransaction_CheckValiditySecondTimeDoesNotVerifySig(t *testi false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(minTxVersion), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) require.Nil(t, err) @@ -1500,6 +1556,82 @@ func TestInterceptedTransaction_CheckValidityOfRelayedTxV2(t *testing.T) { assert.Nil(t, err) } +func TestInterceptedTransaction_CheckValidityOfRelayedTxV3(t *testing.T) { + t.Parallel() + + minTxVersion := uint32(1) + chainID := []byte("chain") + tx := &dataTransaction.Transaction{ + Nonce: 1, + Value: big.NewInt(2), + GasLimit: 3, + GasPrice: 4, + RcvAddr: recvAddress, + SndAddr: senderAddress, + ChainID: chainID, + Version: minTxVersion, + RelayerAddr: relayerAddress, + } + txi, _ := createInterceptedTxFromPlainTxWithArgParser(tx) + err := txi.CheckValidity() + assert.Equal(t, err, process.ErrNilSignature) + + tx.Signature = sigOk + tx.RelayerSignature = sigOk + + // flag not active should error + txi, _ = createInterceptedTxFromPlainTxWithArgParser(tx) + txi.SetEnableEpochsHandler(enableEpochsHandlerMock.NewEnableEpochsHandlerStub()) + err = txi.CheckValidity() + assert.Equal(t, process.ErrRelayedTxV3Disabled, err) + + // invalid relayed v3 should error + tx.RelayerSignature = nil + txi, _ = createInterceptedTxFromPlainTxWithArgParser(tx) + err = txi.CheckValidity() + assert.Equal(t, process.ErrInvalidRelayedTxV3, err) + + // sender in different shard than relayer should fail + tx.RelayerSignature = sigOk + tx.RelayerAddr = bytes.Repeat([]byte("a"), len(relayerAddress)) + txi, _ = createInterceptedTxFromPlainTxWithArgParser(tx) + err = txi.CheckValidity() + assert.Equal(t, process.ErrShardIdMissmatch, err) + + // relayer == guardian should fail + tx.Version = 2 + tx.Options = 2 + tx.RelayerAddr = relayerAddress + tx.GuardianAddr = tx.RelayerAddr + tx.GuardianSignature = sigOk + txi, _ = createInterceptedTxFromPlainTxWithArgParser(tx) + err = txi.CheckValidity() + assert.Equal(t, process.ErrRelayedByGuardianNotAllowed, err) + + // recursive relayed txs + tx.Version = minTxVersion + tx.Options = 0 + tx.GuardianAddr = nil + tx.GuardianSignature = nil + tx.Data = []byte(core.RelayedTransactionV2 + "@" + hex.EncodeToString(recvAddress) + "@" + hex.EncodeToString(big.NewInt(0).SetUint64(0).Bytes()) + "@" + hex.EncodeToString([]byte("some method")) + "@" + hex.EncodeToString(sigOk)) + txi, _ = createInterceptedTxFromPlainTxWithArgParser(tx) + err = txi.CheckValidity() + assert.True(t, errors.Is(err, process.ErrRecursiveRelayedTxIsNotAllowed)) + + // invalid relayer signature + tx.Data = nil + tx.RelayerSignature = bytes.Repeat([]byte("a"), len(sigOk)) // same length but invalid relayer sig + txi, _ = createInterceptedTxFromPlainTxWithArgParser(tx) + err = txi.CheckValidity() + assert.NotNil(t, err) + + // should work + tx.RelayerSignature = sigOk + txi, _ = createInterceptedTxFromPlainTxWithArgParser(tx) + err = txi.CheckValidity() + assert.Nil(t, err) +} + // ------- IsInterfaceNil func TestInterceptedTransaction_IsInterfaceNil(t *testing.T) { t.Parallel() @@ -1629,6 +1761,7 @@ func TestInterceptedTransaction_Fee(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(0), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) assert.Equal(t, big.NewInt(0), txin.Fee()) @@ -1672,6 +1805,7 @@ func TestInterceptedTransaction_String(t *testing.T) { false, &hashingMocks.HasherMock{}, versioning.NewTxVersionChecker(0), + enableEpochsHandlerMock.NewEnableEpochsHandlerStub(), ) expectedFormat := fmt.Sprintf( diff --git a/process/transaction/metaProcess.go b/process/transaction/metaProcess.go index 13d6fd4715b..090d4ad89e4 100644 --- a/process/transaction/metaProcess.go +++ b/process/transaction/metaProcess.go @@ -135,7 +135,7 @@ func (txProc *metaTxProcessor) ProcessTransaction(tx *transaction.Transaction) ( return 0, err } - txType, _ := txProc.txTypeHandler.ComputeTransactionType(tx) + txType, _, _ := txProc.txTypeHandler.ComputeTransactionType(tx) switch txType { case process.SCDeployment: return txProc.processSCDeployment(tx, tx.SndAddr) diff --git a/process/transaction/metaProcess_test.go b/process/transaction/metaProcess_test.go index eaaa1382d2e..42a04260077 100644 --- a/process/transaction/metaProcess_test.go +++ b/process/transaction/metaProcess_test.go @@ -277,8 +277,8 @@ func TestMetaTxProcessor_ProcessTransactionScTxShouldWork(t *testing.T) { args.Accounts = adb args.ScProcessor = scProcessorMock args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }, } txProc, _ := txproc.NewMetaTxProcessor(args) @@ -323,8 +323,8 @@ func TestMetaTxProcessor_ProcessTransactionScTxShouldReturnErrWhenExecutionFails args.Accounts = adb args.ScProcessor = scProcessorMock args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }, } txProc, _ := txproc.NewMetaTxProcessor(args) @@ -439,8 +439,8 @@ func TestMetaTxProcessor_ProcessTransactionBuiltInCallTxShouldWork(t *testing.T) args.Accounts = adb args.ScProcessor = scProcessorMock args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.BuiltInFunctionCall, process.BuiltInFunctionCall + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.BuiltInFunctionCall, process.BuiltInFunctionCall, false }, } enableEpochsHandlerStub := enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.ESDTFlag) diff --git a/process/transaction/shardProcess.go b/process/transaction/shardProcess.go index 51910b537e2..a9b4d4d68b8 100644 --- a/process/transaction/shardProcess.go +++ b/process/transaction/shardProcess.go @@ -196,18 +196,18 @@ func (txProc *txProcessor) ProcessTransaction(tx *transaction.Transaction) (vmco txProc.pubkeyConv, ) - txType, dstShardTxType := txProc.txTypeHandler.ComputeTransactionType(tx) + txType, dstShardTxType, isRelayedV3 := txProc.txTypeHandler.ComputeTransactionType(tx) err = txProc.checkTxValues(tx, acntSnd, acntDst, false) if err != nil { if errors.Is(err, process.ErrInsufficientFunds) { - receiptErr := txProc.executingFailedTransaction(tx, acntSnd, err) + receiptErr := txProc.executingFailedTransaction(tx, acntSnd, acntDst, err) if receiptErr != nil { return 0, receiptErr } } if errors.Is(err, process.ErrUserNameDoesNotMatch) && txProc.enableEpochsHandler.IsFlagEnabled(common.RelayedTransactionsFlag) { - receiptErr := txProc.executingFailedTransaction(tx, acntSnd, err) + receiptErr := txProc.executingFailedTransaction(tx, acntSnd, acntDst, err) if receiptErr != nil { return vmcommon.UserError, receiptErr } @@ -223,6 +223,13 @@ func (txProc *txProcessor) ProcessTransaction(tx *transaction.Transaction) (vmco return vmcommon.UserError, err } + if isRelayedV3 { + err = txProc.verifyRelayedTxV3(tx) + if err != nil { + return vmcommon.UserError, err + } + } + switch txType { case process.MoveBalance: err = txProc.processMoveBalance(tx, acntSnd, acntDst, dstShardTxType, nil, false) @@ -242,7 +249,7 @@ func (txProc *txProcessor) ProcessTransaction(tx *transaction.Transaction) (vmco return txProc.processRelayedTxV2(tx, acntSnd, acntDst) } - return vmcommon.UserError, txProc.executingFailedTransaction(tx, acntSnd, process.ErrWrongTransaction) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, acntSnd, acntDst, process.ErrWrongTransaction) } func (txProc *txProcessor) executeAfterFailedMoveBalanceTransaction( @@ -289,14 +296,20 @@ func (txProc *txProcessor) executeAfterFailedMoveBalanceTransaction( func (txProc *txProcessor) executingFailedTransaction( tx *transaction.Transaction, acntSnd state.UserAccountHandler, + acntDst state.UserAccountHandler, txError error, ) error { if check.IfNil(acntSnd) { return nil } + feePayer, isRelayedV3, err := txProc.getFeePayer(tx, acntSnd, acntDst) + if err != nil { + return err + } + txFee := txProc.economicsFee.ComputeTxFee(tx) - err := acntSnd.SubFromBalance(txFee) + err = feePayer.SubFromBalance(txFee) if err != nil { return err } @@ -328,24 +341,32 @@ func (txProc *txProcessor) executingFailedTransaction( txProc.txFeeHandler.ProcessTransactionFee(txFee, big.NewInt(0), txHash) - err = txProc.accounts.SaveAccount(acntSnd) + err = txProc.accounts.SaveAccount(feePayer) if err != nil { return err } + if isRelayedV3 { + // for relayed v3, the nonce was increased for sender, but fees consumed from relayer + err = txProc.accounts.SaveAccount(acntSnd) + if err != nil { + return err + } + } + return process.ErrFailedTransaction } func (txProc *txProcessor) createReceiptWithReturnedGas( txHash []byte, tx *transaction.Transaction, - acntSnd state.UserAccountHandler, + feePayer state.UserAccountHandler, moveBalanceCost *big.Int, totalProvided *big.Int, destShardTxType process.TransactionType, isUserTxOfRelayed bool, ) error { - if check.IfNil(acntSnd) || isUserTxOfRelayed { + if check.IfNil(feePayer) || isUserTxOfRelayed { return nil } shouldCreateReceiptBackwardCompatible := !txProc.enableEpochsHandler.IsFlagEnabled(common.MetaProtectionFlag) && core.IsSmartContractAddress(tx.RcvAddr) @@ -362,33 +383,33 @@ func (txProc *txProcessor) createReceiptWithReturnedGas( rpt := &receipt.Receipt{ Value: big.NewInt(0).Set(refundValue), - SndAddr: tx.SndAddr, + SndAddr: feePayer.AddressBytes(), Data: []byte(RefundGasMessage), TxHash: txHash, } - err := txProc.receiptForwarder.AddIntermediateTransactions([]data.TransactionHandler{rpt}, txHash) - if err != nil { - return err - } - - return nil + return txProc.receiptForwarder.AddIntermediateTransactions([]data.TransactionHandler{rpt}, txHash) } func (txProc *txProcessor) processTxFee( tx *transaction.Transaction, - acntSnd, acntDst state.UserAccountHandler, + feePayer, acntDst state.UserAccountHandler, dstShardTxType process.TransactionType, isUserTxOfRelayed bool, ) (*big.Int, *big.Int, error) { - if check.IfNil(acntSnd) { + if check.IfNil(feePayer) { return big.NewInt(0), big.NewInt(0), nil } if isUserTxOfRelayed { totalCost := txProc.computeInnerTxFee(tx) - err := acntSnd.SubFromBalance(totalCost) + err := feePayer.SubFromBalance(totalCost) + if err != nil { + return nil, nil, err + } + + err = txProc.accounts.SaveAccount(feePayer) if err != nil { return nil, nil, err } @@ -417,26 +438,28 @@ func (txProc *txProcessor) processTxFee( if dstShardTxType != process.MoveBalance || (!txProc.enableEpochsHandler.IsFlagEnabled(common.MetaProtectionFlag) && isCrossShardSCCall) { - err := acntSnd.SubFromBalance(totalCost) + err := feePayer.SubFromBalance(totalCost) if err != nil { return nil, nil, err } } else { - err := acntSnd.SubFromBalance(moveBalanceFee) + err := feePayer.SubFromBalance(moveBalanceFee) if err != nil { return nil, nil, err } } + err := txProc.accounts.SaveAccount(feePayer) + if err != nil { + return nil, nil, err + } + return moveBalanceFee, totalCost, nil } -func (txProc *txProcessor) checkIfValidTxToMetaChain( - tx *transaction.Transaction, - adrDst []byte, -) error { +func (txProc *txProcessor) checkIfValidTxToMetaChain(tx *transaction.Transaction) error { - destShardId := txProc.shardCoordinator.ComputeId(adrDst) + destShardId := txProc.shardCoordinator.ComputeId(tx.RcvAddr) if destShardId != core.MetachainShardId { return nil } @@ -464,7 +487,11 @@ func (txProc *txProcessor) processMoveBalance( isUserTxOfRelayed bool, ) error { - moveBalanceCost, totalCost, err := txProc.processTxFee(tx, acntSrc, acntDst, destShardTxType, isUserTxOfRelayed) + feePayer, _, err := txProc.getFeePayer(tx, acntSrc, acntDst) + if err != nil { + return nil + } + moveBalanceCost, totalCost, err := txProc.processTxFee(tx, feePayer, acntDst, destShardTxType, isUserTxOfRelayed) if err != nil { return err } @@ -500,7 +527,7 @@ func (txProc *txProcessor) processMoveBalance( return process.ErrAccountNotPayable } - err = txProc.checkIfValidTxToMetaChain(tx, tx.RcvAddr) + err = txProc.checkIfValidTxToMetaChain(tx) if err != nil { errLocal := txProc.revertConsumedValueFromSender(tx, acntSrc, isUserTxOfRelayed) if errLocal != nil { @@ -528,7 +555,7 @@ func (txProc *txProcessor) processMoveBalance( return err } - err = txProc.createReceiptWithReturnedGas(txHash, tx, acntSrc, moveBalanceCost, totalCost, destShardTxType, isUserTxOfRelayed) + err = txProc.createReceiptWithReturnedGas(txHash, tx, feePayer, moveBalanceCost, totalCost, destShardTxType, isUserTxOfRelayed) if err != nil { return err } @@ -604,7 +631,12 @@ func (txProc *txProcessor) finishExecutionOfRelayedTx( userTx *transaction.Transaction, ) (vmcommon.ReturnCode, error) { computedFees := txProc.computeRelayedTxFees(tx, userTx) - txHash, err := txProc.processTxAtRelayer(relayerAcnt, computedFees.totalFee, computedFees.relayerFee, tx) + txHash, err := txProc.processTxAtRelayer( + relayerAcnt, + computedFees.totalFee, + computedFees.relayerFee, + tx, + tx.Value) if err != nil { return 0, err } @@ -618,7 +650,7 @@ func (txProc *txProcessor) finishExecutionOfRelayedTx( return 0, err } - return txProc.processUserTx(tx, userTx, tx.Value, tx.Nonce, txHash) + return txProc.processUserTx(tx, userTx, tx.Value, tx.Nonce, tx.SndAddr, txHash) } func (txProc *txProcessor) processTxAtRelayer( @@ -626,6 +658,7 @@ func (txProc *txProcessor) processTxAtRelayer( totalFee *big.Int, relayerFee *big.Int, tx *transaction.Transaction, + valueToSubFromRelayer *big.Int, ) ([]byte, error) { txHash, err := core.CalculateHash(txProc.marshalizer, txProc.hasher, tx) if err != nil { @@ -633,7 +666,7 @@ func (txProc *txProcessor) processTxAtRelayer( } if !check.IfNil(relayerAcnt) { - err = relayerAcnt.SubFromBalance(tx.GetValue()) + err = relayerAcnt.SubFromBalance(valueToSubFromRelayer) if err != nil { return nil, err } @@ -656,6 +689,10 @@ func (txProc *txProcessor) processTxAtRelayer( } func (txProc *txProcessor) addFeeAndValueToDest(acntDst state.UserAccountHandler, txValue *big.Int, remainingFee *big.Int) error { + if check.IfNil(acntDst) { + return nil + } + err := acntDst.AddToBalance(txValue) if err != nil { return err @@ -669,23 +706,48 @@ func (txProc *txProcessor) addFeeAndValueToDest(acntDst state.UserAccountHandler return txProc.accounts.SaveAccount(acntDst) } +func (txProc *txProcessor) verifyRelayedTxV3(tx *transaction.Transaction) error { + if !txProc.enableEpochsHandler.IsFlagEnabled(common.RelayedTransactionsV3Flag) { + return fmt.Errorf("%w, %s", process.ErrTransactionNotExecutable, process.ErrRelayedTxV3Disabled) + } + + if !txProc.shardCoordinator.SameShard(tx.RelayerAddr, tx.SndAddr) { + return fmt.Errorf("%w, %s", process.ErrTransactionNotExecutable, process.ErrShardIdMissmatch) + } + + if bytes.Equal(tx.RelayerAddr, tx.GuardianAddr) { + return fmt.Errorf("%w, %s", process.ErrTransactionNotExecutable, process.ErrRelayedByGuardianNotAllowed) + } + + relayerAccount, err := txProc.getAccountFromAddress(tx.RelayerAddr) + if err != nil { + return fmt.Errorf("%w, %s", process.ErrTransactionNotExecutable, err) + } + + if !check.IfNil(relayerAccount) && relayerAccount.IsGuarded() { + return fmt.Errorf("%w, %s", process.ErrTransactionNotExecutable, process.ErrGuardedRelayerNotAllowed) + } + + return nil +} + func (txProc *txProcessor) processRelayedTxV2( tx *transaction.Transaction, relayerAcnt, acntDst state.UserAccountHandler, ) (vmcommon.ReturnCode, error) { if !txProc.enableEpochsHandler.IsFlagEnabled(common.RelayedTransactionsV2Flag) { - return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, process.ErrRelayedTxV2Disabled) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, acntDst, process.ErrRelayedTxV2Disabled) } if tx.GetValue().Cmp(big.NewInt(0)) != 0 { - return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, process.ErrRelayedTxV2ZeroVal) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, acntDst, process.ErrRelayedTxV2ZeroVal) } _, args, err := txProc.argsParser.ParseCallData(string(tx.GetData())) if err != nil { - return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, err) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, acntDst, err) } if len(args) != 4 { - return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, process.ErrInvalidArguments) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, acntDst, process.ErrInvalidArguments) } userTx := makeUserTxFromRelayedTxV2Args(args) @@ -706,31 +768,31 @@ func (txProc *txProcessor) processRelayedTx( return 0, err } if len(args) != 1 { - return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, process.ErrInvalidArguments) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, acntDst, process.ErrInvalidArguments) } if !txProc.enableEpochsHandler.IsFlagEnabled(common.RelayedTransactionsFlag) { - return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, process.ErrRelayedTxDisabled) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, acntDst, process.ErrRelayedTxDisabled) } userTx := &transaction.Transaction{} err = txProc.signMarshalizer.Unmarshal(userTx, args[0]) if err != nil { - return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, err) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, acntDst, err) } if !bytes.Equal(userTx.SndAddr, tx.RcvAddr) { - return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, process.ErrRelayedTxBeneficiaryDoesNotMatchReceiver) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, acntDst, process.ErrRelayedTxBeneficiaryDoesNotMatchReceiver) } if userTx.Value.Cmp(tx.Value) < 0 { - return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, process.ErrRelayedTxValueHigherThenUserTxValue) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, acntDst, process.ErrRelayedTxValueHigherThenUserTxValue) } if userTx.GasPrice != tx.GasPrice { - return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, process.ErrRelayedGasPriceMissmatch) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, acntDst, process.ErrRelayedGasPriceMissmatch) } remainingGasLimit := tx.GasLimit - txProc.economicsFee.ComputeGasLimit(tx) if userTx.GasLimit != remainingGasLimit { - return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, process.ErrRelayedTxGasLimitMissmatch) + return vmcommon.UserError, txProc.executingFailedTransaction(tx, relayerAcnt, acntDst, process.ErrRelayedTxGasLimitMissmatch) } return txProc.finishExecutionOfRelayedTx(relayerAcnt, acntDst, tx, userTx) @@ -838,10 +900,10 @@ func (txProc *txProcessor) processUserTx( userTx *transaction.Transaction, relayedTxValue *big.Int, relayedNonce uint64, + relayerAddr []byte, originalTxHash []byte, ) (vmcommon.ReturnCode, error) { - relayerAdr := originalTx.SndAddr acntSnd, acntDst, err := txProc.getAccounts(userTx.SndAddr, userTx.RcvAddr) if err != nil { errRemove := txProc.removeValueAndConsumedFeeFromUser(userTx, relayedTxValue, originalTxHash, originalTx, err) @@ -850,7 +912,7 @@ func (txProc *txProcessor) processUserTx( } return vmcommon.UserError, txProc.executeFailedRelayedUserTx( userTx, - relayerAdr, + relayerAddr, relayedTxValue, relayedNonce, originalTx, @@ -858,7 +920,7 @@ func (txProc *txProcessor) processUserTx( err.Error()) } - txType, dstShardTxType := txProc.txTypeHandler.ComputeTransactionType(userTx) + txType, dstShardTxType, _ := txProc.txTypeHandler.ComputeTransactionType(userTx) err = txProc.checkTxValues(userTx, acntSnd, acntDst, true) if err != nil { errRemove := txProc.removeValueAndConsumedFeeFromUser(userTx, relayedTxValue, originalTxHash, originalTx, err) @@ -867,7 +929,7 @@ func (txProc *txProcessor) processUserTx( } return vmcommon.UserError, txProc.executeFailedRelayedUserTx( userTx, - relayerAdr, + relayerAddr, relayedTxValue, relayedNonce, originalTx, @@ -875,7 +937,7 @@ func (txProc *txProcessor) processUserTx( err.Error()) } - scrFromTx, err := txProc.makeSCRFromUserTx(userTx, relayerAdr, relayedTxValue, originalTxHash) + scrFromTx, err := txProc.makeSCRFromUserTx(userTx, relayerAddr, relayedTxValue, originalTxHash) if err != nil { return 0, err } @@ -913,7 +975,7 @@ func (txProc *txProcessor) processUserTx( } return vmcommon.UserError, txProc.executeFailedRelayedUserTx( userTx, - relayerAdr, + relayerAddr, relayedTxValue, relayedNonce, originalTx, @@ -924,7 +986,7 @@ func (txProc *txProcessor) processUserTx( if errors.Is(err, process.ErrInvalidMetaTransaction) || errors.Is(err, process.ErrAccountNotPayable) { return vmcommon.UserError, txProc.executeFailedRelayedUserTx( userTx, - relayerAdr, + relayerAddr, relayedTxValue, relayedNonce, originalTx, diff --git a/process/transaction/shardProcess_test.go b/process/transaction/shardProcess_test.go index 88797d31a0c..75adbf3f3b9 100644 --- a/process/transaction/shardProcess_test.go +++ b/process/transaction/shardProcess_test.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "math/big" + "strings" "testing" "github.com/multiversx/mx-chain-core-go/core" @@ -923,7 +924,7 @@ func TestTxProcessor_ProcessMoveBalanceToSmartPayableContract(t *testing.T) { _, err := execTx.ProcessTransaction(&tx) assert.Nil(t, err) - assert.Equal(t, 2, saveAccountCalled) + assert.Equal(t, 3, saveAccountCalled) } func testProcessCheck(t *testing.T, nonce uint64, value *big.Int) { @@ -989,7 +990,7 @@ func TestTxProcessor_ProcessMoveBalancesShouldWork(t *testing.T) { _, err := execTx.ProcessTransaction(&tx) assert.Nil(t, err) - assert.Equal(t, 2, saveAccountCalled) + assert.Equal(t, 3, saveAccountCalled) } func TestTxProcessor_ProcessOkValsShouldWork(t *testing.T) { @@ -1025,7 +1026,7 @@ func TestTxProcessor_ProcessOkValsShouldWork(t *testing.T) { assert.Equal(t, uint64(5), acntSrc.GetNonce()) assert.Equal(t, big.NewInt(29), acntSrc.GetBalance()) assert.Equal(t, big.NewInt(71), acntDst.GetBalance()) - assert.Equal(t, 2, saveAccountCalled) + assert.Equal(t, 3, saveAccountCalled) } func TestTxProcessor_MoveBalanceWithFeesShouldWork(t *testing.T) { @@ -1072,7 +1073,7 @@ func TestTxProcessor_MoveBalanceWithFeesShouldWork(t *testing.T) { assert.Equal(t, uint64(5), acntSrc.GetNonce()) assert.Equal(t, big.NewInt(13), acntSrc.GetBalance()) assert.Equal(t, big.NewInt(71), acntDst.GetBalance()) - assert.Equal(t, 2, saveAccountCalled) + assert.Equal(t, 3, saveAccountCalled) } func TestTxProcessor_ProcessTransactionScDeployTxShouldWork(t *testing.T) { @@ -1111,8 +1112,8 @@ func TestTxProcessor_ProcessTransactionScDeployTxShouldWork(t *testing.T) { args.Accounts = adb args.ScProcessor = scProcessorMock args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType process.TransactionType, destinationTransactionType process.TransactionType) { - return process.SCDeployment, process.SCDeployment + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType process.TransactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.SCDeployment, process.SCDeployment, false }, } execTx, _ := txproc.NewTxProcessor(args) @@ -1159,8 +1160,8 @@ func TestTxProcessor_ProcessTransactionBuiltInFunctionCallShouldWork(t *testing. args.Accounts = adb args.ScProcessor = scProcessorMock args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.BuiltInFunctionCall, process.BuiltInFunctionCall + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.BuiltInFunctionCall, process.BuiltInFunctionCall, false }, } execTx, _ := txproc.NewTxProcessor(args) @@ -1207,8 +1208,8 @@ func TestTxProcessor_ProcessTransactionScTxShouldWork(t *testing.T) { args.Accounts = adb args.ScProcessor = scProcessorMock args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }, } execTx, _ := txproc.NewTxProcessor(args) @@ -1253,8 +1254,8 @@ func TestTxProcessor_ProcessTransactionScTxShouldReturnErrWhenExecutionFails(t * args.Accounts = adb args.ScProcessor = scProcessorMock args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }, } execTx, _ := txproc.NewTxProcessor(args) @@ -1324,7 +1325,7 @@ func TestTxProcessor_ProcessTransactionScTxShouldNotBeCalledWhenAdrDstIsNotInNod _, err := execTx.ProcessTransaction(&tx) assert.Nil(t, err) assert.False(t, wasCalled) - assert.Equal(t, 1, saveAccountCalled) + assert.Equal(t, 2, saveAccountCalled) } func TestTxProcessor_ProcessTxFeeIntraShard(t *testing.T) { @@ -1573,8 +1574,8 @@ func TestTxProcessor_ProcessTransactionShouldReturnErrForInvalidMetaTx(t *testin }, } args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.MoveBalance, process.MoveBalance + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.MoveBalance, false }, } args.EnableEpochsHandler = enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.MetaProtectionFlag) @@ -1621,8 +1622,8 @@ func TestTxProcessor_ProcessTransactionShouldTreatAsInvalidTxIfTxTypeIsWrong(t * }, } args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.InvalidTransaction, process.InvalidTransaction + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.InvalidTransaction, process.InvalidTransaction, false }, } execTx, _ := txproc.NewTxProcessor(args) @@ -2181,8 +2182,8 @@ func TestTxProcessor_ProcessRelayedTransactionArgsParserErrorShouldError(t *test return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.RelayedTx, process.RelayedTx + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.RelayedTx, process.RelayedTx, false }} execTx, _ := txproc.NewTxProcessor(args) @@ -2244,8 +2245,8 @@ func TestTxProcessor_ProcessRelayedTransactionMultipleArgumentsShouldError(t *te return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.RelayedTx, process.RelayedTx + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.RelayedTx, process.RelayedTx, false }} execTx, _ := txproc.NewTxProcessor(args) @@ -2307,8 +2308,8 @@ func TestTxProcessor_ProcessRelayedTransactionFailUnMarshalInnerShouldError(t *t return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.RelayedTx, process.RelayedTx + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.RelayedTx, process.RelayedTx, false }} execTx, _ := txproc.NewTxProcessor(args) @@ -2370,8 +2371,8 @@ func TestTxProcessor_ProcessRelayedTransactionDifferentSenderInInnerTxThanReceiv return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.RelayedTx, process.RelayedTx + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.RelayedTx, process.RelayedTx, false }} execTx, _ := txproc.NewTxProcessor(args) @@ -2433,8 +2434,8 @@ func TestTxProcessor_ProcessRelayedTransactionSmallerValueInnerTxShouldError(t * return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.RelayedTx, process.RelayedTx + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.RelayedTx, process.RelayedTx, false }} execTx, _ := txproc.NewTxProcessor(args) @@ -2496,8 +2497,8 @@ func TestTxProcessor_ProcessRelayedTransactionGasPriceMismatchShouldError(t *tes return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.RelayedTx, process.RelayedTx + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.RelayedTx, process.RelayedTx, false }} execTx, _ := txproc.NewTxProcessor(args) @@ -2559,8 +2560,8 @@ func TestTxProcessor_ProcessRelayedTransactionGasLimitMismatchShouldError(t *tes return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.RelayedTx, process.RelayedTx + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.RelayedTx, process.RelayedTx, false }} execTx, _ := txproc.NewTxProcessor(args) @@ -2653,6 +2654,167 @@ func TestTxProcessor_ProcessRelayedTransactionDisabled(t *testing.T) { assert.True(t, called) } +func TestTxProcessor_ProcessRelayedTransactionV3(t *testing.T) { + t.Parallel() + + pubKeyConverter := testscommon.NewPubkeyConverterMock(4) + + userAddr := []byte("user") + tx := &transaction.Transaction{} + tx.Nonce = 0 + tx.SndAddr = []byte("sSRC") + tx.RcvAddr = userAddr + tx.Value = big.NewInt(45) + tx.GasPrice = 1 + tx.GasLimit = 1 + tx.RelayerAddr = []byte("rela") + tx.Signature = []byte("ssig") + tx.RelayerSignature = []byte("rsig") + + acntSrc := createUserAcc(tx.SndAddr) + _ = acntSrc.AddToBalance(big.NewInt(100)) + acntDst := createUserAcc(tx.RcvAddr) + _ = acntDst.AddToBalance(big.NewInt(10)) + acntRelayer := createUserAcc(tx.RelayerAddr) + _ = acntRelayer.AddToBalance(big.NewInt(10)) + + adb := &stateMock.AccountsStub{} + adb.LoadAccountCalled = func(address []byte) (vmcommon.AccountHandler, error) { + if bytes.Equal(address, tx.SndAddr) { + return acntSrc, nil + } + if bytes.Equal(address, tx.RcvAddr) { + return acntDst, nil + } + if bytes.Equal(address, tx.RelayerAddr) { + return acntRelayer, nil + } + + return nil, errors.New("failure") + } + scProcessorMock := &testscommon.SCProcessorMock{} + shardC, _ := sharding.NewMultiShardCoordinator(1, 0) + + esdtTransferParser, _ := parsers.NewESDTTransferParser(&mock.MarshalizerMock{}) + argTxTypeHandler := coordinator.ArgNewTxTypeHandler{ + PubkeyConverter: pubKeyConverter, + ShardCoordinator: shardC, + BuiltInFunctions: builtInFunctions.NewBuiltInFunctionContainer(), + ArgumentParser: parsers.NewCallArgsParser(), + ESDTTransferParser: esdtTransferParser, + EnableEpochsHandler: enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.RelayedTransactionsV3Flag), + } + txTypeHandler, _ := coordinator.NewTxTypeHandler(argTxTypeHandler) + + args := createArgsForTxProcessor() + args.Accounts = adb + args.ScProcessor = scProcessorMock + args.ShardCoordinator = shardC + args.TxTypeHandler = txTypeHandler + args.PubkeyConv = pubKeyConverter + args.ArgsParser = smartContract.NewArgumentParser() + args.EnableEpochsHandler = enableEpochsHandlerMock.NewEnableEpochsHandlerStub(common.RelayedTransactionsV3Flag) + txProc, _ := txproc.NewTxProcessor(args) + require.NotNil(t, txProc) + + t.Run("should work", func(t *testing.T) { + txCopy := *tx + txCopy.Nonce = acntSrc.GetNonce() + returnCode, err := txProc.ProcessTransaction(&txCopy) + assert.NoError(t, err) + assert.Equal(t, vmcommon.Ok, returnCode) + }) + t.Run("flag not active should error", func(t *testing.T) { + argsCopy := args + argsCopy.EnableEpochsHandler = enableEpochsHandlerMock.NewEnableEpochsHandlerStub() + txProcLocal, _ := txproc.NewTxProcessor(argsCopy) + require.NotNil(t, txProcLocal) + + txCopy := *tx + txCopy.Nonce = acntSrc.GetNonce() + returnCode, err := txProcLocal.ProcessTransaction(&txCopy) + assert.True(t, errors.Is(err, process.ErrTransactionNotExecutable)) + assert.True(t, strings.Contains(err.Error(), process.ErrRelayedTxV3Disabled.Error())) + assert.Equal(t, vmcommon.UserError, returnCode) + }) + t.Run("relayer not in the same shard with the sender should error", func(t *testing.T) { + argsCopy := args + argsCopy.ShardCoordinator = &mock.ShardCoordinatorStub{ + SameShardCalled: func(firstAddress, secondAddress []byte) bool { + return false + }, + } + txProcLocal, _ := txproc.NewTxProcessor(argsCopy) + require.NotNil(t, txProcLocal) + + txCopy := *tx + txCopy.Nonce = acntSrc.GetNonce() + returnCode, err := txProcLocal.ProcessTransaction(&txCopy) + assert.True(t, errors.Is(err, process.ErrTransactionNotExecutable)) + assert.True(t, strings.Contains(err.Error(), process.ErrShardIdMissmatch.Error())) + assert.Equal(t, vmcommon.UserError, returnCode) + }) + t.Run("guarded relayer account should error", func(t *testing.T) { + acntRelayerCopy := acntRelayer + acntRelayerCopy.IsGuarded() + argsCopy := args + argsCopy.Accounts = &stateMock.AccountsStub{ + LoadAccountCalled: func(address []byte) (vmcommon.AccountHandler, error) { + if bytes.Equal(address, tx.SndAddr) { + return acntSrc, nil + } + if bytes.Equal(address, tx.RcvAddr) { + return acntDst, nil + } + if bytes.Equal(address, tx.RelayerAddr) { + return &stateMock.UserAccountStub{ + IsGuardedCalled: func() bool { + return true + }, + Balance: big.NewInt(1), + }, nil + } + + return nil, errors.New("failure") + }, + } + txProcLocal, _ := txproc.NewTxProcessor(argsCopy) + require.NotNil(t, txProcLocal) + + txCopy := *tx + txCopy.Nonce = acntSrc.GetNonce() + returnCode, err := txProcLocal.ProcessTransaction(&txCopy) + assert.True(t, errors.Is(err, process.ErrTransactionNotExecutable)) + assert.True(t, strings.Contains(err.Error(), process.ErrGuardedRelayerNotAllowed.Error())) + assert.Equal(t, vmcommon.UserError, returnCode) + }) + t.Run("same guardian and relayer should error", func(t *testing.T) { + txCopy := *tx + txCopy.Nonce = acntSrc.GetNonce() + txCopy.GuardianAddr = txCopy.RelayerAddr + returnCode, err := txProc.ProcessTransaction(&txCopy) + assert.True(t, errors.Is(err, process.ErrTransactionNotExecutable)) + assert.True(t, strings.Contains(err.Error(), process.ErrRelayedByGuardianNotAllowed.Error())) + assert.Equal(t, vmcommon.UserError, returnCode) + }) + t.Run("insufficient gas limit should error", func(t *testing.T) { + txCopy := *tx + txCopy.Nonce = acntSrc.GetNonce() + argsCopy := args + argsCopy.EconomicsFee = &economicsmocks.EconomicsHandlerStub{ + ComputeGasLimitCalled: func(tx data.TransactionWithFeeHandler) uint64 { + return txCopy.GasLimit + 1 + }, + } + txProcLocal, _ := txproc.NewTxProcessor(argsCopy) + require.NotNil(t, txProcLocal) + + returnCode, err := txProcLocal.ProcessTransaction(&txCopy) + assert.Equal(t, process.ErrNotEnoughGas, err) + assert.Equal(t, vmcommon.UserError, returnCode) + }) +} + func TestTxProcessor_ConsumeMoveBalanceWithUserTx(t *testing.T) { t.Parallel() @@ -2755,13 +2917,13 @@ func TestTxProcessor_ProcessUserTxOfTypeRelayedShouldError(t *testing.T) { return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.RelayedTx, process.RelayedTx + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.RelayedTx, process.RelayedTx, false }} execTx, _ := txproc.NewTxProcessor(args) - returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, txHash) + returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, tx.SndAddr, txHash) assert.Nil(t, err) assert.Equal(t, vmcommon.UserError, returnCode) } @@ -2818,13 +2980,13 @@ func TestTxProcessor_ProcessUserTxOfTypeMoveBalanceShouldWork(t *testing.T) { return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.MoveBalance, process.MoveBalance + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.MoveBalance, process.MoveBalance, false }} execTx, _ := txproc.NewTxProcessor(args) - returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, txHash) + returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, tx.SndAddr, txHash) assert.Nil(t, err) assert.Equal(t, vmcommon.Ok, returnCode) } @@ -2881,13 +3043,13 @@ func TestTxProcessor_ProcessUserTxOfTypeSCDeploymentShouldWork(t *testing.T) { return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.SCDeployment, process.SCDeployment + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.SCDeployment, process.SCDeployment, false }} execTx, _ := txproc.NewTxProcessor(args) - returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, txHash) + returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, tx.SndAddr, txHash) assert.Nil(t, err) assert.Equal(t, vmcommon.Ok, returnCode) } @@ -2944,13 +3106,13 @@ func TestTxProcessor_ProcessUserTxOfTypeSCInvokingShouldWork(t *testing.T) { return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.SCInvoking, process.SCInvoking + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.SCInvoking, process.SCInvoking, false }} execTx, _ := txproc.NewTxProcessor(args) - returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, txHash) + returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, tx.SndAddr, txHash) assert.Nil(t, err) assert.Equal(t, vmcommon.Ok, returnCode) } @@ -3007,13 +3169,13 @@ func TestTxProcessor_ProcessUserTxOfTypeBuiltInFunctionCallShouldWork(t *testing return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.BuiltInFunctionCall, process.BuiltInFunctionCall + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.BuiltInFunctionCall, process.BuiltInFunctionCall, false }} execTx, _ := txproc.NewTxProcessor(args) - returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, txHash) + returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, tx.SndAddr, txHash) assert.Nil(t, err) assert.Equal(t, vmcommon.Ok, returnCode) } @@ -3074,13 +3236,13 @@ func TestTxProcessor_ProcessUserTxErrNotPayableShouldFailRelayTx(t *testing.T) { return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.MoveBalance, process.MoveBalance + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.MoveBalance, process.MoveBalance, false }} execTx, _ := txproc.NewTxProcessor(args) - returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, txHash) + returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, tx.SndAddr, txHash) assert.Nil(t, err) assert.Equal(t, vmcommon.UserError, returnCode) } @@ -3143,13 +3305,13 @@ func TestTxProcessor_ProcessUserTxFailedBuiltInFunctionCall(t *testing.T) { return nil, errors.New("failure") } args.Accounts = adb - args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType) { - return process.BuiltInFunctionCall, process.BuiltInFunctionCall + args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (transactionType, destinationTransactionType process.TransactionType, isRelayedV3 bool) { + return process.BuiltInFunctionCall, process.BuiltInFunctionCall, false }} execTx, _ := txproc.NewTxProcessor(args) - returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, txHash) + returnCode, err := execTx.ProcessUserTx(&tx, &userTx, tx.Value, tx.Nonce, tx.SndAddr, txHash) assert.Nil(t, err) assert.Equal(t, vmcommon.ExecutionFailed, returnCode) } @@ -3365,13 +3527,13 @@ func TestTxProcessor_ProcessMoveBalanceToNonPayableContract(t *testing.T) { args.SignMarshalizer = &marshaller cnt := 0 args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { cnt++ if cnt == 1 { - return process.RelayedTx, process.RelayedTx + return process.RelayedTx, process.RelayedTx, false } - return process.MoveBalance, process.MoveBalance + return process.MoveBalance, process.MoveBalance, false }, } args.EnableEpochsHandler = enableEpochsHandlerMock.NewEnableEpochsHandlerStub( diff --git a/process/transactionEvaluator/transactionEvaluator.go b/process/transactionEvaluator/transactionEvaluator.go index 9e61d138419..c2e566490ef 100644 --- a/process/transactionEvaluator/transactionEvaluator.go +++ b/process/transactionEvaluator/transactionEvaluator.go @@ -111,7 +111,7 @@ func (ate *apiTransactionEvaluator) ComputeTransactionGasLimit(tx *transaction.T ate.mutExecution.Unlock() }() - txTypeOnSender, txTypeOnDestination := ate.txTypeHandler.ComputeTransactionType(tx) + txTypeOnSender, txTypeOnDestination, _ := ate.txTypeHandler.ComputeTransactionType(tx) if txTypeOnSender == process.MoveBalance && txTypeOnDestination == process.MoveBalance { return ate.computeMoveBalanceCost(tx), nil } diff --git a/process/transactionEvaluator/transactionEvaluator_test.go b/process/transactionEvaluator/transactionEvaluator_test.go index f36a5388777..bfcbf97f787 100644 --- a/process/transactionEvaluator/transactionEvaluator_test.go +++ b/process/transactionEvaluator/transactionEvaluator_test.go @@ -114,8 +114,8 @@ func TestComputeTransactionGasLimit_MoveBalance(t *testing.T) { args := createArgs() args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.MoveBalance, process.MoveBalance + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.MoveBalance, false }, } args.FeeHandler = &economicsmocks.EconomicsHandlerStub{ @@ -153,8 +153,8 @@ func TestComputeTransactionGasLimit_MoveBalanceInvalidNonceShouldStillComputeCos args := createArgs() args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.MoveBalance, process.MoveBalance + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.MoveBalance, process.MoveBalance, false }, } args.FeeHandler = &economicsmocks.EconomicsHandlerStub{ @@ -187,8 +187,8 @@ func TestComputeTransactionGasLimit_BuiltInFunction(t *testing.T) { consumedGasUnits := uint64(4000) args := createArgs() args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.BuiltInFunctionCall, process.BuiltInFunctionCall + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.BuiltInFunctionCall, process.BuiltInFunctionCall, false }, } args.FeeHandler = &economicsmocks.EconomicsHandlerStub{ @@ -223,8 +223,8 @@ func TestComputeTransactionGasLimit_BuiltInFunctionShouldErr(t *testing.T) { localErr := errors.New("local err") args := createArgs() args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.BuiltInFunctionCall, process.BuiltInFunctionCall + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.BuiltInFunctionCall, process.BuiltInFunctionCall, false }, } args.FeeHandler = &economicsmocks.EconomicsHandlerStub{ @@ -253,8 +253,8 @@ func TestComputeTransactionGasLimit_BuiltInFunctionShouldErr(t *testing.T) { func TestComputeTransactionGasLimit_NilVMOutput(t *testing.T) { args := createArgs() args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.BuiltInFunctionCall, process.BuiltInFunctionCall + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.BuiltInFunctionCall, process.BuiltInFunctionCall, false }, } args.FeeHandler = &economicsmocks.EconomicsHandlerStub{ @@ -284,8 +284,8 @@ func TestComputeTransactionGasLimit_NilVMOutput(t *testing.T) { func TestComputeTransactionGasLimit_RetCodeNotOk(t *testing.T) { args := createArgs() args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.BuiltInFunctionCall, process.BuiltInFunctionCall + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.BuiltInFunctionCall, process.BuiltInFunctionCall, false }, } args.FeeHandler = &economicsmocks.EconomicsHandlerStub{ @@ -321,8 +321,8 @@ func TestTransactionEvaluator_RelayedTxShouldErr(t *testing.T) { args := createArgs() args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.RelayedTx, process.RelayedTx + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.RelayedTx, process.RelayedTx, false }, } tce, _ := NewAPITransactionEvaluator(args) @@ -386,8 +386,8 @@ func TestApiTransactionEvaluator_ComputeTransactionGasLimit(t *testing.T) { _ = args.BlockChain.SetCurrentBlockHeaderAndRootHash(&block.Header{Nonce: expectedNonce}, []byte("test")) args.TxTypeHandler = &testscommon.TxTypeHandlerMock{ - ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { - return process.SCInvoking, process.SCInvoking + ComputeTransactionTypeCalled: func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { + return process.SCInvoking, process.SCInvoking, false }, } args.TxSimulator = &mock.TransactionSimulatorStub{ diff --git a/statusHandler/statusMetricsProvider.go b/statusHandler/statusMetricsProvider.go index 8050d335431..bc92e511c10 100644 --- a/statusHandler/statusMetricsProvider.go +++ b/statusHandler/statusMetricsProvider.go @@ -246,6 +246,7 @@ func (sm *statusMetrics) ConfigMetrics() (map[string]interface{}, error) { configMetrics[common.MetricMinGasPrice] = sm.uint64Metrics[common.MetricMinGasPrice] configMetrics[common.MetricMinGasLimit] = sm.uint64Metrics[common.MetricMinGasLimit] configMetrics[common.MetricExtraGasLimitGuardedTx] = sm.uint64Metrics[common.MetricExtraGasLimitGuardedTx] + configMetrics[common.MetricExtraGasLimitRelayedTx] = sm.uint64Metrics[common.MetricExtraGasLimitRelayedTx] configMetrics[common.MetricMaxGasPerTransaction] = sm.uint64Metrics[common.MetricMaxGasPerTransaction] configMetrics[common.MetricRoundDuration] = sm.uint64Metrics[common.MetricRoundDuration] configMetrics[common.MetricStartTime] = sm.uint64Metrics[common.MetricStartTime] @@ -379,6 +380,7 @@ func (sm *statusMetrics) EnableEpochsMetrics() (map[string]interface{}, error) { enableEpochsMetrics[common.MetricCryptoOpcodesV2EnableEpoch] = sm.uint64Metrics[common.MetricCryptoOpcodesV2EnableEpoch] enableEpochsMetrics[common.MetricMultiESDTNFTTransferAndExecuteByUserEnableEpoch] = sm.uint64Metrics[common.MetricMultiESDTNFTTransferAndExecuteByUserEnableEpoch] enableEpochsMetrics[common.MetricFixRelayedMoveBalanceToNonPayableSCEnableEpoch] = sm.uint64Metrics[common.MetricFixRelayedMoveBalanceToNonPayableSCEnableEpoch] + enableEpochsMetrics[common.MetricRelayedTransactionsV3EnableEpoch] = sm.uint64Metrics[common.MetricRelayedTransactionsV3EnableEpoch] numNodesChangeConfig := sm.uint64Metrics[common.MetricMaxNodesChangeEnableEpoch+"_count"] diff --git a/statusHandler/statusMetricsProvider_test.go b/statusHandler/statusMetricsProvider_test.go index 14b5b5225d3..40303970ce5 100644 --- a/statusHandler/statusMetricsProvider_test.go +++ b/statusHandler/statusMetricsProvider_test.go @@ -179,6 +179,7 @@ func TestStatusMetrics_NetworkConfig(t *testing.T) { sm.SetUInt64Value(common.MetricMinGasPrice, 1000) sm.SetUInt64Value(common.MetricMinGasLimit, 50000) sm.SetUInt64Value(common.MetricExtraGasLimitGuardedTx, 50000) + sm.SetUInt64Value(common.MetricExtraGasLimitRelayedTx, 50000) sm.SetStringValue(common.MetricRewardsTopUpGradientPoint, "12345") sm.SetUInt64Value(common.MetricGasPerDataByte, 1500) sm.SetStringValue(common.MetricChainId, "local-id") @@ -202,6 +203,7 @@ func TestStatusMetrics_NetworkConfig(t *testing.T) { "erd_meta_consensus_group_size": uint64(25), "erd_min_gas_limit": uint64(50000), "erd_extra_gas_limit_guarded_tx": uint64(50000), + "erd_extra_gas_limit_relayed_tx": uint64(50000), "erd_min_gas_price": uint64(1000), "erd_min_transaction_version": uint64(2), "erd_num_metachain_nodes": uint64(50), @@ -402,6 +404,7 @@ func TestStatusMetrics_EnableEpochMetrics(t *testing.T) { sm.SetUInt64Value(common.MetricCryptoOpcodesV2EnableEpoch, uint64(4)) sm.SetUInt64Value(common.MetricMultiESDTNFTTransferAndExecuteByUserEnableEpoch, uint64(4)) sm.SetUInt64Value(common.MetricFixRelayedMoveBalanceToNonPayableSCEnableEpoch, uint64(4)) + sm.SetUInt64Value(common.MetricRelayedTransactionsV3EnableEpoch, uint64(4)) maxNodesChangeConfig := []map[string]uint64{ { @@ -533,6 +536,7 @@ func TestStatusMetrics_EnableEpochMetrics(t *testing.T) { common.MetricCryptoOpcodesV2EnableEpoch: uint64(4), common.MetricMultiESDTNFTTransferAndExecuteByUserEnableEpoch: uint64(4), common.MetricFixRelayedMoveBalanceToNonPayableSCEnableEpoch: uint64(4), + common.MetricRelayedTransactionsV3EnableEpoch: uint64(4), common.MetricMaxNodesChangeEnableEpoch: []map[string]interface{}{ { diff --git a/storage/pruning/fullHistoryPruningStorer.go b/storage/pruning/fullHistoryPruningStorer.go index 71213b1dcdd..97852aa3bcd 100644 --- a/storage/pruning/fullHistoryPruningStorer.go +++ b/storage/pruning/fullHistoryPruningStorer.go @@ -184,6 +184,7 @@ func (fhps *FullHistoryPruningStorer) getOrOpenPersister(epoch uint32) (storage. } fhps.oldEpochsActivePersistersCache.Put([]byte(epochString), newPdata, 0) + log.Trace("full history pruning storer - init new storer", "epoch", epoch) fhps.persistersMapByEpoch[epoch] = newPdata return newPdata.getPersister(), nil diff --git a/storage/pruning/fullHistoryPruningStorer_test.go b/storage/pruning/fullHistoryPruningStorer_test.go index 0e0d43877e8..d1274499bb9 100644 --- a/storage/pruning/fullHistoryPruningStorer_test.go +++ b/storage/pruning/fullHistoryPruningStorer_test.go @@ -18,6 +18,7 @@ import ( "github.com/multiversx/mx-chain-go/storage/factory" "github.com/multiversx/mx-chain-go/storage/pathmanager" "github.com/multiversx/mx-chain-go/storage/pruning" + "github.com/multiversx/mx-chain-go/testscommon" logger "github.com/multiversx/mx-chain-logger-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -399,3 +400,33 @@ func TestFullHistoryPruningStorer_IsInterfaceNil(t *testing.T) { fhps, _ = pruning.NewFullHistoryPruningStorer(fhArgs) require.False(t, fhps.IsInterfaceNil()) } + +func TestFullHistoryPruningStorer_changeEpochClosesOldDbs(t *testing.T) { + t.Parallel() + + shouldCleanCalled := false + args := getDefaultArgs() + fhArgs := pruning.FullHistoryStorerArgs{ + StorerArgs: args, + NumOfOldActivePersisters: 2, + } + fhArgs.OldDataCleanerProvider = &testscommon.OldDataCleanerProviderStub{ + ShouldCleanCalled: func() bool { + shouldCleanCalled = true + return true + }, + } + fhps, err := pruning.NewFullHistoryPruningStorer(fhArgs) + require.Nil(t, err) + + numEpochsChanged := 10 + startEpoch := uint32(0) + for i := 0; i < numEpochsChanged; i++ { + startEpoch++ + key := []byte(fmt.Sprintf("key-%d", i)) + _, _ = fhps.GetFromEpoch(key, startEpoch) + err = fhps.ChangeEpochSimple(startEpoch) + require.Nil(t, err) + } + require.True(t, shouldCleanCalled) +} diff --git a/storage/pruning/pruningStorer.go b/storage/pruning/pruningStorer.go index 2007454a7c8..d40680e5c87 100644 --- a/storage/pruning/pruningStorer.go +++ b/storage/pruning/pruningStorer.go @@ -779,7 +779,7 @@ func (ps *PruningStorer) changeEpoch(header data.HeaderHandler) error { } log.Debug("change epoch pruning storer success", "persister", ps.identifier, "epoch", epoch) - return nil + return ps.removeOldPersistersIfNeeded(header) } shardID := core.GetShardIDString(ps.shardCoordinator.SelfId()) @@ -802,6 +802,11 @@ func (ps *PruningStorer) changeEpoch(header data.HeaderHandler) error { ps.activePersisters = append(singleItemPersisters, ps.activePersisters...) ps.persistersMapByEpoch[epoch] = newPersister + return ps.removeOldPersistersIfNeeded(header) +} + +func (ps *PruningStorer) removeOldPersistersIfNeeded(header data.HeaderHandler) error { + epoch := header.GetEpoch() wasExtended := ps.extendSavedEpochsIfNeeded(header) if wasExtended { if len(ps.activePersisters) > int(ps.numOfActivePersisters) { @@ -814,11 +819,12 @@ func (ps *PruningStorer) changeEpoch(header data.HeaderHandler) error { return nil } - err = ps.closeAndDestroyPersisters(epoch) + err := ps.closeAndDestroyPersisters(epoch) if err != nil { log.Warn("closing persisters", "error", err.Error()) return err } + return nil } diff --git a/testscommon/economicsmocks/economicsDataHandlerStub.go b/testscommon/economicsmocks/economicsDataHandlerStub.go index 4ef784c596f..a69206b12e6 100644 --- a/testscommon/economicsmocks/economicsDataHandlerStub.go +++ b/testscommon/economicsmocks/economicsDataHandlerStub.go @@ -47,6 +47,16 @@ type EconomicsHandlerStub struct { ComputeGasUsedAndFeeBasedOnRefundValueInEpochCalled func(tx data.TransactionWithFeeHandler, refundValue *big.Int, epoch uint32) (uint64, *big.Int) ComputeTxFeeBasedOnGasUsedInEpochCalled func(tx data.TransactionWithFeeHandler, gasUsed uint64, epoch uint32) *big.Int ComputeMoveBalanceFeeInEpochCalled func(tx data.TransactionWithFeeHandler, epoch uint32) *big.Int + ComputeGasUnitsFromRefundValueCalled func(tx data.TransactionWithFeeHandler, refundValue *big.Int, epoch uint32) uint64 +} + +// ComputeGasUnitsFromRefundValue - +func (e *EconomicsHandlerStub) ComputeGasUnitsFromRefundValue(tx data.TransactionWithFeeHandler, refundValue *big.Int, epoch uint32) uint64 { + if e.ComputeGasUnitsFromRefundValueCalled != nil { + return e.ComputeGasUnitsFromRefundValueCalled(tx, refundValue, epoch) + } + + return 0 } // ComputeFeeForProcessing - diff --git a/testscommon/economicsmocks/economicsHandlerMock.go b/testscommon/economicsmocks/economicsHandlerMock.go index b1e4321f389..304e86e37d2 100644 --- a/testscommon/economicsmocks/economicsHandlerMock.go +++ b/testscommon/economicsmocks/economicsHandlerMock.go @@ -49,6 +49,11 @@ type EconomicsHandlerMock struct { ComputeTxFeeBasedOnGasUsedInEpochCalled func(tx data.TransactionWithFeeHandler, gasUsed uint64, epoch uint32) *big.Int } +// ComputeGasUnitsFromRefundValue - +func (ehm *EconomicsHandlerMock) ComputeGasUnitsFromRefundValue(_ data.TransactionWithFeeHandler, _ *big.Int, _ uint32) uint64 { + return 0 +} + // LeaderPercentage - func (ehm *EconomicsHandlerMock) LeaderPercentage() float64 { return ehm.LeaderPercentageCalled() diff --git a/testscommon/scProcessorMock.go b/testscommon/scProcessorMock.go index 95e515b2950..ec96eb4c732 100644 --- a/testscommon/scProcessorMock.go +++ b/testscommon/scProcessorMock.go @@ -10,7 +10,7 @@ import ( // SCProcessorMock - type SCProcessorMock struct { - ComputeTransactionTypeCalled func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) + ComputeTransactionTypeCalled func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) ExecuteSmartContractTransactionCalled func(tx data.TransactionHandler, acntSrc, acntDst state.UserAccountHandler) (vmcommon.ReturnCode, error) ExecuteBuiltInFunctionCalled func(tx data.TransactionHandler, acntSrc, acntDst state.UserAccountHandler) (vmcommon.ReturnCode, error) DeploySmartContractCalled func(tx data.TransactionHandler, acntSrc state.UserAccountHandler) (vmcommon.ReturnCode, error) @@ -45,9 +45,9 @@ func (sc *SCProcessorMock) ProcessIfError( } // ComputeTransactionType - -func (sc *SCProcessorMock) ComputeTransactionType(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { +func (sc *SCProcessorMock) ComputeTransactionType(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { if sc.ComputeTransactionTypeCalled == nil { - return process.MoveBalance, process.MoveBalance + return process.MoveBalance, process.MoveBalance, false } return sc.ComputeTransactionTypeCalled(tx) diff --git a/testscommon/toml/config.go b/testscommon/toml/config.go index 56cfeb1f0ad..7f64ee446d4 100644 --- a/testscommon/toml/config.go +++ b/testscommon/toml/config.go @@ -16,6 +16,7 @@ type Config struct { TestConfigNestedStruct TestMap TestInterface + TestArray } // TestConfigI8 will hold an int8 value for testing @@ -180,3 +181,9 @@ type MapValues struct { type TestInterface struct { Value interface{} } + +// TestArray will hold an array of strings and integers +type TestArray struct { + Strings []string + Ints []int +} diff --git a/testscommon/toml/config.toml b/testscommon/toml/config.toml index 91512d5e664..890e3922789 100644 --- a/testscommon/toml/config.toml +++ b/testscommon/toml/config.toml @@ -51,3 +51,7 @@ [Map] [Map.Key1] Number = 999 + +[TestArray] + Strings = ["a", "b", "c"] + Ints = [0, 1, 2] diff --git a/testscommon/toml/overwrite.toml b/testscommon/toml/overwrite.toml index 63f74b7828c..5e495e5c08b 100644 --- a/testscommon/toml/overwrite.toml +++ b/testscommon/toml/overwrite.toml @@ -1,40 +1,43 @@ OverridableConfigTomlValues = [ - { File = "config.toml", Path = "TestConfigI8.Int8.Number", Value = 127 }, - { File = "config.toml", Path = "TestConfigI8.Int8.Number", Value = 128 }, - { File = "config.toml", Path = "TestConfigI8.Int8.Number", Value = -128 }, - { File = "config.toml", Path = "TestConfigI8.Int8.Number", Value = -129 }, - { File = "config.toml", Path = "TestConfigI16.Int16.Number", Value = 32767 }, - { File = "config.toml", Path = "TestConfigI16.Int16.Number", Value = 32768 }, - { File = "config.toml", Path = "TestConfigI16.Int16.Number", Value = -32768 }, - { File = "config.toml", Path = "TestConfigI16.Int16.Number", Value = -32769 }, - { File = "config.toml", Path = "TestConfigI32.Int32.Number", Value = 2147483647 }, - { File = "config.toml", Path = "TestConfigI32.Int32.Number", Value = 2147483648 }, - { File = "config.toml", Path = "TestConfigI32.Int32.Number", Value = -2147483648 }, - { File = "config.toml", Path = "TestConfigI32.Int32.Number", Value = -2147483649 }, - { File = "config.toml", Path = "TestConfigI64.Int64.Number", Value = 9223372036854775807 }, - { File = "config.toml", Path = "TestConfigI64.Int64.Number", Value = -9223372036854775808 }, - { File = "config.toml", Path = "TestConfigU8.Uint8.Number", Value = 255 }, - { File = "config.toml", Path = "TestConfigU8.Uint8.Number", Value = 256 }, - { File = "config.toml", Path = "TestConfigU8.Uint8.Number", Value = -256 }, - { File = "config.toml", Path = "TestConfigU16.Uint16.Number", Value = 65535 }, - { File = "config.toml", Path = "TestConfigU16.Uint16.Number", Value = 65536 }, - { File = "config.toml", Path = "TestConfigU16.Uint16.Number", Value = -65536 }, - { File = "config.toml", Path = "TestConfigU32.Uint32.Number", Value = 4294967295 }, - { File = "config.toml", Path = "TestConfigU32.Uint32.Number", Value = 4294967296 }, - { File = "config.toml", Path = "TestConfigU32.Uint32.Number", Value = -4294967296 }, - { File = "config.toml", Path = "TestConfigU64.Uint64.Number", Value = 9223372036854775807 }, - { File = "config.toml", Path = "TestConfigU64.Uint64.Number", Value = -9223372036854775808 }, - { File = "config.toml", Path = "TestConfigF32.Float32.Number", Value = 3.4 }, - { File = "config.toml", Path = "TestConfigF32.Float32.Number", Value = 3.4e+39 }, - { File = "config.toml", Path = "TestConfigF32.Float32.Number", Value = -3.4 }, - { File = "config.toml", Path = "TestConfigF32.Float32.Number", Value = -3.4e+40 }, - { File = "config.toml", Path = "TestConfigF64.Float64.Number", Value = 1.7e+308 }, - { File = "config.toml", Path = "TestConfigF64.Float64.Number", Value = -1.7e+308 }, - { File = "config.toml", Path = "TestConfigStruct.ConfigStruct.Description", Value = { Number = 11 } }, - { File = "config.toml", Path = "TestConfigStruct.ConfigStruct.Description", Value = { Nr = 222 } }, - { File = "config.toml", Path = "TestConfigStruct.ConfigStruct.Description", Value = { Number = "11" } }, - { File = "config.toml", Path = "TestConfigNestedStruct.ConfigNestedStruct", Value = { Text = "Overwritten text", Message = { Public = false, MessageDescription = [{ Text = "Overwritten Text1" }] } } }, - { File = "config.toml", Path = "TestConfigNestedStruct.ConfigNestedStruct.Message.MessageDescription", Value = [{ Text = "Overwritten Text1" }, { Text = "Overwritten Text2" }] }, - { File = "config.toml", Path = "TestMap.Map", Value = { "Key1" = { Number = 10 }, "Key2" = { Number = 11 } } }, - { File = "config.toml", Path = "TestMap.Map", Value = { "Key2" = { Number = 2 }, "Key3" = { Number = 3 } } }, + { File = "config.toml", Path = "TestConfigI8.Int8.Number", Value = 127 }, + { File = "config.toml", Path = "TestConfigI8.Int8.Number", Value = 128 }, + { File = "config.toml", Path = "TestConfigI8.Int8.Number", Value = -128 }, + { File = "config.toml", Path = "TestConfigI8.Int8.Number", Value = -129 }, + { File = "config.toml", Path = "TestConfigI16.Int16.Number", Value = 32767 }, + { File = "config.toml", Path = "TestConfigI16.Int16.Number", Value = 32768 }, + { File = "config.toml", Path = "TestConfigI16.Int16.Number", Value = -32768 }, + { File = "config.toml", Path = "TestConfigI16.Int16.Number", Value = -32769 }, + { File = "config.toml", Path = "TestConfigI32.Int32.Number", Value = 2147483647 }, + { File = "config.toml", Path = "TestConfigI32.Int32.Number", Value = 2147483648 }, + { File = "config.toml", Path = "TestConfigI32.Int32.Number", Value = -2147483648 }, + { File = "config.toml", Path = "TestConfigI32.Int32.Number", Value = -2147483649 }, + { File = "config.toml", Path = "TestConfigI64.Int64.Number", Value = 9223372036854775807 }, + { File = "config.toml", Path = "TestConfigI64.Int64.Number", Value = -9223372036854775808 }, + { File = "config.toml", Path = "TestConfigU8.Uint8.Number", Value = 255 }, + { File = "config.toml", Path = "TestConfigU8.Uint8.Number", Value = 256 }, + { File = "config.toml", Path = "TestConfigU8.Uint8.Number", Value = -256 }, + { File = "config.toml", Path = "TestConfigU16.Uint16.Number", Value = 65535 }, + { File = "config.toml", Path = "TestConfigU16.Uint16.Number", Value = 65536 }, + { File = "config.toml", Path = "TestConfigU16.Uint16.Number", Value = -65536 }, + { File = "config.toml", Path = "TestConfigU32.Uint32.Number", Value = 4294967295 }, + { File = "config.toml", Path = "TestConfigU32.Uint32.Number", Value = 4294967296 }, + { File = "config.toml", Path = "TestConfigU32.Uint32.Number", Value = -4294967296 }, + { File = "config.toml", Path = "TestConfigU64.Uint64.Number", Value = 9223372036854775807 }, + { File = "config.toml", Path = "TestConfigU64.Uint64.Number", Value = -9223372036854775808 }, + { File = "config.toml", Path = "TestConfigF32.Float32.Number", Value = 3.4 }, + { File = "config.toml", Path = "TestConfigF32.Float32.Number", Value = 3.4e+39 }, + { File = "config.toml", Path = "TestConfigF32.Float32.Number", Value = -3.4 }, + { File = "config.toml", Path = "TestConfigF32.Float32.Number", Value = -3.4e+40 }, + { File = "config.toml", Path = "TestConfigF64.Float64.Number", Value = 1.7e+308 }, + { File = "config.toml", Path = "TestConfigF64.Float64.Number", Value = -1.7e+308 }, + { File = "config.toml", Path = "TestConfigStruct.ConfigStruct.Description", Value = { Number = 11 } }, + { File = "config.toml", Path = "TestConfigStruct.ConfigStruct.Description", Value = { Nr = 222 } }, + { File = "config.toml", Path = "TestConfigStruct.ConfigStruct.Description", Value = { Number = "11" } }, + { File = "config.toml", Path = "TestConfigNestedStruct.ConfigNestedStruct", Value = { Text = "Overwritten text", Message = { Public = false, MessageDescription = [{ Text = "Overwritten Text1" }] } } }, + { File = "config.toml", Path = "TestConfigNestedStruct.ConfigNestedStruct.Message.MessageDescription", Value = [{ Text = "Overwritten Text1" }, { Text = "Overwritten Text2" }] }, + { File = "config.toml", Path = "TestMap.Map", Value = { "Key1" = { Number = 10 }, "Key2" = { Number = 11 } } }, + { File = "config.toml", Path = "TestMap.Map", Value = { "Key2" = { Number = 2 }, "Key3" = { Number = 3 } } }, + { File = "config.toml", Path = "TestArray.Strings", Value = ["x", "y", "z"] }, + { File = "config.toml", Path = "TestArray.Ints", Value = [10, 20, 30] }, + { File = "config.toml", Path = "TestArray", Value = { Strings = ["x", "y", "z"], Ints = [10, 20, 30] } }, ] diff --git a/testscommon/transactionCoordinatorMock.go b/testscommon/transactionCoordinatorMock.go index a1889b0b753..4aeac14dcff 100644 --- a/testscommon/transactionCoordinatorMock.go +++ b/testscommon/transactionCoordinatorMock.go @@ -12,7 +12,7 @@ import ( // TransactionCoordinatorMock - type TransactionCoordinatorMock struct { - ComputeTransactionTypeCalled func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) + ComputeTransactionTypeCalled func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) RequestMiniBlocksAndTransactionsCalled func(header data.HeaderHandler) RequestBlockTransactionsCalled func(body *block.Body) IsDataPreparedForProcessingCalled func(haveTime func() time.Duration) error @@ -57,9 +57,9 @@ func (tcm *TransactionCoordinatorMock) CreateReceiptsHash() ([]byte, error) { } // ComputeTransactionType - -func (tcm *TransactionCoordinatorMock) ComputeTransactionType(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { +func (tcm *TransactionCoordinatorMock) ComputeTransactionType(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { if tcm.ComputeTransactionTypeCalled == nil { - return 0, 0 + return 0, 0, false } return tcm.ComputeTransactionTypeCalled(tx) diff --git a/testscommon/txTypeHandlerMock.go b/testscommon/txTypeHandlerMock.go index 18a5a136477..bd08cc01c20 100644 --- a/testscommon/txTypeHandlerMock.go +++ b/testscommon/txTypeHandlerMock.go @@ -7,13 +7,13 @@ import ( // TxTypeHandlerMock - type TxTypeHandlerMock struct { - ComputeTransactionTypeCalled func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) + ComputeTransactionTypeCalled func(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) } // ComputeTransactionType - -func (th *TxTypeHandlerMock) ComputeTransactionType(tx data.TransactionHandler) (process.TransactionType, process.TransactionType) { +func (th *TxTypeHandlerMock) ComputeTransactionType(tx data.TransactionHandler) (process.TransactionType, process.TransactionType, bool) { if th.ComputeTransactionTypeCalled == nil { - return process.MoveBalance, process.MoveBalance + return process.MoveBalance, process.MoveBalance, false } return th.ComputeTransactionTypeCalled(tx)