Skip to content
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
23 changes: 23 additions & 0 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
54 changes: 51 additions & 3 deletions eth/tracers/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down