diff --git a/domain/ethereum.go b/domain/ethereum.go index 24d8b8d3..2a3a6f03 100644 --- a/domain/ethereum.go +++ b/domain/ethereum.go @@ -289,6 +289,29 @@ type TransactionReceipt struct { TransactionIndex *string `json:"transactionIndex"` } +// TraceCallTransaction contains the fields of the to-be-simulated transaction. +type TraceCallTransaction struct { + From string `json:"from"` + To string `json:"to"` + Gas *int64 `json:"gas,omitempty"` + GasPrice *int64 `json:"gasPrice,omitempty"` + Value *int64 `json:"value,omitempty"` + Data string `json:"data"` +} + +// TraceCallConfig contains the tracer configuration to be used while simulating the transaction. +type TraceCallConfig struct { + Tracer string `json:"tracer,omitempty"` + TracerConfig *TracerConfig `json:"tracerConfig,omitempty"` + StateOverrides map[string]interface{} `json:"stateOverrides,omitempty"` +} + +// TracerConfig contains some extra tracer parameters. +type TracerConfig struct { + WithLog bool `json:"withLog,omitempty"` + OnlyTopCall bool `json:"onlyTopCall,omitempty"` +} + // TraceAction is an element of a trace_block Trace response type TraceAction struct { From *string `json:"from"` diff --git a/ethereum/client.go b/ethereum/client.go index 4493a676..b31d9ab7 100644 --- a/ethereum/client.go +++ b/ethereum/client.go @@ -63,19 +63,27 @@ type Client interface { TransactionReceipt(ctx context.Context, txHash string) (*domain.TransactionReceipt, error) ChainID(ctx context.Context) (*big.Int, error) TraceBlock(ctx context.Context, number *big.Int) ([]domain.Trace, error) + DebugTraceCall( + ctx context.Context, req *domain.TraceCallTransaction, + block any, traceCallConfig domain.TraceCallConfig, + result interface{}, + ) error GetLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) SubscribeToHead(ctx context.Context) (domain.HeaderCh, error) health.Reporter } -const blocksByNumber = "eth_getBlockByNumber" -const blocksByHash = "eth_getBlockByHash" -const blockNumber = "eth_blockNumber" -const getLogs = "eth_getLogs" -const transactionReceipt = "eth_getTransactionReceipt" -const traceBlock = "trace_block" -const chainId = "eth_chainId" +const ( + blocksByNumber = "eth_getBlockByNumber" + blocksByHash = "eth_getBlockByHash" + blockNumber = "eth_blockNumber" + getLogs = "eth_getLogs" + transactionReceipt = "eth_getTransactionReceipt" + traceBlock = "trace_block" + debugTraceCall = "debug_traceCall" + chainId = "eth_chainId" +) const defaultRetryInterval = time.Second * 15 @@ -242,6 +250,39 @@ func (e *streamEthClient) TraceBlock(ctx context.Context, number *big.Int) ([]do return result, err } +// DebugTraceCall returns the traces of a call. +func (e *streamEthClient) DebugTraceCall( + ctx context.Context, req *domain.TraceCallTransaction, + block any, traceCallConfig domain.TraceCallConfig, + result interface{}, +) error { + name := fmt.Sprintf("%s(%v)", debugTraceCall, req) + log.Debugf(name) + + switch block.(type) { + case string: + case *rpc.BlockNumberOrHash: + default: + return errors.New("invalid block number type") + } + + args := []interface{}{req, block, traceCallConfig} + + err := withBackoff(ctx, name, func(ctx context.Context) error { + err := e.rpcClient.CallContext(ctx, &result, debugTraceCall, args...) + if err != nil { + return err + } + + return nil + }, RetryOptions{ + MinBackoff: pointDur(e.retryInterval), + MaxElapsedTime: pointDur(1 * time.Minute), + MaxBackoff: pointDur(e.retryInterval), + }, nil, nil) + return err +} + // GetLogs returns the set of logs for a block func (e *streamEthClient) GetLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { name := fmt.Sprintf("%s(%v)", getLogs, q) diff --git a/ethereum/mocks/mock_client.go b/ethereum/mocks/mock_client.go index a6a10ce1..54255c8f 100644 --- a/ethereum/mocks/mock_client.go +++ b/ethereum/mocks/mock_client.go @@ -570,6 +570,20 @@ func (mr *MockClientMockRecorder) Close() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockClient)(nil).Close)) } +// DebugTraceCall mocks base method. +func (m *MockClient) DebugTraceCall(ctx context.Context, req *domain.TraceCallTransaction, block any, traceCallConfig domain.TraceCallConfig, result interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DebugTraceCall", ctx, req, block, traceCallConfig, result) + ret0, _ := ret[0].(error) + return ret0 +} + +// DebugTraceCall indicates an expected call of DebugTraceCall. +func (mr *MockClientMockRecorder) DebugTraceCall(ctx, req, block, traceCallConfig, result interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DebugTraceCall", reflect.TypeOf((*MockClient)(nil).DebugTraceCall), ctx, req, block, traceCallConfig, result) +} + // GetLogs mocks base method. func (m *MockClient) GetLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { m.ctrl.T.Helper()