From 89ad27b390e5a9b66159d5d27b77ca17f32b52ce Mon Sep 17 00:00:00 2001 From: miiu Date: Mon, 5 Aug 2024 14:52:14 +0300 Subject: [PATCH] fix get transaction endpoint --- api/errors/errors.go | 3 + data/transaction.go | 12 +++ process/transactionProcessor.go | 135 ++++++++++++++++++++++++++- process/transactionProcessor_test.go | 5 +- 4 files changed, 151 insertions(+), 4 deletions(-) diff --git a/api/errors/errors.go b/api/errors/errors.go index d3499a73..5f45ff38 100644 --- a/api/errors/errors.go +++ b/api/errors/errors.go @@ -98,6 +98,9 @@ var ErrInvalidReceiverAddress = errors.New("invalid hex receiver address provide // ErrTransactionNotFound signals that a transaction was not found var ErrTransactionNotFound = errors.New("transaction not found") +// ErrSCRsNoFound signals that smart contract results were not found +var ErrSCRsNoFound = errors.New("smart contract results not found") + // ErrTransactionsNotFoundInPool signals that no transaction was not found in pool var ErrTransactionsNotFoundInPool = errors.New("transactions not found in pool") diff --git a/data/transaction.go b/data/transaction.go index e60ed8c2..757c59fd 100644 --- a/data/transaction.go +++ b/data/transaction.go @@ -41,6 +41,18 @@ type GetTransactionResponse struct { Code string `json:"code"` } +// GetSCRsResponseData follows the format of the data field of get smart contract results response +type GetSCRsResponseData struct { + SCRs []*transaction.ApiSmartContractResult `json:"scrs"` +} + +// GetSCRsResponse defines a response from the node holding the smart contract results +type GetSCRsResponse struct { + Data GetSCRsResponseData `json:"data"` + Error string `json:"error"` + Code string `json:"code"` +} + // transactionWrapper is a wrapper over a normal transaction in order to implement the interface needed in mx-chain-go // for computing gas cost for a transaction type transactionWrapper struct { diff --git a/process/transactionProcessor.go b/process/transactionProcessor.go index 05cae2b5..b2905ba5 100644 --- a/process/transactionProcessor.go +++ b/process/transactionProcessor.go @@ -31,8 +31,12 @@ const TransactionSimulatePath = "/transaction/simulate" // MultipleTransactionsPath defines the multiple transactions send path of the node const MultipleTransactionsPath = "/transaction/send-multiple" +// SCRsByTxHash defines smart contract results by transaction hash path of the node +const SCRsByTxHash = "/transaction/scrs-by-tx-hash/" + const ( withResultsParam = "?withResults=true" + scrHashParam = "?scrHash=%s" checkSignatureFalse = "?checkSignature=false" bySenderParam = "&by-sender=" fieldsParam = "?fields=" @@ -68,6 +72,11 @@ type erdTransaction struct { Version uint32 `json:"version"` } +type tupleHashWasFetched struct { + hash string + fetched bool +} + // TransactionProcessor is able to process transaction requests type TransactionProcessor struct { proc Processor @@ -723,6 +732,7 @@ func (tp *TransactionProcessor) gatherAllLogsAndScrs(tx *transaction.ApiTransact func (tp *TransactionProcessor) getTxFromObservers(txHash string, reqType requestType, withResults bool) (*transaction.ApiTransactionResult, error) { observersShardIDs := tp.proc.GetShardIDs() + shardIDWasFetch := make(map[uint32]*tupleHashWasFetched) for _, observerShardID := range observersShardIDs { nodesInShard, err := tp.getNodesInShard(observerShardID, reqType) if err != nil { @@ -749,6 +759,10 @@ func (tp *TransactionProcessor) getTxFromObservers(txHash string, reqType reques "sender address", getTxResponse.Data.Transaction.Sender, "error", err.Error()) } + shardIDWasFetch[sndShardID] = &tupleHashWasFetched{ + hash: getTxResponse.Data.Transaction.Hash, + fetched: false, + } rcvShardID, err := tp.getShardByAddress(getTxResponse.Data.Transaction.Receiver) if err != nil { @@ -756,35 +770,144 @@ func (tp *TransactionProcessor) getTxFromObservers(txHash string, reqType reques "receiver address", getTxResponse.Data.Transaction.Receiver, "error", err.Error()) } + shardIDWasFetch[rcvShardID] = &tupleHashWasFetched{ + hash: getTxResponse.Data.Transaction.Hash, + fetched: false, + } isIntraShard := sndShardID == rcvShardID observerIsInDestShard := rcvShardID == observerShardID if isIntraShard { - return &getTxResponse.Data.Transaction, nil + shardIDWasFetch[sndShardID].fetched = true + if len(getTxResponse.Data.Transaction.SmartContractResults) == 0 { + return &getTxResponse.Data.Transaction, nil + } + + tp.extraShardFromSCRs(getTxResponse.Data.Transaction.SmartContractResults, shardIDWasFetch) } if observerIsInDestShard { // need to get transaction from source shard and merge scResults // if withEvents is true - return tp.alterTxWithScResultsFromSourceIfNeeded(txHash, &getTxResponse.Data.Transaction, withResults), nil + txFromSource := tp.alterTxWithScResultsFromSourceIfNeeded(txHash, &getTxResponse.Data.Transaction, withResults, shardIDWasFetch) + + tp.extraShardFromSCRs(txFromSource.SmartContractResults, shardIDWasFetch) + + err = tp.fetchSCRSBasedOnShardMap(txFromSource, shardIDWasFetch) + if err != nil { + return nil, err + } + + return txFromSource, nil } // get transaction from observer that is in destination shard txFromDstShard, ok := tp.getTxFromDestShard(txHash, rcvShardID, withResults) if ok { + tp.extraShardFromSCRs(txFromDstShard.SmartContractResults, shardIDWasFetch) + alteredTxFromDest := tp.mergeScResultsFromSourceAndDestIfNeeded(&getTxResponse.Data.Transaction, txFromDstShard, withResults) + + err = tp.fetchSCRSBasedOnShardMap(alteredTxFromDest, shardIDWasFetch) + if err != nil { + return nil, err + } + return alteredTxFromDest, nil } // return transaction from observer from source shard // if did not get ok responses from observers from destination shard + + err = tp.fetchSCRSBasedOnShardMap(&getTxResponse.Data.Transaction, shardIDWasFetch) + if err != nil { + return nil, err + } + return &getTxResponse.Data.Transaction, nil } return nil, errors.ErrTransactionNotFound } -func (tp *TransactionProcessor) alterTxWithScResultsFromSourceIfNeeded(txHash string, tx *transaction.ApiTransactionResult, withResults bool) *transaction.ApiTransactionResult { +func (tp *TransactionProcessor) fetchSCRSBasedOnShardMap(tx *transaction.ApiTransactionResult, shardIDWasFetch map[uint32]*tupleHashWasFetched) error { + for shardID, info := range shardIDWasFetch { + scrs, err := tp.fetchSCRs(tx.Hash, info.hash, shardID) + if err != nil { + return err + } + + scResults := append(tx.SmartContractResults, scrs...) + scResultsNew := tp.getScResultsUnion(scResults) + + tx.SmartContractResults = scResultsNew + info.fetched = true + } + + return nil +} + +func (tp *TransactionProcessor) fetchSCRs(txHash, scrHash string, shardID uint32) ([]*transaction.ApiSmartContractResult, error) { + observers, err := tp.getNodesInShard(shardID, requestTypeFullHistoryNodes) + if err != nil { + return nil, err + } + + apiPath := SCRsByTxHash + txHash + fmt.Sprintf(scrHashParam, scrHash) + for _, observer := range observers { + getTxResponseDst := &data.GetSCRsResponse{} + respCode, errG := tp.proc.CallGetRestEndPoint(observer.Address, apiPath, getTxResponseDst) + if errG != nil { + log.Trace("cannot get smart contract results", "address", observer.Address, "error", errG) + continue + } + + if respCode != http.StatusOK { + continue + } + + return getTxResponseDst.Data.SCRs, nil + } + + return nil, errors.ErrSCRsNoFound + +} + +func (tp *TransactionProcessor) extraShardFromSCRs(scrs []*transaction.ApiSmartContractResult, shardIDWasFetch map[uint32]*tupleHashWasFetched) { + for _, scr := range scrs { + sndShardID, err := tp.getShardByAddress(scr.SndAddr) + if err != nil { + log.Warn("cannot compute shard ID from sender address", + "sender address", scr.SndAddr, + "error", err.Error()) + } + + _, found := shardIDWasFetch[sndShardID] + if !found { + shardIDWasFetch[sndShardID] = &tupleHashWasFetched{ + hash: scr.Hash, + fetched: false, + } + } + + rcvShardID, err := tp.getShardByAddress(scr.SndAddr) + if err != nil { + log.Warn("cannot compute shard ID from receiver address", + "receiver address", scr.RcvAddr, + "error", err.Error()) + } + + _, found = shardIDWasFetch[rcvShardID] + if !found { + shardIDWasFetch[rcvShardID] = &tupleHashWasFetched{ + hash: scr.Hash, + fetched: false, + } + } + } +} + +func (tp *TransactionProcessor) alterTxWithScResultsFromSourceIfNeeded(txHash string, tx *transaction.ApiTransactionResult, withResults bool, shardIDWasFetch map[uint32]*tupleHashWasFetched) *transaction.ApiTransactionResult { if !withResults || len(tx.SmartContractResults) == 0 { return tx } @@ -801,6 +924,12 @@ func (tp *TransactionProcessor) alterTxWithScResultsFromSourceIfNeeded(txHash st } alteredTxFromDest := tp.mergeScResultsFromSourceAndDestIfNeeded(&getTxResponse.Data.Transaction, tx, withResults) + + shardIDWasFetch[tx.SourceShard] = &tupleHashWasFetched{ + hash: getTxResponse.Data.Transaction.Hash, + fetched: true, + } + return alteredTxFromDest } diff --git a/process/transactionProcessor_test.go b/process/transactionProcessor_test.go index c1329261..f5c8deb5 100644 --- a/process/transactionProcessor_test.go +++ b/process/transactionProcessor_test.go @@ -650,7 +650,10 @@ func TestTransactionProcessor_GetTransactionStatusCrossShardTransaction(t *testi }, nil }, CallGetRestEndPointCalled: func(address string, path string, value interface{}) (i int, err error) { - responseGetTx := value.(*data.GetTransactionResponse) + responseGetTx, ok := value.(*data.GetTransactionResponse) + if !ok { + return http.StatusOK, nil + } responseGetTx.Data.Transaction = transaction.ApiTransactionResult{ Receiver: sndrShard1,