Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the use of debug_traceTransaction configurable #131

Merged
merged 4 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
|passthroughHeadersEnabled|Enable passing through the set of allowed HTTP request headers|`boolean`|`false`
|requestTimeout|The maximum amount of time that a request is allowed to remain open|[`time.Duration`](https://pkg.go.dev/time#Duration)|`30s`
|tlsHandshakeTimeout|The maximum amount of time to wait for a successful TLS handshake|[`time.Duration`](https://pkg.go.dev/time#Duration)|`10s`
|traceTXForRevertReason|Enable the use of transaction trace functions (e.g. debug_traceTransaction) to obtain transaction revert reasons. This can place a high load on the EVM client.|`boolean`|`false`
|txCacheSize|Maximum of transactions to hold in the transaction info cache|`int`|`250`
|url|URL of JSON/RPC endpoint for the Ethereum node/gateway|string|`<nil>`

Expand Down
2 changes: 2 additions & 0 deletions internal/ethereum/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (
RetryFactor = "retry.factor"
MaxConcurrentRequests = "maxConcurrentRequests"
TxCacheSize = "txCacheSize"
TraceTXForRevertReason = "traceTXForRevertReason"
)

const (
Expand Down Expand Up @@ -70,4 +71,5 @@ func InitConfig(conf config.Section) {
conf.AddKnownKey(RetryMaxDelay, DefaultRetryMaxDelay)
conf.AddKnownKey(MaxConcurrentRequests, 50)
conf.AddKnownKey(TxCacheSize, 250)
conf.AddKnownKey(TraceTXForRevertReason, false)
}
2 changes: 2 additions & 0 deletions internal/ethereum/ethereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type ethConnector struct {
eventBlockTimestamps bool
blockListener *blockListener
eventFilterPollingInterval time.Duration
traceTXForRevertReason bool

mux sync.Mutex
eventStreams map[fftypes.UUID]*eventStream
Expand All @@ -64,6 +65,7 @@ func NewEthereumConnector(ctx context.Context, conf config.Section) (cc ffcapi.A
checkpointBlockGap: conf.GetInt64(EventsCheckpointBlockGap),
eventBlockTimestamps: conf.GetBool(EventsBlockTimestamps),
eventFilterPollingInterval: conf.GetDuration(EventsFilterPollingInterval),
traceTXForRevertReason: conf.GetBool(TraceTXForRevertReason),
retry: &retry.Retry{
InitialDelay: conf.GetDuration(RetryInitDelay),
MaximumDelay: conf.GetDuration(RetryMaxDelay),
Expand Down
1 change: 1 addition & 0 deletions internal/ethereum/ethereum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func newTestConnector(t *testing.T) (context.Context, *ethConnector, *rpcbackend
config.RootConfigReset()
conf := config.RootSection("unittest")
InitConfig(conf)
//conf.Set(TraceTXForRevertReason, true)
conf.Set(ffresty.HTTPConfigURL, "http://localhost:8545")
conf.Set(BlockPollingInterval, "1h") // Disable for tests that are not using it
logrus.SetLevel(logrus.DebugLevel)
Expand Down
35 changes: 19 additions & 16 deletions internal/ethereum/get_receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,23 +134,26 @@ func (c *ethConnector) getErrorInfo(ctx context.Context, transactionHash string,

var revertReason string
if revertFromReceipt == nil {
log.L(ctx).Trace("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
}
// Tracing a transaction to get revert information is expensive so it's not enabled by default
if c.traceTXForRevertReason {
log.L(ctx).Trace("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
}

revertReason = debugTrace.ReturnValue
log.L(ctx).Debugf("Revert reason from debug_traceTransaction: '%v'", revertReason)
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
revertReason = debugTrace.ReturnValue
log.L(ctx).Debugf("Revert reason from debug_traceTransaction: '%v'", revertReason)
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
}
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions internal/ethereum/get_receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ func TestGetReceiptError(t *testing.T) {
func TestGetReceiptErrorReasonGeth(t *testing.T) {

ctx, c, mRPC, done := newTestConnector(t)
c.traceTXForRevertReason = true
defer done()

mRPC.On("CallRPC", mock.Anything, mock.Anything, "eth_getTransactionReceipt",
Expand Down Expand Up @@ -306,6 +307,7 @@ func TestGetReceiptErrorReasonGeth(t *testing.T) {
func TestGetReceiptErrorReasonBesu(t *testing.T) {

ctx, c, mRPC, done := newTestConnector(t)
c.traceTXForRevertReason = true
defer done()

mRPC.On("CallRPC", mock.Anything, mock.Anything, "eth_getTransactionReceipt",
Expand Down Expand Up @@ -343,6 +345,7 @@ func TestGetReceiptErrorReasonBesu(t *testing.T) {
func TestGetReceiptErrorReasonNotFound(t *testing.T) {

ctx, c, mRPC, done := newTestConnector(t)
c.traceTXForRevertReason = true
defer done()

mRPC.On("CallRPC", mock.Anything, mock.Anything, "eth_getTransactionReceipt",
Expand Down Expand Up @@ -379,6 +382,7 @@ func TestGetReceiptErrorReasonNotFound(t *testing.T) {
func TestGetReceiptErrorReasonErrorFromHexDecode(t *testing.T) {

ctx, c, mRPC, done := newTestConnector(t)
c.traceTXForRevertReason = true
defer done()

mRPC.On("CallRPC", mock.Anything, mock.Anything, "eth_getTransactionReceipt",
Expand Down Expand Up @@ -415,6 +419,7 @@ func TestGetReceiptErrorReasonErrorFromTrace(t *testing.T) {
// if we get an error tracing the transaction, we ignore it. Not all nodes support the debug_traceTransaction RPC call

ctx, c, mRPC, done := newTestConnector(t)
c.traceTXForRevertReason = true
defer done()

mRPC.On("CallRPC", mock.Anything, mock.Anything, "eth_getTransactionReceipt",
Expand Down Expand Up @@ -450,6 +455,7 @@ func TestGetReceiptErrorReasonErrorFromTrace(t *testing.T) {
func TestGetReceiptErrorReasonErrorFromDecodeCallData(t *testing.T) {

ctx, c, mRPC, done := newTestConnector(t)
c.traceTXForRevertReason = true
defer done()

mRPC.On("CallRPC", mock.Anything, mock.Anything, "eth_getTransactionReceipt",
Expand Down Expand Up @@ -508,6 +514,32 @@ func TestGetReceiptErrorReasonErrorFromReceiptRevert(t *testing.T) {
assert.False(t, res.Success)
}

func TestGetReceiptNoDebugTraceIfDisabled(t *testing.T) {

ctx, c, mRPC, done := newTestConnector(t)
defer done()

mRPC.On("CallRPC", mock.Anything, mock.Anything, "eth_getTransactionReceipt",
mock.MatchedBy(func(txHash string) bool {
assert.Equal(t, "0x7d48ae971faf089878b57e3c28e3035540d34f38af395958d2c73c36c57c83a2", txHash)
return true
})).
Return(nil).
Run(func(args mock.Arguments) {
err := json.Unmarshal([]byte(sampleJSONRPCReceiptFailed), args[1])
assert.NoError(t, err)
})
mRPC.AssertNotCalled(t, "CallRPC", mock.Anything, mock.Anything, "debug_traceTransaction")
var req ffcapi.TransactionReceiptRequest
err := json.Unmarshal([]byte(sampleGetReceipt), &req)
assert.NoError(t, err)
res, reason, err := c.TransactionReceipt(ctx, &req)
assert.NoError(t, err)
assert.Empty(t, reason)
assert.False(t, res.Success)
mRPC.AssertExpectations(t)
}

func TestProtocolIDForReceipt(t *testing.T) {
assert.Equal(t, "000000012345/000042", ProtocolIDForReceipt(fftypes.NewFFBigInt(12345), fftypes.NewFFBigInt(42)))
assert.Equal(t, "", ProtocolIDForReceipt(nil, nil))
Expand Down
1 change: 1 addition & 0 deletions internal/msgs/en_config_descriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ var (
ConfigEventsFilterPollingInterval = ffc("config.connector.events.filterPollingInterval", "The interval between polling calls to a filter, when checking for newly arrived events", i18n.TimeDurationType)
ConfigTxCacheSize = ffc("config.connector.txCacheSize", "Maximum of transactions to hold in the transaction info cache", i18n.IntType)
ConfigMaxConcurrentRequests = ffc("config.connector.maxConcurrentRequests", "Maximum of concurrent requests to be submitted to the blockchain", i18n.IntType)
ConfigTraceTXForRevertReason = ffc("config.connector.traceTXForRevertReason", "Enable the use of transaction trace functions (e.g. debug_traceTransaction) to obtain transaction revert reasons. This can place a high load on the EVM client.", i18n.BooleanType)
)
Loading