From 5041002b7aac4fc86e9cff5c19c48409d61d98df Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Tue, 4 Mar 2025 11:00:49 -0800 Subject: [PATCH] fix(zetacore): allow object for tracerConfig (#3622) * fix(zetacore): allow object for tracerConfig * add empty test case * use explicit error return * changelog --- changelog.md | 1 + rpc/backend/backend.go | 4 +- rpc/backend/tracing.go | 44 ++++++++++++-- rpc/backend/tracing_convert_test.go | 88 ++++++++++++++++++++++++++++ rpc/backend/tracing_test.go | 7 ++- rpc/namespaces/ethereum/debug/api.go | 6 +- rpc/types/types.go | 6 ++ 7 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 rpc/backend/tracing_convert_test.go diff --git a/changelog.md b/changelog.md index 856020bdd5..bff8c76f3b 100644 --- a/changelog.md +++ b/changelog.md @@ -42,6 +42,7 @@ * [3509](https://github.com/zeta-chain/node/pull/3509) - schedule Bitcoin TSS keysign on interval to avoid TSS keysign spam * [3517](https://github.com/zeta-chain/node/pull/3517) - remove duplicate gateway event appending to fix false positive on multiple events in same tx * [3602](https://github.com/zeta-chain/node/pull/3602) - hardcode gas limits to avoid estimate gas calls +* [3622](https://github.com/zeta-chain/node/pull/3622) - allow object for tracerConfig in `debug_traceTransaction` RPC ### Tests diff --git a/rpc/backend/backend.go b/rpc/backend/backend.go index 7a71a3da73..ec02edd71b 100644 --- a/rpc/backend/backend.go +++ b/rpc/backend/backend.go @@ -165,10 +165,10 @@ type EVMBackend interface { BloomStatus() (uint64, uint64) // Tracing - TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) + TraceTransaction(hash common.Hash, config *rpctypes.TraceConfig) (interface{}, error) TraceBlock( height rpctypes.BlockNumber, - config *evmtypes.TraceConfig, + config *rpctypes.TraceConfig, block *tmrpctypes.ResultBlock, ) ([]*evmtypes.TxTraceResult, error) } diff --git a/rpc/backend/tracing.go b/rpc/backend/tracing.go index b228f24c67..68fa11b7e8 100644 --- a/rpc/backend/tracing.go +++ b/rpc/backend/tracing.go @@ -30,7 +30,7 @@ import ( // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) { +func (b *Backend) TraceTransaction(hash common.Hash, config *rpctypes.TraceConfig) (interface{}, error) { // Get transaction by hash transaction, _, err := b.GetTxByEthHash(hash) if err != nil { @@ -80,7 +80,10 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi } if config != nil { - traceTxRequest.TraceConfig = config + traceTxRequest.TraceConfig, err = convertConfig(config) + if err != nil { + return nil, err + } } // minus one to get the context of block beginning @@ -109,7 +112,7 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi // executes all the transactions contained within. The return value will be one item // per transaction, dependent on the requested tracer. func (b *Backend) TraceBlock(height rpctypes.BlockNumber, - config *evmtypes.TraceConfig, + config *rpctypes.TraceConfig, block *tmrpctypes.ResultBlock, ) ([]*evmtypes.TxTraceResult, error) { txs := block.Block.Txs @@ -135,9 +138,14 @@ func (b *Backend) TraceBlock(height rpctypes.BlockNumber, } ctxWithHeight := rpctypes.ContextWithHeight(int64(contextHeight)) + traceConfig, err := convertConfig(config) + if err != nil { + return nil, err + } + traceBlockRequest := &evmtypes.QueryTraceBlockRequest{ Txs: msgs, - TraceConfig: config, + TraceConfig: traceConfig, BlockNumber: block.Block.Height, BlockTime: block.Block.Time, BlockHash: common.Bytes2Hex(block.BlockID.Hash), @@ -157,3 +165,31 @@ func (b *Backend) TraceBlock(height rpctypes.BlockNumber, return decodedResults, nil } + +func convertConfig(config *rpctypes.TraceConfig) (*evmtypes.TraceConfig, error) { + if config == nil { + return &evmtypes.TraceConfig{}, nil + } + + cfg := config.TraceConfig + + if config.TracerConfig != nil { + switch v := config.TracerConfig.(type) { + case string: + // It's already a string, use it directly + cfg.TracerJsonConfig = v + case map[string]interface{}: + // this is the compliant style + // we need to encode it to a string before passing it to the ethermint side + jsonBytes, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("unable to encode traceConfig to JSON: %w", err) + } + cfg.TracerJsonConfig = string(jsonBytes) + default: + return nil, errors.New("unexpected traceConfig type") + } + } + + return &cfg, nil +} diff --git a/rpc/backend/tracing_convert_test.go b/rpc/backend/tracing_convert_test.go new file mode 100644 index 0000000000..0a4cb72d0f --- /dev/null +++ b/rpc/backend/tracing_convert_test.go @@ -0,0 +1,88 @@ +package backend + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + rpctypes "github.com/zeta-chain/node/rpc/types" +) + +const expectedConvertedValue = `{"onlyTopCall":false}` + +const brokenStyleRaw = ` +{ + "tracer": "callTracer", + "tracerConfig": "{\"onlyTopCall\":false}" +} +` + +const compliantStyleRaw = ` +{ + "tracer": "callTracer", + "tracerConfig": {"onlyTopCall":false} +} +` + +const emptyStyleRaw = ` +{ + "tracer": "callTracer" +} +` + +const invalidStyleRaw = ` +{ + "tracer": "callTracer", + "tracerConfig": [] +} +` + +func TestConvertConfig(t *testing.T) { + tests := []struct { + name string + input string + expectError bool + expected string + }{ + { + name: "broken style", + input: brokenStyleRaw, + expectError: false, + expected: expectedConvertedValue, + }, + { + name: "compliant style", + input: compliantStyleRaw, + expectError: false, + expected: expectedConvertedValue, + }, + { + name: "empty style", + input: emptyStyleRaw, + expectError: false, + expected: "", + }, + { + name: "invalid style", + input: invalidStyleRaw, + expectError: true, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rpcConfig := &rpctypes.TraceConfig{} + err := json.Unmarshal([]byte(tt.input), rpcConfig) + require.NoError(t, err) + + ethermintConfig, err := convertConfig(rpcConfig) + if tt.expectError { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.expected, ethermintConfig.TracerJsonConfig) + }) + } +} diff --git a/rpc/backend/tracing_test.go b/rpc/backend/tracing_test.go index 9fc30eda1c..443820df52 100644 --- a/rpc/backend/tracing_test.go +++ b/rpc/backend/tracing_test.go @@ -14,6 +14,7 @@ import ( "github.com/zeta-chain/ethermint/crypto/ethsecp256k1" "github.com/zeta-chain/ethermint/indexer" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + rpctypes "github.com/zeta-chain/node/rpc/types" "github.com/zeta-chain/node/rpc/backend/mocks" ) @@ -272,7 +273,7 @@ func (suite *BackendTestSuite) TestTraceBlock() { registerMock func() expTraceResults []*evmtypes.TxTraceResult resBlock *tmrpctypes.ResultBlock - config *evmtypes.TraceConfig + config *rpctypes.TraceConfig expPass bool }{ { @@ -280,7 +281,7 @@ func (suite *BackendTestSuite) TestTraceBlock() { func() {}, []*evmtypes.TxTraceResult{}, &resBlockEmpty, - &evmtypes.TraceConfig{}, + &rpctypes.TraceConfig{}, true, }, { @@ -293,7 +294,7 @@ func (suite *BackendTestSuite) TestTraceBlock() { }, []*evmtypes.TxTraceResult{}, &resBlockFilled, - &evmtypes.TraceConfig{}, + &rpctypes.TraceConfig{}, false, }, } diff --git a/rpc/namespaces/ethereum/debug/api.go b/rpc/namespaces/ethereum/debug/api.go index 3bb389cb79..85245c098d 100644 --- a/rpc/namespaces/ethereum/debug/api.go +++ b/rpc/namespaces/ethereum/debug/api.go @@ -72,7 +72,7 @@ func NewAPI( // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) { +func (a *API) TraceTransaction(hash common.Hash, config *rpctypes.TraceConfig) (interface{}, error) { a.logger.Debug("debug_traceTransaction", "hash", hash) return a.backend.TraceTransaction(hash, config) } @@ -81,7 +81,7 @@ func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) ( // EVM and returns them as a JSON object. func (a *API) TraceBlockByNumber( height rpctypes.BlockNumber, - config *evmtypes.TraceConfig, + config *rpctypes.TraceConfig, ) ([]*evmtypes.TxTraceResult, error) { a.logger.Debug("debug_traceBlockByNumber", "height", height) if height == 0 { @@ -99,7 +99,7 @@ func (a *API) TraceBlockByNumber( // TraceBlockByHash returns the structured logs created during the execution of // EVM and returns them as a JSON object. -func (a *API) TraceBlockByHash(hash common.Hash, config *evmtypes.TraceConfig) ([]*evmtypes.TxTraceResult, error) { +func (a *API) TraceBlockByHash(hash common.Hash, config *rpctypes.TraceConfig) ([]*evmtypes.TxTraceResult, error) { a.logger.Debug("debug_traceBlockByHash", "hash", hash) // Get Tendermint Block resBlock, err := a.backend.TendermintBlockByHash(hash) diff --git a/rpc/types/types.go b/rpc/types/types.go index 0d53851866..f900f6b127 100644 --- a/rpc/types/types.go +++ b/rpc/types/types.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" + evmtypes "github.com/zeta-chain/ethermint/x/evm/types" ) // Copied the Account and StorageResult types since they are registered under an @@ -115,3 +116,8 @@ type OneFeeHistory struct { Reward []*big.Int // each element of the array will have the tip provided to miners for the percentile given GasUsedRatio float64 // the ratio of gas used to the gas limit for each block } + +type TraceConfig struct { + evmtypes.TraceConfig + TracerConfig interface{} `json:"tracerConfig"` +}