From 40f69c13062db09d3810a84b863df7c416f166cd Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Thu, 4 Apr 2024 13:56:17 +0100 Subject: [PATCH 1/2] Check for the revert reason in the TX receipt before using debug_traceTransaction Signed-off-by: Matthew Whitehead --- internal/ethereum/get_receipt.go | 71 ++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/internal/ethereum/get_receipt.go b/internal/ethereum/get_receipt.go index 88d81c0..8495813 100644 --- a/internal/ethereum/get_receipt.go +++ b/internal/ethereum/get_receipt.go @@ -26,6 +26,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/fftypes" "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly-common/pkg/log" "github.com/hyperledger/firefly-evmconnect/internal/msgs" "github.com/hyperledger/firefly-signer/pkg/ethtypes" "github.com/hyperledger/firefly-transaction-manager/pkg/ffcapi" @@ -33,17 +34,18 @@ import ( // txReceiptJSONRPC is the receipt obtained over JSON/RPC from the ethereum client, with gas used, logs and contract address type txReceiptJSONRPC struct { - BlockHash ethtypes.HexBytes0xPrefix `json:"blockHash"` - BlockNumber *ethtypes.HexInteger `json:"blockNumber"` - ContractAddress *ethtypes.Address0xHex `json:"contractAddress"` - CumulativeGasUsed *ethtypes.HexInteger `json:"cumulativeGasUsed"` - From *ethtypes.Address0xHex `json:"from"` - GasUsed *ethtypes.HexInteger `json:"gasUsed"` - Logs []*logJSONRPC `json:"logs"` - Status *ethtypes.HexInteger `json:"status"` - To *ethtypes.Address0xHex `json:"to"` - TransactionHash ethtypes.HexBytes0xPrefix `json:"transactionHash"` - TransactionIndex *ethtypes.HexInteger `json:"transactionIndex"` + BlockHash ethtypes.HexBytes0xPrefix `json:"blockHash"` + BlockNumber *ethtypes.HexInteger `json:"blockNumber"` + ContractAddress *ethtypes.Address0xHex `json:"contractAddress"` + CumulativeGasUsed *ethtypes.HexInteger `json:"cumulativeGasUsed"` + From *ethtypes.Address0xHex `json:"from"` + GasUsed *ethtypes.HexInteger `json:"gasUsed"` + Logs []*logJSONRPC `json:"logs"` + Status *ethtypes.HexInteger `json:"status"` + To *ethtypes.Address0xHex `json:"to"` + TransactionHash ethtypes.HexBytes0xPrefix `json:"transactionHash"` + TransactionIndex *ethtypes.HexInteger `json:"transactionIndex"` + RevertReason *ethtypes.HexBytes0xPrefix `json:"revertReason"` } // receiptExtraInfo is the version of the receipt we store under the TX. @@ -119,30 +121,37 @@ func ProtocolIDForReceipt(blockNumber, transactionIndex *fftypes.FFBigInt) strin return "" } -func (c *ethConnector) getErrorInfo(ctx context.Context, transactionHash string) (pReturnValue *string, pErrorMessage *string) { - - // Attempt to get the return value of the transaction - not possible on all RPC endpoints - var debugTrace *txDebugTrace - traceErr := c.backend.CallRPC(ctx, &debugTrace, "debug_traceTransaction", transactionHash) - if traceErr != nil { - msg := i18n.NewError(ctx, msgs.MsgUnableToCallDebug, traceErr).Error() - return nil, &msg - } +func (c *ethConnector) getErrorInfo(ctx context.Context, transactionHash string, revertFromReceipt *ethtypes.HexBytes0xPrefix) (pReturnValue *string, pErrorMessage *string) { + + var revertReason string + if revertFromReceipt == nil { + log.L(ctx).Debug("No revert reason for the failed transaction found in the receipt. Calling debug_traceTransaction to retrieve it.") + // Attempt to get the return value of the transaction - not possible on all RPC endpoints + var debugTrace *txDebugTrace + traceErr := c.backend.CallRPC(ctx, &debugTrace, "debug_traceTransaction", transactionHash) + if traceErr != nil { + msg := i18n.NewError(ctx, msgs.MsgUnableToCallDebug, traceErr).Error() + return nil, &msg + } - returnValue := debugTrace.ReturnValue - if returnValue == "" { - // some clients (e.g. Besu) include the error reason on the final struct log - if len(debugTrace.StructLogs) > 0 { - finalStructLog := debugTrace.StructLogs[len(debugTrace.StructLogs)-1] - if *finalStructLog.Op == "REVERT" && finalStructLog.Reason != nil { - returnValue = *finalStructLog.Reason + revertReason := debugTrace.ReturnValue + if revertReason == "" { + // some clients (e.g. Besu) include the error reason on the final struct log + if len(debugTrace.StructLogs) > 0 { + finalStructLog := debugTrace.StructLogs[len(debugTrace.StructLogs)-1] + if *finalStructLog.Op == "REVERT" && finalStructLog.Reason != nil { + revertReason = *finalStructLog.Reason + } } } + } else { + log.L(ctx).Debug("Revert reason is set in the receipt. Skipping call to debug_traceTransaction.") + revertReason = string(*revertFromReceipt) } // See if the return value is using the default error you get from "revert" var errorMessage string - returnDataBytes, _ := hex.DecodeString(strings.TrimPrefix(returnValue, "0x")) + returnDataBytes, _ := hex.DecodeString(strings.TrimPrefix(revertReason, "0x")) if len(returnDataBytes) > 4 && bytes.Equal(returnDataBytes[0:4], defaultErrorID) { value, err := defaultError.DecodeCallDataCtx(ctx, returnDataBytes) if err == nil { @@ -153,12 +162,12 @@ func (c *ethConnector) getErrorInfo(ctx context.Context, transactionHash string) // Otherwise we can't decode it, so put it directly in the error if errorMessage == "" { if len(returnDataBytes) > 0 { - errorMessage = i18n.NewError(ctx, msgs.MsgReturnValueNotDecoded, returnValue).Error() + errorMessage = i18n.NewError(ctx, msgs.MsgReturnValueNotDecoded, revertReason).Error() } else { errorMessage = i18n.NewError(ctx, msgs.MsgReturnValueNotAvailable).Error() } } - return &returnValue, &errorMessage + return &revertReason, &errorMessage } func (c *ethConnector) TransactionReceipt(ctx context.Context, req *ffcapi.TransactionReceiptRequest) (*ffcapi.TransactionReceiptResponse, ffcapi.ErrorReason, error) { @@ -178,7 +187,7 @@ func (c *ethConnector) TransactionReceipt(ctx context.Context, req *ffcapi.Trans var transactionErrorMessage *string if !isSuccess { - returnDataString, transactionErrorMessage = c.getErrorInfo(ctx, req.TransactionHash) + returnDataString, transactionErrorMessage = c.getErrorInfo(ctx, req.TransactionHash, ethReceipt.RevertReason) } ethReceipt.Logs = nil From c136537b4fa147f88629440f082fb28427818a9f Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Thu, 4 Apr 2024 16:54:45 +0100 Subject: [PATCH 2/2] Restore padding of leading 0 for odd-length hex strings Signed-off-by: Matthew Whitehead --- internal/ethereum/get_receipt.go | 11 ++++++++++- internal/ethereum/get_receipt_test.go | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/ethereum/get_receipt.go b/internal/ethereum/get_receipt.go index 8495813..6976c8c 100644 --- a/internal/ethereum/get_receipt.go +++ b/internal/ethereum/get_receipt.go @@ -121,6 +121,15 @@ func ProtocolIDForReceipt(blockNumber, transactionIndex *fftypes.FFBigInt) strin return "" } +func padHexData(hexString string) string { + hexString = strings.TrimPrefix(hexString, "0x") + if len(hexString)%2 == 1 { + hexString = "0" + hexString + } + + return hexString +} + func (c *ethConnector) getErrorInfo(ctx context.Context, transactionHash string, revertFromReceipt *ethtypes.HexBytes0xPrefix) (pReturnValue *string, pErrorMessage *string) { var revertReason string @@ -151,7 +160,7 @@ func (c *ethConnector) getErrorInfo(ctx context.Context, transactionHash string, // See if the return value is using the default error you get from "revert" var errorMessage string - returnDataBytes, _ := hex.DecodeString(strings.TrimPrefix(revertReason, "0x")) + returnDataBytes, _ := hex.DecodeString(padHexData(revertReason)) if len(returnDataBytes) > 4 && bytes.Equal(returnDataBytes[0:4], defaultErrorID) { value, err := defaultError.DecodeCallDataCtx(ctx, returnDataBytes) if err == nil { diff --git a/internal/ethereum/get_receipt_test.go b/internal/ethereum/get_receipt_test.go index a952833..4bc450d 100644 --- a/internal/ethereum/get_receipt_test.go +++ b/internal/ethereum/get_receipt_test.go @@ -174,7 +174,7 @@ const sampleTransactionTraceBesu = `{ "0000000000000000000000000000000000000000000000000000000000000000" ], "storage": {}, - "reason": "08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000114e6f7420656e6f75676820746f6b656e73000000000000000000000000000000" + "reason": "8c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000114e6f7420656e6f75676820746f6b656e73000000000000000000000000000000" } ] }`