From b31ee0d186dd4587ef4b00d0ad5b6b9e2835725c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 25 Aug 2023 13:04:56 +0300 Subject: [PATCH 01/13] Define "ComputeShardIdOfPubKey". Refactoring. --- server/factory/components/observerFacade.go | 4 ++++ server/factory/interface.go | 1 + server/provider/interface.go | 2 +- server/provider/networkProvider.go | 12 +++++++----- server/provider/networkProvider_test.go | 17 +++++++++++++++++ server/services/interface.go | 1 + testscommon/networkProviderMock.go | 19 ++++++++++++------- testscommon/observerFacadeMock.go | 10 +++------- 8 files changed, 46 insertions(+), 20 deletions(-) diff --git a/server/factory/components/observerFacade.go b/server/factory/components/observerFacade.go index 3f0ff6d..97dd815 100644 --- a/server/factory/components/observerFacade.go +++ b/server/factory/components/observerFacade.go @@ -11,3 +11,7 @@ type ObserverFacade struct { facade.TransactionProcessor facade.BlockProcessor } + +func (facade *ObserverFacade) ComputeShardId(pubKey []byte) uint32 { + return facade.GetShardCoordinator().ComputeId(pubKey) +} diff --git a/server/factory/interface.go b/server/factory/interface.go index e4a9940..34f9e65 100644 --- a/server/factory/interface.go +++ b/server/factory/interface.go @@ -25,6 +25,7 @@ type NetworkProvider interface { GetAccountNativeBalance(address string, options resources.AccountQueryOptions) (*resources.AccountOnBlock, error) GetAccountESDTBalance(address string, tokenIdentifier string, options resources.AccountQueryOptions) (*resources.AccountESDTBalance, error) IsAddressObserved(address string) (bool, error) + ComputeShardIdOfPubKey(pubkey []byte) uint32 ConvertPubKeyToAddress(pubkey []byte) string ConvertAddressToPubKey(address string) ([]byte, error) SendTransaction(tx *data.Transaction) (string, error) diff --git a/server/provider/interface.go b/server/provider/interface.go index 72c6dbb..ffb5de9 100644 --- a/server/provider/interface.go +++ b/server/provider/interface.go @@ -8,7 +8,7 @@ import ( type observerFacade interface { CallGetRestEndPoint(baseUrl string, path string, value interface{}) (int, error) - ComputeShardId(pubKey []byte) (uint32, error) + ComputeShardId(pubKey []byte) uint32 SendTransaction(tx *data.Transaction) (int, string, error) ComputeTransactionHash(tx *data.Transaction) (string, error) GetTransactionByHashAndSenderAddress(hash string, sender string, withEvents bool) (*transaction.ApiTransactionResult, int, error) diff --git a/server/provider/networkProvider.go b/server/provider/networkProvider.go index 68c2544..57f9c65 100644 --- a/server/provider/networkProvider.go +++ b/server/provider/networkProvider.go @@ -305,11 +305,7 @@ func (provider *networkProvider) IsAddressObserved(address string) (bool, error) return false, err } - shard, err := provider.observerFacade.ComputeShardId(pubKey) - if err != nil { - return false, err - } - + shard := provider.observerFacade.ComputeShardId(pubKey) isObservedActualShard := shard == provider.observedActualShard isObservedProjectedShard := pubKey[len(pubKey)-1] == byte(provider.observedProjectedShard) @@ -320,6 +316,12 @@ func (provider *networkProvider) IsAddressObserved(address string) (bool, error) return isObservedActualShard, nil } +// ComputeShardIdOfPubKey computes the shard ID of a public key +func (provider *networkProvider) ComputeShardIdOfPubKey(pubKey []byte) uint32 { + shard := provider.observerFacade.ComputeShardId(pubKey) + return shard +} + // ConvertPubKeyToAddress converts a public key to an address func (provider *networkProvider) ConvertPubKeyToAddress(pubkey []byte) string { return provider.pubKeyConverter.Encode(pubkey) diff --git a/server/provider/networkProvider_test.go b/server/provider/networkProvider_test.go index 1be86b3..33cabd3 100644 --- a/server/provider/networkProvider_test.go +++ b/server/provider/networkProvider_test.go @@ -1,6 +1,7 @@ package provider import ( + "encoding/hex" "errors" "strings" "testing" @@ -167,6 +168,22 @@ func TestNetworkProvider_DoGetBlockByNonce(t *testing.T) { }) } +func Test_ComputeShardIdOfPubKey(t *testing.T) { + args := createDefaultArgsNewNetworkProvider() + provider, err := NewNetworkProvider(args) + require.Nil(t, err) + require.NotNil(t, provider) + + pubKey, _ := hex.DecodeString("8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8") + require.Equal(t, uint32(0), provider.ComputeShardIdOfPubKey(pubKey)) + + pubKey, _ = hex.DecodeString("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1") + require.Equal(t, uint32(1), provider.ComputeShardIdOfPubKey(pubKey)) + + pubKey, _ = hex.DecodeString("b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba") + require.Equal(t, uint32(2), provider.ComputeShardIdOfPubKey(pubKey)) +} + func Test_ComputeTransactionFeeForMoveBalance(t *testing.T) { args := createDefaultArgsNewNetworkProvider() provider, err := NewNetworkProvider(args) diff --git a/server/services/interface.go b/server/services/interface.go index 675bb8a..6a1dd8b 100644 --- a/server/services/interface.go +++ b/server/services/interface.go @@ -24,6 +24,7 @@ type NetworkProvider interface { GetAccountNativeBalance(address string, options resources.AccountQueryOptions) (*resources.AccountOnBlock, error) GetAccountESDTBalance(address string, tokenIdentifier string, options resources.AccountQueryOptions) (*resources.AccountESDTBalance, error) IsAddressObserved(address string) (bool, error) + ComputeShardIdOfPubKey(pubkey []byte) uint32 ConvertPubKeyToAddress(pubkey []byte) string ConvertAddressToPubKey(address string) ([]byte, error) SendTransaction(tx *data.Transaction) (string, error) diff --git a/testscommon/networkProviderMock.go b/testscommon/networkProviderMock.go index fcf6d07..2fec973 100644 --- a/testscommon/networkProviderMock.go +++ b/testscommon/networkProviderMock.go @@ -244,18 +244,12 @@ func (mock *networkProviderMock) IsAddressObserved(address string) (bool, error) return false, mock.MockNextError } - shardCoordinator, err := sharding.NewMultiShardCoordinator(mock.MockNumShards, mock.MockObservedActualShard) - if err != nil { - return false, err - } - pubKey, err := mock.ConvertAddressToPubKey(address) if err != nil { return false, err } - shard := shardCoordinator.ComputeId(pubKey) - + shard := mock.ComputeShardIdOfPubKey(pubKey) isObservedActualShard := shard == mock.MockObservedActualShard isObservedProjectedShard := pubKey[len(pubKey)-1] == byte(mock.MockObservedProjectedShard) @@ -266,6 +260,17 @@ func (mock *networkProviderMock) IsAddressObserved(address string) (bool, error) return isObservedActualShard, nil } +// ComputeShardIdOfPubKey - +func (mock *networkProviderMock) ComputeShardIdOfPubKey(pubKey []byte) uint32 { + shardCoordinator, err := sharding.NewMultiShardCoordinator(mock.MockNumShards, mock.MockObservedActualShard) + if err != nil { + return 0 + } + + shard := shardCoordinator.ComputeId(pubKey) + return shard +} + // ConvertPubKeyToAddress - func (mock *networkProviderMock) ConvertPubKeyToAddress(pubkey []byte) string { return mock.pubKeyConverter.Encode(pubkey) diff --git a/testscommon/observerFacadeMock.go b/testscommon/observerFacadeMock.go index c2cf336..647b29c 100644 --- a/testscommon/observerFacadeMock.go +++ b/testscommon/observerFacadeMock.go @@ -78,18 +78,14 @@ func (mock *observerFacadeMock) CallGetRestEndPoint(baseUrl string, path string, } // ComputeShardId - -func (mock *observerFacadeMock) ComputeShardId(pubKey []byte) (uint32, error) { - if mock.MockNextError != nil { - return 0, mock.MockNextError - } - +func (mock *observerFacadeMock) ComputeShardId(pubKey []byte) uint32 { shardCoordinator, err := sharding.NewMultiShardCoordinator(mock.MockNumShards, mock.MockSelfShard) if err != nil { - return 0, err + return 0 } shard := shardCoordinator.ComputeId(pubKey) - return shard, nil + return shard } // SendTransaction - From 6a134abeb0ed76d1651234484fd05280643e9e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 25 Aug 2023 16:03:44 +0300 Subject: [PATCH 02/13] Implement "transactionEventsController.hasAnySignalError()". --- .../services/transactionEventsController.go | 15 +++++++ .../transactionEventsController_test.go | 39 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/server/services/transactionEventsController.go b/server/services/transactionEventsController.go index 03a90b9..14f80a3 100644 --- a/server/services/transactionEventsController.go +++ b/server/services/transactionEventsController.go @@ -58,6 +58,21 @@ func (controller *transactionEventsController) extractEventTransferValueOnly(tx }, nil } +func (controller *transactionEventsController) hasAnySignalError(tx *transaction.ApiTransactionResult) bool { + if !controller.hasEvents(tx) { + return false + } + + for _, event := range tx.Logs.Events { + isSignalError := event.Identifier == transactionEventSignalError + if isSignalError { + return true + } + } + + return false +} + func (controller *transactionEventsController) hasSignalErrorOfSendingValueToNonPayableContract(tx *transaction.ApiTransactionResult) bool { if !controller.hasEvents(tx) { return false diff --git a/server/services/transactionEventsController_test.go b/server/services/transactionEventsController_test.go index 2b4c3ab..7a22e2a 100644 --- a/server/services/transactionEventsController_test.go +++ b/server/services/transactionEventsController_test.go @@ -8,6 +8,33 @@ import ( "github.com/stretchr/testify/require" ) +func TestTransactionEventsController_HasAnySignalError(t *testing.T) { + networkProvider := testscommon.NewNetworkProviderMock() + controller := newTransactionEventsController(networkProvider) + + t.Run("arbitrary tx", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{} + txMatches := controller.hasAnySignalError(tx) + require.False(t, txMatches) + }) + + t.Run("tx with event 'signalError'", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + Logs: &transaction.ApiLogs{ + + Events: []*transaction.Events{ + { + Identifier: transactionEventSignalError, + }, + }, + }, + } + + txMatches := controller.hasAnySignalError(tx) + require.True(t, txMatches) + }) +} + func TestTransactionEventsController_HasSignalErrorOfSendingValueToNonPayableContract(t *testing.T) { networkProvider := testscommon.NewNetworkProviderMock() controller := newTransactionEventsController(networkProvider) @@ -85,6 +112,18 @@ func TestTransactionEventsController_HasSignalErrorOfMetaTransactionIsInvalid(t }) } +func Test(t *testing.T) { + event := transaction.Events{ + Identifier: transactionEventSignalError, + Topics: [][]byte{ + []byte("foo"), + }, + } + + require.True(t, eventHasTopic(&event, "foo")) + require.False(t, eventHasTopic(&event, "bar")) +} + func TestEventHasTopic(t *testing.T) { event := transaction.Events{ Identifier: transactionEventSignalError, From 0da4907152563288db5add3b2fa8e61fe137ab1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 25 Aug 2023 16:44:41 +0300 Subject: [PATCH 03/13] Define "isRelayedV1Transaction()". --- server/services/relayedTransactions.go | 17 ++++++++++++++ server/services/relayedTransactions_test.go | 25 +++++++++++++++++++++ version/constants.go | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 server/services/relayedTransactions.go create mode 100644 server/services/relayedTransactions_test.go diff --git a/server/services/relayedTransactions.go b/server/services/relayedTransactions.go new file mode 100644 index 0000000..fa9ea28 --- /dev/null +++ b/server/services/relayedTransactions.go @@ -0,0 +1,17 @@ +package services + +import ( + "encoding/hex" + "encoding/json" + "math/big" + "strings" + + "github.com/multiversx/mx-chain-core-go/data/transaction" +) + +func isRelayedV1Transaction(tx *transaction.ApiTransactionResult) bool { + return (tx.Type == string(transaction.TxTypeNormal)) && + (tx.ProcessingTypeOnSource == transactionProcessingTypeRelayed) && + (tx.ProcessingTypeOnDestination == transactionProcessingTypeRelayed) +} + diff --git a/server/services/relayedTransactions_test.go b/server/services/relayedTransactions_test.go new file mode 100644 index 0000000..49836a0 --- /dev/null +++ b/server/services/relayedTransactions_test.go @@ -0,0 +1,25 @@ +package services + +import ( + "testing" + + "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/stretchr/testify/require" +) + +func Test_IsRelayedV1Transaction(t *testing.T) { + t.Run("arbitrary tx", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{} + require.False(t, isRelayedV1Transaction(tx)) + }) + + t.Run("relayed v1 tx", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + Type: string(transaction.TxTypeNormal), + ProcessingTypeOnSource: transactionProcessingTypeRelayed, + ProcessingTypeOnDestination: transactionProcessingTypeRelayed, + } + + require.True(t, isRelayedV1Transaction(tx)) + }) +} diff --git a/version/constants.go b/version/constants.go index 488e1e8..7148031 100644 --- a/version/constants.go +++ b/version/constants.go @@ -7,5 +7,5 @@ const ( var ( // RosettaMiddlewareVersion is the version of this package (application) - RosettaMiddlewareVersion = "v0.4.2" + RosettaMiddlewareVersion = "v0.4.4" ) From 855511b4f1e3e05b62ea50d0ba43e1d2b9099dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Sat, 26 Aug 2023 18:39:26 +0300 Subject: [PATCH 04/13] Parse inner TX of relayed TX V1. --- server/services/constants.go | 3 ++- server/services/errors.go | 1 + server/services/relayedTransactions.go | 28 +++++++++++++++++++++ server/services/relayedTransactions_test.go | 25 ++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/server/services/constants.go b/server/services/constants.go index a991f59..4fe7736 100644 --- a/server/services/constants.go +++ b/server/services/constants.go @@ -12,7 +12,8 @@ var ( transactionProcessingTypeMoveBalance = "MoveBalance" builtInFunctionClaimDeveloperRewards = "ClaimDeveloperRewards" refundGasMessage = "refundedGas" - sendingValueToNonPayableContractDataPrefix = "@" + hex.EncodeToString([]byte("sending value to non payable contract")) + argumentsSeparator = "@" + sendingValueToNonPayableContractDataPrefix = argumentsSeparator + hex.EncodeToString([]byte("sending value to non payable contract")) emptyHash = strings.Repeat("0", 64) nodeVersionForOfflineRosetta = "N / A" ) diff --git a/server/services/errors.go b/server/services/errors.go index bdd06a7..76f3bab 100644 --- a/server/services/errors.go +++ b/server/services/errors.go @@ -201,3 +201,4 @@ func (factory *errFactory) getPrototypeByCode(code errCode) errPrototype { var errEventNotFound = errors.New("transaction event not found") var errCannotRecognizeEvent = errors.New("cannot recognize transaction event") +var errCannotParseRelayedV1 = errors.New("cannot parse relayed V1 transaction") diff --git a/server/services/relayedTransactions.go b/server/services/relayedTransactions.go index fa9ea28..af83ca0 100644 --- a/server/services/relayedTransactions.go +++ b/server/services/relayedTransactions.go @@ -9,9 +9,37 @@ import ( "github.com/multiversx/mx-chain-core-go/data/transaction" ) +// innerTransactionOfRelayedV1 is used to parse the inner transaction of a relayed V1 transaction, and holds only the fields handled by Rosetta. +type innerTransactionOfRelayedV1 struct { + Nonce uint64 `json:"nonce"` + Value big.Int `json:"value"` + ReceiverPubKey []byte `json:"receiver"` + SenderPubKey []byte `json:"sender"` +} + func isRelayedV1Transaction(tx *transaction.ApiTransactionResult) bool { return (tx.Type == string(transaction.TxTypeNormal)) && (tx.ProcessingTypeOnSource == transactionProcessingTypeRelayed) && (tx.ProcessingTypeOnDestination == transactionProcessingTypeRelayed) } +func parseInnerTxOfRelayedV1(tx *transaction.ApiTransactionResult) (*innerTransactionOfRelayedV1, error) { + subparts := strings.Split(string(tx.Data), argumentsSeparator) + if len(subparts) != 2 { + return nil, errCannotParseRelayedV1 + } + + innerTxPayloadDecoded, err := hex.DecodeString(subparts[1]) + if err != nil { + return nil, err + } + + var innerTx innerTransactionOfRelayedV1 + + err = json.Unmarshal(innerTxPayloadDecoded, &innerTx) + if err != nil { + return nil, err + } + + return &innerTx, nil +} diff --git a/server/services/relayedTransactions_test.go b/server/services/relayedTransactions_test.go index 49836a0..dd416fb 100644 --- a/server/services/relayedTransactions_test.go +++ b/server/services/relayedTransactions_test.go @@ -1,6 +1,7 @@ package services import ( + "encoding/hex" "testing" "github.com/multiversx/mx-chain-core-go/data/transaction" @@ -23,3 +24,27 @@ func Test_IsRelayedV1Transaction(t *testing.T) { require.True(t, isRelayedV1Transaction(tx)) }) } + +func Test_ParseInnerTxOfRelayedV1(t *testing.T) { + t.Run("arbitrary tx", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{} + innerTx, err := parseInnerTxOfRelayedV1(tx) + require.ErrorIs(t, err, errCannotParseRelayedV1) + require.Nil(t, innerTx) + }) + + t.Run("relayed v1 tx (Alice to Bob, 1 EGLD)", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + Data: []byte("relayedTx@7b226e6f6e6365223a372c2273656e646572223a2241546c484c76396f686e63616d433877673970645168386b77704742356a6949496f3349484b594e6165453d222c227265636569766572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c2276616c7565223a313030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a222b4161696451714c4d6150314b4f414d42506a557554774955775137724f6d62586976446c6b4944775a315a48353053366377714a4163576a496a744f732f435177502b79597a6643356730637571526b55437842413d3d222c22636861696e4944223a224d513d3d222c2276657273696f6e223a327d"), + } + + innerTx, err := parseInnerTxOfRelayedV1(tx) + require.NoError(t, err) + require.NotNil(t, innerTx) + + require.Equal(t, uint64(7), innerTx.Nonce) + require.Equal(t, "1000000000000000000", innerTx.Value.String()) + require.Equal(t, "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", hex.EncodeToString(innerTx.SenderPubKey)) + require.Equal(t, "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", hex.EncodeToString(innerTx.ReceiverPubKey)) + }) +} From 85a9d986c88235725e46df3582e77487f3561baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Sat, 26 Aug 2023 19:40:40 +0300 Subject: [PATCH 05/13] Refactor normalTxToRosetta(). Return err, as well. --- server/services/transactionsTransformer.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/server/services/transactionsTransformer.go b/server/services/transactionsTransformer.go index 2d34c80..b8b8478 100644 --- a/server/services/transactionsTransformer.go +++ b/server/services/transactionsTransformer.go @@ -84,10 +84,14 @@ func (transformer *transactionsTransformer) transformTxsFromBlock(block *api.Blo func (transformer *transactionsTransformer) txToRosettaTx(tx *transaction.ApiTransactionResult, txsInBlock []*transaction.ApiTransactionResult) (*types.Transaction, error) { var rosettaTx *types.Transaction + var err error switch tx.Type { case string(transaction.TxTypeNormal): - rosettaTx = transformer.moveBalanceTxToRosetta(tx) + rosettaTx, err = transformer.normalTxToRosetta(tx) + if err != nil { + return nil, err + } case string(transaction.TxTypeReward): rosettaTx = transformer.rewardTxToRosettaTx(tx) case string(transaction.TxTypeUnsigned): @@ -98,7 +102,7 @@ func (transformer *transactionsTransformer) txToRosettaTx(tx *transaction.ApiTra return nil, fmt.Errorf("unknown transaction type: %s", tx.Type) } - err := transformer.addOperationsGivenTransactionEvents(tx, rosettaTx) + err = transformer.addOperationsGivenTransactionEvents(tx, rosettaTx) if err != nil { return nil, err } @@ -167,7 +171,7 @@ func (transformer *transactionsTransformer) rewardTxToRosettaTx(tx *transaction. } } -func (transformer *transactionsTransformer) moveBalanceTxToRosetta(tx *transaction.ApiTransactionResult) *types.Transaction { +func (transformer *transactionsTransformer) normalTxToRosetta(tx *transaction.ApiTransactionResult) (*types.Transaction, error) { hasValue := tx.Value != "0" operations := make([]*types.Operation, 0) @@ -195,7 +199,7 @@ func (transformer *transactionsTransformer) moveBalanceTxToRosetta(tx *transacti TransactionIdentifier: hashToTransactionIdentifier(tx.Hash), Operations: operations, Metadata: extractTransactionMetadata(tx), - } + }, nil } func (transformer *transactionsTransformer) refundReceiptToRosettaTx(receipt *transaction.ApiReceipt) (*types.Transaction, error) { From 6b9550d10b2e0869143322271f087f1d7f86ab3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Sat, 26 Aug 2023 19:41:02 +0300 Subject: [PATCH 06/13] Refactor, remove duplication. --- server/services/transactionFilters.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/services/transactionFilters.go b/server/services/transactionFilters.go index 900e65d..1c4b479 100644 --- a/server/services/transactionFilters.go +++ b/server/services/transactionFilters.go @@ -40,10 +40,7 @@ func filterOutIntrashardRelayedTransactionAlreadyHeldInInvalidMiniblock(txs []*t } for _, tx := range txs { - isRelayedTransaction := (tx.Type == string(transaction.TxTypeNormal)) && - (tx.ProcessingTypeOnSource == transactionProcessingTypeRelayed) && - (tx.ProcessingTypeOnDestination == transactionProcessingTypeRelayed) - + isRelayedTransaction := isRelayedV1Transaction(tx) _, alreadyHeldInInvalidMiniblock := invalidTxs[tx.Hash] if isRelayedTransaction && alreadyHeldInInvalidMiniblock { From 725dc2f6d8dbb09e5c0549e89c7273b4aecb166a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Sat, 26 Aug 2023 23:57:35 +0300 Subject: [PATCH 07/13] In TX features detector, retain "network provider" as a member. --- server/services/transactionsFeaturesDetector.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/server/services/transactionsFeaturesDetector.go b/server/services/transactionsFeaturesDetector.go index 33b578b..d224352 100644 --- a/server/services/transactionsFeaturesDetector.go +++ b/server/services/transactionsFeaturesDetector.go @@ -7,17 +7,19 @@ import ( ) type transactionsFeaturesDetector struct { + networkProvider NetworkProvider eventsController *transactionEventsController } func newTransactionsFeaturesDetector(provider NetworkProvider) *transactionsFeaturesDetector { return &transactionsFeaturesDetector{ + networkProvider: provider, eventsController: newTransactionEventsController(provider), } } // Example SCRs can be found here: https://api.multiversx.com/transactions?function=ClaimDeveloperRewards -func (extractor *transactionsFeaturesDetector) doesContractResultHoldRewardsOfClaimDeveloperRewards( +func (detector *transactionsFeaturesDetector) doesContractResultHoldRewardsOfClaimDeveloperRewards( contractResult *transaction.ApiTransactionResult, allTransactionsInBlock []*transaction.ApiTransactionResult, ) bool { @@ -44,7 +46,7 @@ func (extractor *transactionsFeaturesDetector) doesContractResultHoldRewardsOfCl // that only consume the "data movement" component of the gas: // - "sending value to non-payable contract" // - "meta transaction is invalid" -func (extractor *transactionsFeaturesDetector) isInvalidTransactionOfTypeMoveBalanceThatOnlyConsumesDataMovementGas(tx *transaction.ApiTransactionResult) bool { +func (detector *transactionsFeaturesDetector) isInvalidTransactionOfTypeMoveBalanceThatOnlyConsumesDataMovementGas(tx *transaction.ApiTransactionResult) bool { isInvalid := tx.Type == string(transaction.TxTypeInvalid) isMoveBalance := tx.ProcessingTypeOnSource == transactionProcessingTypeMoveBalance && tx.ProcessingTypeOnDestination == transactionProcessingTypeMoveBalance @@ -53,7 +55,7 @@ func (extractor *transactionsFeaturesDetector) isInvalidTransactionOfTypeMoveBal } // TODO: Analyze whether we can simplify the conditions below, or possibly discard them completely / replace them with simpler ones. - withSendingValueToNonPayableContract := extractor.eventsController.hasSignalErrorOfSendingValueToNonPayableContract(tx) - withMetaTransactionIsInvalid := extractor.eventsController.hasSignalErrorOfMetaTransactionIsInvalid(tx) + withSendingValueToNonPayableContract := detector.eventsController.hasSignalErrorOfSendingValueToNonPayableContract(tx) + withMetaTransactionIsInvalid := detector.eventsController.hasSignalErrorOfMetaTransactionIsInvalid(tx) return withSendingValueToNonPayableContract || withMetaTransactionIsInvalid } From e40afbbd92f6493f5de4dff3ca4734a3e40c9284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 29 Aug 2023 09:15:52 +0300 Subject: [PATCH 08/13] Implement & test "isRelayedTransactionCompletelyIntrashardWithSignalError". --- server/provider/networkProvider_test.go | 12 +--- .../services/transactionsFeaturesDetector.go | 15 +++++ .../transactionsFeaturesDetector_test.go | 65 ++++++++++++++++++- testscommon/addresses.go | 7 ++ 4 files changed, 89 insertions(+), 10 deletions(-) diff --git a/server/provider/networkProvider_test.go b/server/provider/networkProvider_test.go index 33cabd3..523801d 100644 --- a/server/provider/networkProvider_test.go +++ b/server/provider/networkProvider_test.go @@ -1,7 +1,6 @@ package provider import ( - "encoding/hex" "errors" "strings" "testing" @@ -174,14 +173,9 @@ func Test_ComputeShardIdOfPubKey(t *testing.T) { require.Nil(t, err) require.NotNil(t, provider) - pubKey, _ := hex.DecodeString("8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8") - require.Equal(t, uint32(0), provider.ComputeShardIdOfPubKey(pubKey)) - - pubKey, _ = hex.DecodeString("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1") - require.Equal(t, uint32(1), provider.ComputeShardIdOfPubKey(pubKey)) - - pubKey, _ = hex.DecodeString("b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba") - require.Equal(t, uint32(2), provider.ComputeShardIdOfPubKey(pubKey)) + require.Equal(t, uint32(0), provider.ComputeShardIdOfPubKey(testscommon.TestPubKeyBob)) + require.Equal(t, uint32(1), provider.ComputeShardIdOfPubKey(testscommon.TestPubKeyAlice)) + require.Equal(t, uint32(2), provider.ComputeShardIdOfPubKey(testscommon.TestPubKeyCarol)) } func Test_ComputeTransactionFeeForMoveBalance(t *testing.T) { diff --git a/server/services/transactionsFeaturesDetector.go b/server/services/transactionsFeaturesDetector.go index d224352..f22549d 100644 --- a/server/services/transactionsFeaturesDetector.go +++ b/server/services/transactionsFeaturesDetector.go @@ -59,3 +59,18 @@ func (detector *transactionsFeaturesDetector) isInvalidTransactionOfTypeMoveBala withMetaTransactionIsInvalid := detector.eventsController.hasSignalErrorOfMetaTransactionIsInvalid(tx) return withSendingValueToNonPayableContract || withMetaTransactionIsInvalid } + +func (detector *transactionsFeaturesDetector) isRelayedTransactionCompletelyIntrashardWithSignalError(tx *transaction.ApiTransactionResult, innerTx *innerTransactionOfRelayedV1) bool { + innerTxSenderShard := detector.networkProvider.ComputeShardIdOfPubKey(innerTx.SenderPubKey) + innerTxReceiverShard := detector.networkProvider.ComputeShardIdOfPubKey(innerTx.ReceiverPubKey) + + isCompletelyIntrashard := tx.SourceShard == tx.DestinationShard && + innerTxSenderShard == innerTxReceiverShard && + innerTxSenderShard == tx.SourceShard + if !isCompletelyIntrashard { + return false + } + + isWithSignalError := detector.eventsController.hasAnySignalError(tx) + return isWithSignalError +} diff --git a/server/services/transactionsFeaturesDetector_test.go b/server/services/transactionsFeaturesDetector_test.go index 8b50aaa..89b598d 100644 --- a/server/services/transactionsFeaturesDetector_test.go +++ b/server/services/transactionsFeaturesDetector_test.go @@ -1,6 +1,7 @@ package services import ( + "encoding/hex" "testing" "github.com/multiversx/mx-chain-core-go/data/transaction" @@ -8,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestFeaturesDetector_IsInvalidTransactionOfTypeMoveBalanceThatOnlyConsumesDataMovementGas(t *testing.T) { +func TestTransactionsFeaturesDetector_IsInvalidTransactionOfTypeMoveBalanceThatOnlyConsumesDataMovementGas(t *testing.T) { networkProvider := testscommon.NewNetworkProviderMock() detector := newTransactionsFeaturesDetector(networkProvider) @@ -74,3 +75,65 @@ func TestFeaturesDetector_IsInvalidTransactionOfTypeMoveBalanceThatOnlyConsumesD require.True(t, featureDetected) }) } + +func TestTransactionsFeaturesDetector_IsRelayedTransactionCompletelyIntrashardWithSignalError(t *testing.T) { + networkProvider := testscommon.NewNetworkProviderMock() + detector := newTransactionsFeaturesDetector(networkProvider) + + pubkeyAShard0, _ := hex.DecodeString("8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8") + pubkeyBShard0, _ := hex.DecodeString("e32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60") + pubkeyShard1, _ := hex.DecodeString("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1") + pubkeyShard2, _ := hex.DecodeString("b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba") + + t.Run("arbitrary relayed tx", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + SourceShard: 0, + DestinationShard: 1, + } + + innerTx := &innerTransactionOfRelayedV1{ + SenderPubKey: pubkeyShard1, + ReceiverPubKey: pubkeyShard2, + } + + featureDetected := detector.isRelayedTransactionCompletelyIntrashardWithSignalError(tx, innerTx) + require.False(t, featureDetected) + }) + + t.Run("completely intrashard relayed tx, but no signal error", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + SourceShard: 0, + DestinationShard: 0, + } + + innerTx := &innerTransactionOfRelayedV1{ + SenderPubKey: pubkeyAShard0, + ReceiverPubKey: pubkeyBShard0, + } + + featureDetected := detector.isRelayedTransactionCompletelyIntrashardWithSignalError(tx, innerTx) + require.False(t, featureDetected) + }) + + t.Run("completely intrashard relayed tx, with signal error", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + SourceShard: 0, + DestinationShard: 0, + Logs: &transaction.ApiLogs{ + Events: []*transaction.Events{ + { + Identifier: transactionEventSignalError, + }, + }, + }, + } + + innerTx := &innerTransactionOfRelayedV1{ + SenderPubKey: pubkeyAShard0, + ReceiverPubKey: pubkeyBShard0, + } + + featureDetected := detector.isRelayedTransactionCompletelyIntrashardWithSignalError(tx, innerTx) + require.True(t, featureDetected) + }) +} diff --git a/testscommon/addresses.go b/testscommon/addresses.go index 0400136..0d26d91 100644 --- a/testscommon/addresses.go +++ b/testscommon/addresses.go @@ -8,6 +8,13 @@ var ( // TestAddressBob is a test address TestAddressBob = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" + // TestPubKeyBob is a test pubkey + TestPubKeyBob, _ = RealWorldBech32PubkeyConverter.Decode(TestAddressBob) + + // TestAddressCarol is a test address + TestAddressCarol = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8" + // TestPubKeyCarol is a test pubkey + TestPubKeyCarol, _ = RealWorldBech32PubkeyConverter.Decode(TestAddressCarol) // TestAddressOfContract is a test address TestAddressOfContract = "erd1qqqqqqqqqqqqqpgqfejaxfh4ktp8mh8s77pl90dq0uzvh2vk396qlcwepw" From 9032f4219b1b3089447b58f89a2d7df7eae7645c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 29 Aug 2023 11:09:10 +0300 Subject: [PATCH 09/13] Handle relayed TX, intra-shard, with signal error. --- server/services/relayedTransactions_test.go | 6 +- .../transactionsFeaturesDetector_test.go | 18 ++-- server/services/transactionsTransformer.go | 33 +++++++ .../services/transactionsTransformer_test.go | 96 ++++++++++++++++++- testscommon/addresses.go | 36 +++++++ 5 files changed, 173 insertions(+), 16 deletions(-) diff --git a/server/services/relayedTransactions_test.go b/server/services/relayedTransactions_test.go index dd416fb..029db0b 100644 --- a/server/services/relayedTransactions_test.go +++ b/server/services/relayedTransactions_test.go @@ -1,10 +1,10 @@ package services import ( - "encoding/hex" "testing" "github.com/multiversx/mx-chain-core-go/data/transaction" + "github.com/multiversx/mx-chain-rosetta/testscommon" "github.com/stretchr/testify/require" ) @@ -44,7 +44,7 @@ func Test_ParseInnerTxOfRelayedV1(t *testing.T) { require.Equal(t, uint64(7), innerTx.Nonce) require.Equal(t, "1000000000000000000", innerTx.Value.String()) - require.Equal(t, "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", hex.EncodeToString(innerTx.SenderPubKey)) - require.Equal(t, "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", hex.EncodeToString(innerTx.ReceiverPubKey)) + require.Equal(t, testscommon.TestPubKeyAlice, innerTx.SenderPubKey) + require.Equal(t, testscommon.TestPubKeyBob, innerTx.ReceiverPubKey) }) } diff --git a/server/services/transactionsFeaturesDetector_test.go b/server/services/transactionsFeaturesDetector_test.go index 89b598d..440fc8e 100644 --- a/server/services/transactionsFeaturesDetector_test.go +++ b/server/services/transactionsFeaturesDetector_test.go @@ -1,7 +1,6 @@ package services import ( - "encoding/hex" "testing" "github.com/multiversx/mx-chain-core-go/data/transaction" @@ -80,11 +79,6 @@ func TestTransactionsFeaturesDetector_IsRelayedTransactionCompletelyIntrashardWi networkProvider := testscommon.NewNetworkProviderMock() detector := newTransactionsFeaturesDetector(networkProvider) - pubkeyAShard0, _ := hex.DecodeString("8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8") - pubkeyBShard0, _ := hex.DecodeString("e32afedc904fe1939746ad973beb383563cf63642ba669b3040f9b9428a5ed60") - pubkeyShard1, _ := hex.DecodeString("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1") - pubkeyShard2, _ := hex.DecodeString("b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba") - t.Run("arbitrary relayed tx", func(t *testing.T) { tx := &transaction.ApiTransactionResult{ SourceShard: 0, @@ -92,8 +86,8 @@ func TestTransactionsFeaturesDetector_IsRelayedTransactionCompletelyIntrashardWi } innerTx := &innerTransactionOfRelayedV1{ - SenderPubKey: pubkeyShard1, - ReceiverPubKey: pubkeyShard2, + SenderPubKey: testscommon.TestUserShard1.PubKey, + ReceiverPubKey: testscommon.TestUserShard2.PubKey, } featureDetected := detector.isRelayedTransactionCompletelyIntrashardWithSignalError(tx, innerTx) @@ -107,8 +101,8 @@ func TestTransactionsFeaturesDetector_IsRelayedTransactionCompletelyIntrashardWi } innerTx := &innerTransactionOfRelayedV1{ - SenderPubKey: pubkeyAShard0, - ReceiverPubKey: pubkeyBShard0, + SenderPubKey: testscommon.TestUserAShard0.PubKey, + ReceiverPubKey: testscommon.TestUserBShard0.PubKey, } featureDetected := detector.isRelayedTransactionCompletelyIntrashardWithSignalError(tx, innerTx) @@ -129,8 +123,8 @@ func TestTransactionsFeaturesDetector_IsRelayedTransactionCompletelyIntrashardWi } innerTx := &innerTransactionOfRelayedV1{ - SenderPubKey: pubkeyAShard0, - ReceiverPubKey: pubkeyBShard0, + SenderPubKey: testscommon.TestUserAShard0.PubKey, + ReceiverPubKey: testscommon.TestUserBShard0.PubKey, } featureDetected := detector.isRelayedTransactionCompletelyIntrashardWithSignalError(tx, innerTx) diff --git a/server/services/transactionsTransformer.go b/server/services/transactionsTransformer.go index b8b8478..05ce5b3 100644 --- a/server/services/transactionsTransformer.go +++ b/server/services/transactionsTransformer.go @@ -195,6 +195,13 @@ func (transformer *transactionsTransformer) normalTxToRosetta(tx *transaction.Ap Amount: transformer.extension.valueToNativeAmount("-" + tx.InitiallyPaidFee), }) + innerTxOperationsIfRelayedCompletelyIntrashardWithSignalError, err := transformer.extractInnerTxOperationsIfRelayedCompletelyIntrashardWithSignalError(tx) + if err != nil { + return nil, err + } + + operations = append(operations, innerTxOperationsIfRelayedCompletelyIntrashardWithSignalError...) + return &types.Transaction{ TransactionIdentifier: hashToTransactionIdentifier(tx.Hash), Operations: operations, @@ -202,6 +209,32 @@ func (transformer *transactionsTransformer) normalTxToRosetta(tx *transaction.Ap }, nil } +func (transformer *transactionsTransformer) extractInnerTxOperationsIfRelayedCompletelyIntrashardWithSignalError(tx *transaction.ApiTransactionResult) ([]*types.Operation, error) { + isRelayedTransaction := isRelayedV1Transaction(tx) + if !isRelayedTransaction { + return nil, nil + } + + innerTx, err := parseInnerTxOfRelayedV1(tx) + if err != nil { + return nil, err + } + + if !transformer.featuresDetector.isRelayedTransactionCompletelyIntrashardWithSignalError(tx, innerTx) { + return nil, nil + } + + senderAddress := transformer.provider.ConvertPubKeyToAddress(innerTx.SenderPubKey) + + return []*types.Operation{ + { + Type: opTransfer, + Account: addressToAccountIdentifier(senderAddress), + Amount: transformer.extension.valueToNativeAmount("-" + innerTx.Value.String()), + }, + }, nil +} + func (transformer *transactionsTransformer) refundReceiptToRosettaTx(receipt *transaction.ApiReceipt) (*types.Transaction, error) { receiptHash, err := transformer.provider.ComputeReceiptHash(receipt) if err != nil { diff --git a/server/services/transactionsTransformer_test.go b/server/services/transactionsTransformer_test.go index d5fe59a..a7ed32b 100644 --- a/server/services/transactionsTransformer_test.go +++ b/server/services/transactionsTransformer_test.go @@ -9,7 +9,101 @@ import ( "github.com/stretchr/testify/require" ) -// TODO: Add more tests. +func TestTransactionsTransformer_NormalTxToRosettaTx(t *testing.T) { + networkProvider := testscommon.NewNetworkProviderMock() + extension := newNetworkProviderExtension(networkProvider) + transformer := newTransactionsTransformer(networkProvider) + + t.Run("move balance tx", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + Hash: "aaaa", + Sender: testscommon.TestAddressAlice, + Receiver: testscommon.TestAddressBob, + Value: "1234", + InitiallyPaidFee: "50000000000000", + } + + expectedRosettaTx := &types.Transaction{ + TransactionIdentifier: hashToTransactionIdentifier("aaaa"), + Operations: []*types.Operation{ + { + Type: opTransfer, + Account: addressToAccountIdentifier(testscommon.TestAddressAlice), + Amount: extension.valueToNativeAmount("-1234"), + }, + { + Type: opTransfer, + Account: addressToAccountIdentifier(testscommon.TestAddressBob), + Amount: extension.valueToNativeAmount("1234"), + }, + { + Type: opFee, + Account: addressToAccountIdentifier(testscommon.TestAddressAlice), + Amount: extension.valueToNativeAmount("-50000000000000"), + }, + }, + Metadata: extractTransactionMetadata(tx), + } + + rosettaTx, err := transformer.normalTxToRosetta(tx) + require.NoError(t, err) + require.Equal(t, expectedRosettaTx, rosettaTx) + }) + + t.Run("relayed tx, completely intrashard, with signal error", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + Type: string(transaction.TxTypeNormal), + ProcessingTypeOnSource: transactionProcessingTypeRelayed, + ProcessingTypeOnDestination: transactionProcessingTypeRelayed, + Hash: "aaaa", + Sender: testscommon.TestUserAShard0.Address, + Receiver: testscommon.TestUserBShard0.Address, + SourceShard: 0, + DestinationShard: 0, + Value: "1234", + InitiallyPaidFee: "50000000000000", + Data: []byte("relayedTx@7b226e6f6e6365223a372c2273656e646572223a226e69424758747949504349644a78793373796c6c455a474c78506a503148734a45646e43732b6d726577413d222c227265636569766572223a224141414141414141414141464145356c3079623173734a3933504433672f4b396f48384579366d576958513d222c2276616c7565223a313030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a226e6830743338585a77614b6a725878446969716f6d364d6a5671724d612f6b70767474696a33692b5a6d43492f3778626830596762363548424151445744396f7036575567674541755430756e5253595736455341413d3d222c22636861696e4944223a224d513d3d222c2276657273696f6e223a327d"), + Logs: &transaction.ApiLogs{ + Events: []*transaction.Events{ + { + Identifier: transactionEventSignalError, + }, + }, + }, + } + + expectedRosettaTx := &types.Transaction{ + TransactionIdentifier: hashToTransactionIdentifier("aaaa"), + Operations: []*types.Operation{ + { + Type: opTransfer, + Account: addressToAccountIdentifier(testscommon.TestUserAShard0.Address), + Amount: extension.valueToNativeAmount("-1234"), + }, + { + Type: opTransfer, + Account: addressToAccountIdentifier(testscommon.TestUserBShard0.Address), + Amount: extension.valueToNativeAmount("1234"), + }, + { + Type: opFee, + Account: addressToAccountIdentifier(testscommon.TestUserAShard0.Address), + Amount: extension.valueToNativeAmount("-50000000000000"), + }, + { + Type: opTransfer, + Account: addressToAccountIdentifier(testscommon.TestUserCShard0.Address), + Amount: extension.valueToNativeAmount("-1000000000000000000"), + }, + }, + Metadata: extractTransactionMetadata(tx), + } + + rosettaTx, err := transformer.normalTxToRosetta(tx) + require.NoError(t, err) + require.Equal(t, expectedRosettaTx, rosettaTx) + }) +} func TestTransactionsTransformer_UnsignedTxToRosettaTx(t *testing.T) { networkProvider := testscommon.NewNetworkProviderMock() diff --git a/testscommon/addresses.go b/testscommon/addresses.go index 0d26d91..ad5e2a4 100644 --- a/testscommon/addresses.go +++ b/testscommon/addresses.go @@ -1,21 +1,57 @@ package testscommon var ( + // TODO: use "testAccount", instead // TestAddressAlice is a test address TestAddressAlice = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" // TestPubKeyAlice is a test pubkey TestPubKeyAlice, _ = RealWorldBech32PubkeyConverter.Decode(TestAddressAlice) + // TODO: use "testAccount", instead // TestAddressBob is a test address TestAddressBob = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx" // TestPubKeyBob is a test pubkey TestPubKeyBob, _ = RealWorldBech32PubkeyConverter.Decode(TestAddressBob) + // TODO: use "testAccount", instead // TestAddressCarol is a test address TestAddressCarol = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8" // TestPubKeyCarol is a test pubkey TestPubKeyCarol, _ = RealWorldBech32PubkeyConverter.Decode(TestAddressCarol) + // TODO: use "testAccount", instead // TestAddressOfContract is a test address TestAddressOfContract = "erd1qqqqqqqqqqqqqpgqfejaxfh4ktp8mh8s77pl90dq0uzvh2vk396qlcwepw" + + // TestUserAShard0 is a test account (user) + TestUserAShard0 = newTestAccount("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx") + + // TestUserBShard0 is a test account (user) + TestUserBShard0 = newTestAccount("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa") + + // TestUserCShard0 is a test account (user) + TestUserCShard0 = newTestAccount("erd1ncsyvhku3q7zy8f8rjmmx2t9zxgch38cel28kzg3m8pt86dt0vqqecw0gy") + + // TestContractShard0 is a test account (contract) + TestContractShard0 = newTestAccount("erd1qqqqqqqqqqqqqpgqfejaxfh4ktp8mh8s77pl90dq0uzvh2vk396qlcwepw") + + // TestUserShard1 is a test account (user) + TestUserShard1 = newTestAccount("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") + + // TestUserShard2 is a test account (user) + TestUserShard2 = newTestAccount("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8") ) + +type testAccount struct { + Address string + PubKey []byte +} + +func newTestAccount(address string) *testAccount { + pubKey, _ := RealWorldBech32PubkeyConverter.Decode(address) + + return &testAccount{ + Address: address, + PubKey: pubKey, + } +} From c3d66e2ea9cd914e79a22af8761fde617e30dd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 29 Aug 2023 12:51:17 +0300 Subject: [PATCH 10/13] Zero-value checks. Additional tests. --- server/services/common.go | 8 ++ server/services/common_test.go | 7 ++ server/services/transactionsTransformer.go | 6 +- .../services/transactionsTransformer_test.go | 93 ++++++++++++++++--- 4 files changed, 101 insertions(+), 13 deletions(-) diff --git a/server/services/common.go b/server/services/common.go index 90ce158..f24789a 100644 --- a/server/services/common.go +++ b/server/services/common.go @@ -50,6 +50,14 @@ func isZeroAmount(amount string) bool { return false } +func isZeroBigInt(value *big.Int) bool { + if value == nil { + return true + } + + return value.Sign() == 0 +} + func getMagnitudeOfAmount(amount string) string { return strings.Trim(amount, "-") } diff --git a/server/services/common_test.go b/server/services/common_test.go index 9c17b39..4e0f1b8 100644 --- a/server/services/common_test.go +++ b/server/services/common_test.go @@ -42,6 +42,13 @@ func Test_IsZeroAmount(t *testing.T) { require.False(t, isZeroAmount("-1")) } +func Test_IsZeroBigInt(t *testing.T) { + require.True(t, isZeroBigInt(big.NewInt(0))) + require.True(t, isZeroBigInt(nil)) + require.False(t, isZeroBigInt(big.NewInt(42))) + require.False(t, isZeroBigInt(big.NewInt(-42))) +} + func Test_GetMagnitudeOfAmount(t *testing.T) { require.Equal(t, "100", getMagnitudeOfAmount("100")) require.Equal(t, "100", getMagnitudeOfAmount("-100")) diff --git a/server/services/transactionsTransformer.go b/server/services/transactionsTransformer.go index 05ce5b3..6298ae5 100644 --- a/server/services/transactionsTransformer.go +++ b/server/services/transactionsTransformer.go @@ -172,7 +172,7 @@ func (transformer *transactionsTransformer) rewardTxToRosettaTx(tx *transaction. } func (transformer *transactionsTransformer) normalTxToRosetta(tx *transaction.ApiTransactionResult) (*types.Transaction, error) { - hasValue := tx.Value != "0" + hasValue := !isZeroAmount(tx.Value) operations := make([]*types.Operation, 0) if hasValue { @@ -220,6 +220,10 @@ func (transformer *transactionsTransformer) extractInnerTxOperationsIfRelayedCom return nil, err } + if isZeroBigInt(&innerTx.Value) { + return nil, nil + } + if !transformer.featuresDetector.isRelayedTransactionCompletelyIntrashardWithSignalError(tx, innerTx) { return nil, nil } diff --git a/server/services/transactionsTransformer_test.go b/server/services/transactionsTransformer_test.go index a7ed32b..c336d30 100644 --- a/server/services/transactionsTransformer_test.go +++ b/server/services/transactionsTransformer_test.go @@ -60,9 +60,9 @@ func TestTransactionsTransformer_NormalTxToRosettaTx(t *testing.T) { Receiver: testscommon.TestUserBShard0.Address, SourceShard: 0, DestinationShard: 0, - Value: "1234", InitiallyPaidFee: "50000000000000", - Data: []byte("relayedTx@7b226e6f6e6365223a372c2273656e646572223a226e69424758747949504349644a78793373796c6c455a474c78506a503148734a45646e43732b6d726577413d222c227265636569766572223a224141414141414141414141464145356c3079623173734a3933504433672f4b396f48384579366d576958513d222c2276616c7565223a313030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a226e6830743338585a77614b6a725878446969716f6d364d6a5671724d612f6b70767474696a33692b5a6d43492f3778626830596762363548424151445744396f7036575567674541755430756e5253595736455341413d3d222c22636861696e4944223a224d513d3d222c2276657273696f6e223a327d"), + // Has non-zero value + Data: []byte("relayedTx@7b226e6f6e6365223a372c2273656e646572223a226e69424758747949504349644a78793373796c6c455a474c78506a503148734a45646e43732b6d726577413d222c227265636569766572223a224141414141414141414141464145356c3079623173734a3933504433672f4b396f48384579366d576958513d222c2276616c7565223a313030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a226e6830743338585a77614b6a725878446969716f6d364d6a5671724d612f6b70767474696a33692b5a6d43492f3778626830596762363548424151445744396f7036575567674541755430756e5253595736455341413d3d222c22636861696e4944223a224d513d3d222c2276657273696f6e223a327d"), Logs: &transaction.ApiLogs{ Events: []*transaction.Events{ { @@ -75,16 +75,6 @@ func TestTransactionsTransformer_NormalTxToRosettaTx(t *testing.T) { expectedRosettaTx := &types.Transaction{ TransactionIdentifier: hashToTransactionIdentifier("aaaa"), Operations: []*types.Operation{ - { - Type: opTransfer, - Account: addressToAccountIdentifier(testscommon.TestUserAShard0.Address), - Amount: extension.valueToNativeAmount("-1234"), - }, - { - Type: opTransfer, - Account: addressToAccountIdentifier(testscommon.TestUserBShard0.Address), - Amount: extension.valueToNativeAmount("1234"), - }, { Type: opFee, Account: addressToAccountIdentifier(testscommon.TestUserAShard0.Address), @@ -105,6 +95,85 @@ func TestTransactionsTransformer_NormalTxToRosettaTx(t *testing.T) { }) } +func TestTransactionsTransformer_ExtractInnerTxOperationsIfRelayedCompletelyIntrashardWithSignalError(t *testing.T) { + networkProvider := testscommon.NewNetworkProviderMock() + extension := newNetworkProviderExtension(networkProvider) + transformer := newTransactionsTransformer(networkProvider) + + t.Run("non-relayed tx", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + Type: string(transaction.TxTypeNormal), + } + + operations, err := transformer.extractInnerTxOperationsIfRelayedCompletelyIntrashardWithSignalError(tx) + require.NoError(t, err) + require.Len(t, operations, 0) + }) + + t.Run("relayed tx (badly formatted)", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + Type: string(transaction.TxTypeNormal), + ProcessingTypeOnSource: transactionProcessingTypeRelayed, + ProcessingTypeOnDestination: transactionProcessingTypeRelayed, + Data: []byte("bad"), + } + + operations, err := transformer.extractInnerTxOperationsIfRelayedCompletelyIntrashardWithSignalError(tx) + require.ErrorIs(t, err, errCannotParseRelayedV1) + require.Nil(t, operations) + }) + + t.Run("relayed tx, completely intrashard, with signal error, inner tx has non-zero value", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + Type: string(transaction.TxTypeNormal), + ProcessingTypeOnSource: transactionProcessingTypeRelayed, + ProcessingTypeOnDestination: transactionProcessingTypeRelayed, + SourceShard: 0, + DestinationShard: 0, + Data: []byte("relayedTx@7b226e6f6e6365223a372c2273656e646572223a226e69424758747949504349644a78793373796c6c455a474c78506a503148734a45646e43732b6d726577413d222c227265636569766572223a224141414141414141414141464145356c3079623173734a3933504433672f4b396f48384579366d576958513d222c2276616c7565223a313030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a226e6830743338585a77614b6a725878446969716f6d364d6a5671724d612f6b70767474696a33692b5a6d43492f3778626830596762363548424151445744396f7036575567674541755430756e5253595736455341413d3d222c22636861696e4944223a224d513d3d222c2276657273696f6e223a327d"), + Logs: &transaction.ApiLogs{ + Events: []*transaction.Events{ + { + Identifier: transactionEventSignalError, + }, + }, + }, + } + + operations, err := transformer.extractInnerTxOperationsIfRelayedCompletelyIntrashardWithSignalError(tx) + require.NoError(t, err) + require.Equal(t, []*types.Operation{ + { + Type: opTransfer, + Account: addressToAccountIdentifier(testscommon.TestUserCShard0.Address), + Amount: extension.valueToNativeAmount("-1000000000000000000"), + }, + }, operations) + }) + + t.Run("relayed tx, completely intrashard, with signal error, inner tx has zero value", func(t *testing.T) { + tx := &transaction.ApiTransactionResult{ + Type: string(transaction.TxTypeNormal), + ProcessingTypeOnSource: transactionProcessingTypeRelayed, + ProcessingTypeOnDestination: transactionProcessingTypeRelayed, + SourceShard: 0, + DestinationShard: 0, + Data: []byte("relayedTx@7b226e6f6e6365223a372c2273656e646572223a226e69424758747949504349644a78793373796c6c455a474c78506a503148734a45646e43732b6d726577413d222c227265636569766572223a224141414141414141414141464145356c3079623173734a3933504433672f4b396f48384579366d576958513d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a22336c644e7a32435734416143675069495863636c466b4654324149586a4a4757316a526a306c542b4f3161736b6241394a744e365a764173396e394f58716d343130574a49665332332b4168666e48793267446c41773d3d222c22636861696e4944223a224d513d3d222c2276657273696f6e223a327d"), + Logs: &transaction.ApiLogs{ + Events: []*transaction.Events{ + { + Identifier: transactionEventSignalError, + }, + }, + }, + } + + operations, err := transformer.extractInnerTxOperationsIfRelayedCompletelyIntrashardWithSignalError(tx) + require.NoError(t, err) + require.Len(t, operations, 0) + }) +} + func TestTransactionsTransformer_UnsignedTxToRosettaTx(t *testing.T) { networkProvider := testscommon.NewNetworkProviderMock() extension := newNetworkProviderExtension(networkProvider) From 468511b69f430619afb01d1ade81f3f1f2bbfc28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 30 Aug 2023 12:18:08 +0300 Subject: [PATCH 11/13] Fix after review. --- server/factory/components/observerFacade.go | 1 + server/services/common.go | 2 +- server/services/common_test.go | 8 ++++---- server/services/transactionsTransformer.go | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/server/factory/components/observerFacade.go b/server/factory/components/observerFacade.go index 97dd815..1ca94f0 100644 --- a/server/factory/components/observerFacade.go +++ b/server/factory/components/observerFacade.go @@ -12,6 +12,7 @@ type ObserverFacade struct { facade.BlockProcessor } +// ComputeShardId computes the shard ID for a given public key func (facade *ObserverFacade) ComputeShardId(pubKey []byte) uint32 { return facade.GetShardCoordinator().ComputeId(pubKey) } diff --git a/server/services/common.go b/server/services/common.go index f24789a..df9ebe0 100644 --- a/server/services/common.go +++ b/server/services/common.go @@ -50,7 +50,7 @@ func isZeroAmount(amount string) bool { return false } -func isZeroBigInt(value *big.Int) bool { +func isZeroBigIntOrNil(value *big.Int) bool { if value == nil { return true } diff --git a/server/services/common_test.go b/server/services/common_test.go index 4e0f1b8..8942548 100644 --- a/server/services/common_test.go +++ b/server/services/common_test.go @@ -43,10 +43,10 @@ func Test_IsZeroAmount(t *testing.T) { } func Test_IsZeroBigInt(t *testing.T) { - require.True(t, isZeroBigInt(big.NewInt(0))) - require.True(t, isZeroBigInt(nil)) - require.False(t, isZeroBigInt(big.NewInt(42))) - require.False(t, isZeroBigInt(big.NewInt(-42))) + require.True(t, isZeroBigIntOrNil(big.NewInt(0))) + require.True(t, isZeroBigIntOrNil(nil)) + require.False(t, isZeroBigIntOrNil(big.NewInt(42))) + require.False(t, isZeroBigIntOrNil(big.NewInt(-42))) } func Test_GetMagnitudeOfAmount(t *testing.T) { diff --git a/server/services/transactionsTransformer.go b/server/services/transactionsTransformer.go index 6298ae5..1a4521b 100644 --- a/server/services/transactionsTransformer.go +++ b/server/services/transactionsTransformer.go @@ -220,7 +220,7 @@ func (transformer *transactionsTransformer) extractInnerTxOperationsIfRelayedCom return nil, err } - if isZeroBigInt(&innerTx.Value) { + if isZeroBigIntOrNil(&innerTx.Value) { return nil, nil } From 8a86700ec476fc5998fbae6420c74655a266c680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 4 Sep 2023 12:24:33 +0300 Subject: [PATCH 12/13] Fix after review. --- server/services/transactionsTransformer.go | 8 ++++---- server/services/transactionsTransformer_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/services/transactionsTransformer.go b/server/services/transactionsTransformer.go index 1a4521b..37e8f74 100644 --- a/server/services/transactionsTransformer.go +++ b/server/services/transactionsTransformer.go @@ -212,20 +212,20 @@ func (transformer *transactionsTransformer) normalTxToRosetta(tx *transaction.Ap func (transformer *transactionsTransformer) extractInnerTxOperationsIfRelayedCompletelyIntrashardWithSignalError(tx *transaction.ApiTransactionResult) ([]*types.Operation, error) { isRelayedTransaction := isRelayedV1Transaction(tx) if !isRelayedTransaction { - return nil, nil + return []*types.Operation{}, nil } innerTx, err := parseInnerTxOfRelayedV1(tx) if err != nil { - return nil, err + return []*types.Operation{}, err } if isZeroBigIntOrNil(&innerTx.Value) { - return nil, nil + return []*types.Operation{}, nil } if !transformer.featuresDetector.isRelayedTransactionCompletelyIntrashardWithSignalError(tx, innerTx) { - return nil, nil + return []*types.Operation{}, nil } senderAddress := transformer.provider.ConvertPubKeyToAddress(innerTx.SenderPubKey) diff --git a/server/services/transactionsTransformer_test.go b/server/services/transactionsTransformer_test.go index c336d30..67c7284 100644 --- a/server/services/transactionsTransformer_test.go +++ b/server/services/transactionsTransformer_test.go @@ -120,7 +120,7 @@ func TestTransactionsTransformer_ExtractInnerTxOperationsIfRelayedCompletelyIntr operations, err := transformer.extractInnerTxOperationsIfRelayedCompletelyIntrashardWithSignalError(tx) require.ErrorIs(t, err, errCannotParseRelayedV1) - require.Nil(t, operations) + require.Empty(t, operations) }) t.Run("relayed tx, completely intrashard, with signal error, inner tx has non-zero value", func(t *testing.T) { From f524b19149b05ae4a3f8966ecf135ea49a5a4568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 4 Sep 2023 14:21:02 +0300 Subject: [PATCH 13/13] Fix after review. --- server/services/constants.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/services/constants.go b/server/services/constants.go index 4fe7736..0b6f4f9 100644 --- a/server/services/constants.go +++ b/server/services/constants.go @@ -3,6 +3,8 @@ package services import ( "encoding/hex" "strings" + + "github.com/multiversx/mx-chain-core-go/core" ) var ( @@ -10,7 +12,7 @@ var ( transactionProcessingTypeRelayed = "RelayedTx" transactionProcessingTypeBuiltInFunctionCall = "BuiltInFunctionCall" transactionProcessingTypeMoveBalance = "MoveBalance" - builtInFunctionClaimDeveloperRewards = "ClaimDeveloperRewards" + builtInFunctionClaimDeveloperRewards = core.BuiltInFunctionClaimDeveloperRewards refundGasMessage = "refundedGas" argumentsSeparator = "@" sendingValueToNonPayableContractDataPrefix = argumentsSeparator + hex.EncodeToString([]byte("sending value to non payable contract")) @@ -19,7 +21,7 @@ var ( ) var ( - transactionEventSignalError = "signalError" + transactionEventSignalError = core.SignalErrorOperation transactionEventTransferValueOnly = "transferValueOnly" transactionEventTopicInvalidMetaTransaction = "meta transaction is invalid" transactionEventTopicInvalidMetaTransactionNotEnoughGas = "meta transaction is invalid: not enough gas"