Skip to content

Commit

Permalink
Merge pull request #439 from multiversx/multiple_inner_txs
Browse files Browse the repository at this point in the history
Multiple inner txs on relayed v3
  • Loading branch information
sstanculeanu authored Jul 3, 2024
2 parents caf3d97 + 0283601 commit 551788b
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 31 deletions.
36 changes: 18 additions & 18 deletions data/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,24 @@ import (
// Transaction represents the structure that maps and validates user input for publishing a new transaction
type Transaction struct {
// This field is used to tag transactions for send-multiple route
Index int `json:"-"`
Nonce uint64 `json:"nonce"`
Value string `json:"value"`
Receiver string `json:"receiver"`
Sender string `json:"sender"`
SenderUsername []byte `json:"senderUsername,omitempty"`
ReceiverUsername []byte `json:"receiverUsername,omitempty"`
GasPrice uint64 `json:"gasPrice"`
GasLimit uint64 `json:"gasLimit"`
Data []byte `json:"data,omitempty"`
Signature string `json:"signature,omitempty"`
ChainID string `json:"chainID"`
Version uint32 `json:"version"`
Options uint32 `json:"options,omitempty"`
GuardianAddr string `json:"guardian,omitempty"`
GuardianSignature string `json:"guardianSignature,omitempty"`
Relayer string `json:"relayer,omitempty"`
InnerTransaction *Transaction `json:"innerTransaction,omitempty"`
Index int `json:"-"`
Nonce uint64 `json:"nonce"`
Value string `json:"value"`
Receiver string `json:"receiver"`
Sender string `json:"sender"`
SenderUsername []byte `json:"senderUsername,omitempty"`
ReceiverUsername []byte `json:"receiverUsername,omitempty"`
GasPrice uint64 `json:"gasPrice"`
GasLimit uint64 `json:"gasLimit"`
Data []byte `json:"data,omitempty"`
Signature string `json:"signature,omitempty"`
ChainID string `json:"chainID"`
Version uint32 `json:"version"`
Options uint32 `json:"options,omitempty"`
GuardianAddr string `json:"guardian,omitempty"`
GuardianSignature string `json:"guardianSignature,omitempty"`
Relayer string `json:"relayer,omitempty"`
InnerTransactions []*Transaction `json:"innerTransactions,omitempty"`
}

// GetTransactionResponseData follows the format of the data field of get transaction response
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ require (
github.com/gin-contrib/pprof v1.4.0
github.com/gin-contrib/static v0.0.1
github.com/gin-gonic/gin v1.9.1
github.com/multiversx/mx-chain-core-go v1.2.21-0.20240508071047-fefea5737840
github.com/multiversx/mx-chain-core-go v1.2.21-0.20240703095353-e5daea901067
github.com/multiversx/mx-chain-crypto-go v1.2.12-0.20240508074452-cc21c1b505df
github.com/multiversx/mx-chain-es-indexer-go v1.7.1-0.20240509104512-25512675833d
github.com/multiversx/mx-chain-es-indexer-go v1.7.2-0.20240514103357-929ece92ef86
github.com/multiversx/mx-chain-logger-go v1.0.15-0.20240508072523-3f00a726af57
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.4
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiversx/mx-chain-core-go v1.2.21-0.20240508071047-fefea5737840 h1:2mCrTUmbbA+Xv4UifZY9xptrGjcJBcJ2wavSb4FwejU=
github.com/multiversx/mx-chain-core-go v1.2.21-0.20240508071047-fefea5737840/go.mod h1:B5zU4MFyJezmEzCsAHE9YNULmGCm2zbPHvl9hazNxmE=
github.com/multiversx/mx-chain-core-go v1.2.21-0.20240703095353-e5daea901067 h1:xkWwOJok4GlbMd/BBtJ75wnNRjIVh4o+7RdZL/q/mlQ=
github.com/multiversx/mx-chain-core-go v1.2.21-0.20240703095353-e5daea901067/go.mod h1:B5zU4MFyJezmEzCsAHE9YNULmGCm2zbPHvl9hazNxmE=
github.com/multiversx/mx-chain-crypto-go v1.2.12-0.20240508074452-cc21c1b505df h1:clihfi78bMEOWk/qw6WA4uQbCM2e2NGliqswLAvw19k=
github.com/multiversx/mx-chain-crypto-go v1.2.12-0.20240508074452-cc21c1b505df/go.mod h1:gtJYB4rR21KBSqJlazn+2z6f9gFSqQP3KvAgL7Qgxw4=
github.com/multiversx/mx-chain-es-indexer-go v1.7.1-0.20240509104512-25512675833d h1:GD1D8V0bE6hDLjrduSsMwQwwf6PMq2Zww7FYMfJsuiw=
github.com/multiversx/mx-chain-es-indexer-go v1.7.1-0.20240509104512-25512675833d/go.mod h1:UDKRXmxsSyPeAcjLUfGeYkAtYp424PIYkL82kzFYobM=
github.com/multiversx/mx-chain-es-indexer-go v1.7.2-0.20240514103357-929ece92ef86 h1:rw+u7qv0HO+7lRddCzfciqDcAWL9/fl2LQqU8AmVtdU=
github.com/multiversx/mx-chain-es-indexer-go v1.7.2-0.20240514103357-929ece92ef86/go.mod h1:UDKRXmxsSyPeAcjLUfGeYkAtYp424PIYkL82kzFYobM=
github.com/multiversx/mx-chain-logger-go v1.0.15-0.20240508072523-3f00a726af57 h1:g9t410dqjcb7UUptbVd/H6Ua12sEzWU4v7VplyNvRZ0=
github.com/multiversx/mx-chain-logger-go v1.0.15-0.20240508072523-3f00a726af57/go.mod h1:cY6CIXpndW5g5PTPn4WzPwka/UBEf+mgw+PSY5pHGAU=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
Expand Down
3 changes: 0 additions & 3 deletions process/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import (
proxyData "github.com/multiversx/mx-chain-proxy-go/data"
)

// RelayedTxV2DataMarker -
const RelayedTxV2DataMarker = relayedTxV2DataMarker

// SetDelayForCheckingNodesSyncState -
func (bp *BaseProcessor) SetDelayForCheckingNodesSyncState(delay time.Duration) {
bp.delayForCheckingNodesSyncState = delay
Expand Down
98 changes: 94 additions & 4 deletions process/transactionProcessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ const (
moveBalanceDescriptor = "MoveBalance"
relayedV1TransactionDescriptor = "RelayedTx"
relayedV2TransactionDescriptor = "RelayedTxV2"
relayedTxV1DataMarker = "relayedTx@"
relayedTxV2DataMarker = "relayedTxV2"
argumentsSeparator = "@"
relayedV3TransactionDescriptor = "RelayedTxV3"
emptyDataStr = ""
)

Expand Down Expand Up @@ -478,6 +476,13 @@ func (tp *TransactionProcessor) computeTransactionStatus(tx *transaction.ApiTran
}
}

isRelayedV3, status := checkIfRelayedV3Completed(allLogs, tx)
if isRelayedV3 {
return &data.ProcessStatusResponse{
Status: status,
}
}

failed, reason = checkIfFailed(allLogs)
if failed {
return &data.ProcessStatusResponse{
Expand All @@ -504,11 +509,16 @@ func (tp *TransactionProcessor) computeTransactionStatus(tx *transaction.ApiTran
}

func checkIfFailedOnReturnMessage(allScrs []*transaction.ApiTransactionResult, tx *transaction.ApiTransactionResult) bool {
if len(tx.ReturnMessage) > 0 && isZeroValue(tx.Value) {
hasReturnMessageWithZeroValue := len(tx.ReturnMessage) > 0 && isZeroValue(tx.Value)
if hasReturnMessageWithZeroValue && !isRefundScr(tx.ReturnMessage) {
return true
}

for _, scr := range allScrs {
if isRefundScr(scr.ReturnMessage) {
continue
}

if len(scr.ReturnMessage) > 0 && isZeroValue(scr.Value) {
return true
}
Expand All @@ -517,6 +527,10 @@ func checkIfFailedOnReturnMessage(allScrs []*transaction.ApiTransactionResult, t
return false
}

func isRefundScr(returnMessage string) bool {
return returnMessage == core.GasRefundForRelayerMessage
}

func isZeroValue(value string) bool {
if len(value) == 0 {
return true
Expand Down Expand Up @@ -548,6 +562,18 @@ func checkIfCompleted(logs []*transaction.ApiLogs) bool {
return found
}

func checkIfRelayedV3Completed(logs []*transaction.ApiLogs, tx *transaction.ApiTransactionResult) (bool, string) {
if len(tx.InnerTransactions) == 0 {
return false, string(transaction.TxStatusPending)
}

if len(logs) < len(tx.InnerTransactions) {
return true, string(transaction.TxStatusPending)
}

return true, string(transaction.TxStatusSuccess)
}

func checkIfMoveBalanceNotarized(tx *transaction.ApiTransactionResult) bool {
isNotarized := tx.NotarizedAtSourceInMetaNonce > 0 && tx.NotarizedAtDestinationInMetaNonce > 0
if !isNotarized {
Expand Down Expand Up @@ -708,8 +734,13 @@ func (tp *TransactionProcessor) getTxFromObservers(txHash string, reqType reques
"error", err.Error())
}

if isRelayedTxV3(getTxResponse.Data.Transaction) {
return tp.mergeSCRLogsFromInnerReceivers(&getTxResponse.Data.Transaction, withResults), nil
}

isIntraShard := sndShardID == rcvShardID
observerIsInDestShard := rcvShardID == observerShardID

if isIntraShard {
return &getTxResponse.Data.Transaction, nil
}
Expand All @@ -735,6 +766,65 @@ func (tp *TransactionProcessor) getTxFromObservers(txHash string, reqType reques
return nil, errors.ErrTransactionNotFound
}

func isRelayedTxV3(tx transaction.ApiTransactionResult) bool {
return tx.ProcessingTypeOnSource == relayedV3TransactionDescriptor && tx.ProcessingTypeOnDestination == relayedV3TransactionDescriptor
}

func (tp *TransactionProcessor) mergeSCRLogsFromInnerReceivers(tx *transaction.ApiTransactionResult, withResults bool) *transaction.ApiTransactionResult {
logsOnDestMap := make(map[string]*transaction.ApiLogs, len(tx.SmartContractResults))

txsByReceiverShardMap := tp.groupTxsByReceiverShard(tx)
for shardID, scrHashes := range txsByReceiverShardMap {
for _, scrHash := range scrHashes {
observers, err := tp.getNodesInShard(shardID, requestTypeObservers)
if err != nil {
break
}

if withResults {
for _, observer := range observers {
getTxResponse, ok, _ := tp.getTxFromObserver(observer, scrHash, withResults)
if !ok {
continue
}

logsOnDestMap[scrHash] = getTxResponse.Data.Transaction.Logs
break
}
}
}
}

finalTx := *tx
// if withResults, override the scr logs with the one from the receiver shard
if withResults {
for _, scr := range finalTx.SmartContractResults {
logsOnDest, found := logsOnDestMap[scr.Hash]
if !found {
continue
}

scr.Logs = logsOnDest
}
}

return &finalTx
}

func (tp *TransactionProcessor) groupTxsByReceiverShard(tx *transaction.ApiTransactionResult) map[uint32][]string {
txsByReceiverShardMap := make(map[uint32][]string)
for _, scr := range tx.SmartContractResults {
shardID, err := tp.getShardByAddress(scr.RcvAddr)
if err != nil {
continue
}

txsByReceiverShardMap[shardID] = append(txsByReceiverShardMap[shardID], scr.Hash)
}

return txsByReceiverShardMap
}

func (tp *TransactionProcessor) alterTxWithScResultsFromSourceIfNeeded(txHash string, tx *transaction.ApiTransactionResult, withResults bool) *transaction.ApiTransactionResult {
if !withResults || len(tx.SmartContractResults) == 0 {
return tx
Expand Down
125 changes: 125 additions & 0 deletions process/transactionProcessor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,131 @@ func TestTransactionProcessor_GetTransactionWithEventsFirstFromDstShardAndAfterS
assert.Equal(t, 3, len(tx.SmartContractResults))
}

func TestTransactionProcessor_GetTransactionRelayedV3ShouldMergeEvents(t *testing.T) {
t.Parallel()

relayer := hex.EncodeToString([]byte("relayer"))
sender1 := hex.EncodeToString([]byte("sender1"))
sender2 := hex.EncodeToString([]byte("sender2"))
receiver1 := hex.EncodeToString([]byte("receiver1"))
receiver2 := hex.EncodeToString([]byte("receiver2"))

addrObs0 := "observer0"
addrObsFailing := "observerFailing"
addrObs1 := "observer1"

hashRelayed := "hashRelayed"

scrHash1 := "scrHash1"
providedLogsInnerTx1 := &transaction.ApiLogs{
Events: []*transaction.Events{
{
Address: receiver1,
Identifier: "events innertx 1",
},
},
}

scrHash2 := "scrHash2"
providedLogsInnerTx2 := &transaction.ApiLogs{
Events: []*transaction.Events{
{
Address: receiver2,
Identifier: "events innertx 2",
},
},
}

tp, _ := process.NewTransactionProcessor(
&mock.ProcessorStub{
ComputeShardIdCalled: func(addressBuff []byte) (uint32, error) {
switch string(addressBuff) {
case "relayer", "sender1", "sender2", "receiver1":
return 0, nil
case "receiver2":
return 1, nil
}

return 0, nil
},
GetShardIDsCalled: func() []uint32 {
return []uint32{0, 1}
},
GetObserversCalled: func(shardId uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) {
if shardId == 0 {
return []*data.NodeData{
{Address: addrObs0, ShardId: 0},
}, nil
}
if shardId == 1 {
return []*data.NodeData{
{Address: addrObsFailing, ShardId: 1},
{Address: addrObs1, ShardId: 1},
}, nil
}

return nil, nil
},
CallGetRestEndPointCalled: func(address string, path string, value interface{}) (i int, err error) {
if address == addrObsFailing {
return http.StatusBadRequest, errors.New("error for coverage only")
}

responseGetTx := value.(*data.GetTransactionResponse)
if strings.Contains(path, hashRelayed) {
responseGetTx.Data.Transaction.Hash = hashRelayed
responseGetTx.Data.Transaction.ProcessingTypeOnSource = "RelayedTxV3"
responseGetTx.Data.Transaction.ProcessingTypeOnDestination = "RelayedTxV3"
responseGetTx.Data.Transaction.SmartContractResults = []*transaction.ApiSmartContractResult{
{
Hash: scrHash1,
RcvAddr: receiver1,
SndAddr: sender1,
RelayerAddr: relayer,
IsRelayed: true,
},
{
Hash: scrHash2,
RcvAddr: receiver2,
SndAddr: sender2,
RelayerAddr: relayer,
IsRelayed: true,
},
}

return http.StatusOK, nil
}
if strings.Contains(path, scrHash1) {
responseGetTx.Data.Transaction.Hash = scrHash1
responseGetTx.Data.Transaction.Logs = providedLogsInnerTx1

return http.StatusOK, nil
}
if strings.Contains(path, scrHash2) {
responseGetTx.Data.Transaction.Hash = scrHash2
responseGetTx.Data.Transaction.Logs = providedLogsInnerTx2

return http.StatusOK, nil
}

return http.StatusBadRequest, nil
},
},
&mock.PubKeyConverterMock{},
hasher,
marshalizer,
funcNewTxCostHandler,
logsMerger,
true,
)

tx, err := tp.GetTransaction(hashRelayed, true)
require.NoError(t, err)
require.Equal(t, 2, len(tx.SmartContractResults))
require.Equal(t, providedLogsInnerTx1, tx.SmartContractResults[0].Logs)
require.Equal(t, providedLogsInnerTx2, tx.SmartContractResults[1].Logs)
}

func TestTransactionProcessor_GetTransactionPool(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit 551788b

Please sign in to comment.