diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 8d1cb264ab..ac313f8205 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -860,6 +860,29 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc } else { return nil, errors.New("invalid arguments; neither block nor hash specified") } + + // If block still holds no value, but we have an error, then one of the two previous conditions + // was entered, meaning: + // 1. blockNrOrHash has either a valid block or hash + // 2. we don't have that block locally + if block == nil && errors.Is(err, ethereum.NotFound) && api.backend.HistoricalRPCService() != nil { + var histResult json.RawMessage + err = api.backend.HistoricalRPCService().CallContext(ctx, &histResult, "debug_traceCall", args, blockNrOrHash, config) + if err != nil && err.Error() == "not found" { + // Not found locally or in history. We need to return different errors based on the input + // in order match geth's native behavior + if hash, ok := blockNrOrHash.Hash(); ok { + return nil, fmt.Errorf("block %s %w", hash, ethereum.NotFound) + } else if number, ok := blockNrOrHash.Number(); ok { + return nil, fmt.Errorf("block #%d %w", number, ethereum.NotFound) + } + } else if err != nil { + return nil, fmt.Errorf("error querying historical RPC: %w", err) + } + + return histResult, nil + } + if err != nil { return nil, err } diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index a96f3efdad..9ed538414f 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -51,8 +51,9 @@ import ( ) var ( - errStateNotFound = errors.New("state not found") - errBlockNotFound = errors.New("block not found") + errStateNotFound = errors.New("state not found") + errBlockNotFound = errors.New("block not found") + errFailingUpstream = errors.New("historical query failed") ) type mockHistoricalBackend struct{} @@ -84,6 +85,17 @@ func (m *mockHistoricalBackend) TraceTransaction(ctx context.Context, hash commo return nil, ethereum.NotFound } +func (m *mockHistoricalBackend) TraceCall(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { + num, ok := blockNrOrHash.Number() + if ok && num == 777 { + return json.RawMessage(`{"gas":21000,"failed":false,"returnValue":"777","structLogs":[]}`), nil + } + if ok && num == 12345 { + return nil, errFailingUpstream + } + return nil, ethereum.NotFound +} + func newMockHistoricalBackend(t *testing.T) string { s := rpc.NewServer() err := node.RegisterApis([]rpc.API{ @@ -323,6 +335,40 @@ func TestTraceCall(t *testing.T) { expectErr: fmt.Errorf("block #%d %w", genBlocks+1, ethereum.NotFound), //expect: nil, }, + // Optimism: Trace block on the historical chain + { + blockNumber: rpc.BlockNumber(777), + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: `{"gas":21000,"failed":false,"returnValue":"777","structLogs":[]}`, + }, + // Optimism: Trace block that doesn't exist anywhere + { + blockNumber: rpc.BlockNumber(39347856), + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: fmt.Errorf("block #39347856 %w", ethereum.NotFound), + }, + // Optimism: Trace block with failing historical upstream + { + blockNumber: rpc.BlockNumber(12345), + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: fmt.Errorf("error querying historical RPC: %w", errFailingUpstream), + }, // Standard JSON trace upon the latest block { blockNumber: rpc.LatestBlockNumber, @@ -368,8 +414,10 @@ func TestTraceCall(t *testing.T) { t.Errorf("test %d: expect error %v, got nothing", i, testspec.expectErr) continue } + // Have to introduce this diff to reflect the fact that errors + // from the upstream will not preserve pointer equality. if err.Error() != testspec.expectErr.Error() { - t.Errorf("test %d: error mismatch, want %v, git %v", i, testspec.expectErr, err) + t.Errorf("test %d: error mismatch, want %v, got %v", i, testspec.expectErr, err) } } else { if err != nil {