diff --git a/core/state_transition.go b/core/state_transition.go index 3d7107472a11..43f89f20c2d6 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -428,7 +428,9 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { st.state.AddBalance(st.evm.Context.Coinbase, fee) } - if optimismConfig := st.evm.ChainConfig().Optimism; optimismConfig != nil { + // Hack: Check that we are post bedrock to enable op-geth to be able to create pseudo pre-bedrock blocks (these are pre-bedrock, but don't follow l2 geth rules) + // Note optimismConfig will not be nil if rules.IsOptimismBedrock is true + if optimismConfig := st.evm.ChainConfig().Optimism; optimismConfig != nil && rules.IsOptimismBedrock { st.state.AddBalance(params.OptimismBaseFeeRecipient, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee)) if cost := st.evm.Context.L1CostFunc(st.evm.Context.BlockNumber.Uint64(), st.msg); cost != nil { st.state.AddBalance(params.OptimismL1FeeRecipient, cost) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index a09467c16d04..a416d7c67c59 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -23,12 +23,12 @@ import ( "encoding/json" "errors" "fmt" + "math/big" "os" "runtime" "sync" "time" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" @@ -138,7 +138,7 @@ func (api *API) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*typ return nil, err } if block == nil { - return nil, fmt.Errorf("block #%d %w", number, ethereum.NotFound) + return nil, fmt.Errorf("block #%d not found", number) } return block, nil } @@ -151,7 +151,7 @@ func (api *API) blockByHash(ctx context.Context, hash common.Hash) (*types.Block return nil, err } if block == nil { - return nil, fmt.Errorf("block %s %w", hash.Hex(), ethereum.NotFound) + return nil, fmt.Errorf("block %s not found", hash.Hex()) } return block, nil } @@ -231,6 +231,7 @@ type txTraceTask struct { // TraceChain returns the structured logs created during the execution of EVM // between two blocks (excluding start) and returns them as a JSON object. func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { // Fetch the block interval that we want to trace + // TODO: Need to implement a fallback for this from, err := api.blockByNumber(ctx, start) if err != nil { return nil, err @@ -456,16 +457,23 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed // EVM and returns them as a JSON object. func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { block, err := api.blockByNumber(ctx, number) - if errors.Is(err, ethereum.NotFound) && api.backend.HistoricalRPCService() != nil { - var histResult []*txTraceResult - err = api.backend.HistoricalRPCService().CallContext(ctx, &histResult, "debug_traceBlockByNumber", number, config) - if err != nil && err.Error() == "not found" { - return nil, fmt.Errorf("block #%d %w", number, ethereum.NotFound) - } - return histResult, err - } else if err != nil { + if err != nil { return nil, err } + + if api.backend.ChainConfig().IsOptimismPreBedrock(block.Number()) { + if api.backend.HistoricalRPCService() != nil { + var histResult []*txTraceResult + err = api.backend.HistoricalRPCService().CallContext(ctx, &histResult, "debug_traceBlockByNumber", number, config) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return histResult, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } + return api.traceBlock(ctx, block, config) } @@ -473,16 +481,23 @@ func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, // EVM and returns them as a JSON object. func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { block, err := api.blockByHash(ctx, hash) - if errors.Is(err, ethereum.NotFound) && api.backend.HistoricalRPCService() != nil { - var histResult []*txTraceResult - err = api.backend.HistoricalRPCService().CallContext(ctx, &histResult, "debug_traceBlockByHash", hash, config) - if err != nil && err.Error() == "not found" { - return nil, fmt.Errorf("block #%d %w", hash, ethereum.NotFound) - } - return histResult, err - } else if err != nil { + if err != nil { return nil, err } + + if api.backend.ChainConfig().IsOptimismPreBedrock(block.Number()) { + if api.backend.HistoricalRPCService() != nil { + var histResult []*txTraceResult + err = api.backend.HistoricalRPCService().CallContext(ctx, &histResult, "debug_traceBlockByHash", hash, config) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return histResult, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } + return api.traceBlock(ctx, block, config) } @@ -532,6 +547,7 @@ func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, // of intermediate roots: the stateroot after each transaction. func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config *TraceConfig) ([]common.Hash, error) { block, _ := api.blockByHash(ctx, hash) + // TODO: Cannot get intermediate roots for pre-bedrock block without daisy chain if block == nil { // Check in the bad blocks block = rawdb.ReadBadBlock(api.backend.ChainDb(), hash) @@ -823,18 +839,24 @@ func containsTx(block *types.Block, hash common.Hash) bool { // and returns them as a JSON object. func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { // GetTransaction returns 0 for the blocknumber if the transaction is not found - tx, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) + _, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) if err != nil { return nil, err } - if tx == nil && api.backend.HistoricalRPCService() != nil { - var histResult []*txTraceResult - err = api.backend.HistoricalRPCService().CallContext(ctx, &histResult, "debug_traceTransaction", hash, config) - if err != nil && err.Error() == "not found" { - return nil, fmt.Errorf("transaction %s %w", hash, ethereum.NotFound) + + if api.backend.ChainConfig().IsOptimismPreBedrock(new(big.Int).SetUint64(blockNumber)) { + if api.backend.HistoricalRPCService() != nil { + var histResult json.RawMessage + err := api.backend.HistoricalRPCService().CallContext(ctx, &histResult, "debug_traceTransaction", hash, config) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return histResult, nil + } else { + return nil, rpc.ErrNoHistoricalFallback } - return histResult, err } + // It shouldn't happen in practice. if blockNumber == 0 { return nil, errors.New("genesis is not traceable") @@ -885,32 +907,14 @@ 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 } + + if api.backend.ChainConfig().IsOptimismPreBedrock(block.Number()) { + return nil, errors.New("l2geth does not have a debug_traceCall method") + } + // try to recompute the state reexec := defaultTraceReexec if config != nil && config.Reexec != nil { diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 97e27c07426b..8f1606b05086 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -32,7 +32,7 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" @@ -49,60 +49,53 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/mock" ) var ( - errStateNotFound = errors.New("state not found") - errBlockNotFound = errors.New("block not found") - errFailingUpstream = errors.New("historical query failed") + errStateNotFound = errors.New("state not found") + errBlockNotFound = errors.New("block not found") + errTransactionNotFound = errors.New("transaction not found") ) -type mockHistoricalBackend struct{} - -func (m *mockHistoricalBackend) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { - if hash == common.HexToHash("0xabba") { - result := make([]*txTraceResult, 1) - result[0] = &txTraceResult{Result: "0xabba"} - return result, nil - } - return nil, ethereum.NotFound +type mockHistoricalBackend struct { + mock.Mock } func (m *mockHistoricalBackend) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { - if number == 999 { - result := make([]*txTraceResult, 1) - result[0] = &txTraceResult{Result: "0xabba"} - return result, nil - } - return nil, ethereum.NotFound + ret := m.Mock.MethodCalled("TraceBlockByNumber", number, config) + return ret[0].([]*txTraceResult), *ret[1].(*error) +} + +func (m *mockHistoricalBackend) ExpectTraceBlockByNumber(number rpc.BlockNumber, config *TraceConfig, out []*txTraceResult, err error) { + m.Mock.On("TraceBlockByNumber", number, config).Once().Return(out, &err) } func (m *mockHistoricalBackend) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { - if hash == common.HexToHash("0xACDC") { - result := make([]*txTraceResult, 1) - result[0] = &txTraceResult{Result: "0x8888"} - return result, nil - } - return nil, ethereum.NotFound + ret := m.Mock.MethodCalled("TraceTransaction", hash, config) + return ret[0], *ret[1].(*error) +} + +func (m *mockHistoricalBackend) ExpectTraceTransaction(hash common.Hash, config *TraceConfig, out interface{}, err error) { + jsonOut, _ := json.Marshal(out) + m.Mock.On("TraceTransaction", hash, config).Once().Return(json.RawMessage(jsonOut), &err) } 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 + ret := m.Mock.MethodCalled("TraceCall", args, blockNrOrHash, config) + return ret[0], *ret[1].(*error) } -func newMockHistoricalBackend(t *testing.T) string { +func (m *mockHistoricalBackend) ExpectTraceCall(args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig, out interface{}, err error) { + m.Mock.On("TraceCall", args, blockNrOrHash, config).Once().Return(out, &err) +} + +func newMockHistoricalBackend(t *testing.T, backend *mockHistoricalBackend) string { s := rpc.NewServer() err := node.RegisterApis([]rpc.API{ { Namespace: "debug", - Service: new(mockHistoricalBackend), + Service: backend, Public: true, Authenticated: false, }, @@ -141,13 +134,15 @@ type testBackend struct { refHook func() // Hook is invoked when the requested state is referenced relHook func() // Hook is invoked when the requested state is released - historical *rpc.Client + historical *rpc.Client + mockHistorical *mockHistoricalBackend } // testBackend creates a new test backend. OBS: After test is done, teardown must be // invoked in order to release associated resources. func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { - historicalAddr := newMockHistoricalBackend(t) + mock := new(mockHistoricalBackend) + historicalAddr := newMockHistoricalBackend(t, mock) historicalClient, err := rpc.Dial(historicalAddr) if err != nil { @@ -155,10 +150,11 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i i } backend := &testBackend{ - chainConfig: gspec.Config, - engine: ethash.NewFaker(), - chaindb: rawdb.NewMemoryDatabase(), - historical: historicalClient, + chainConfig: gspec.Config, + engine: ethash.NewFaker(), + chaindb: rawdb.NewMemoryDatabase(), + historical: historicalClient, + mockHistorical: mock, } // Generate blocks for testing _, blocks, _ := core.GenerateChainWithGenesis(gspec, backend.engine, n, generator) @@ -207,7 +203,7 @@ func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash) if tx == nil { - return nil, common.Hash{}, 0, 0, nil + return nil, common.Hash{}, 0, 0, errTransactionNotFound } return tx, hash, blockNumber, index, nil } @@ -348,43 +344,9 @@ func TestTraceCall(t *testing.T) { Value: (*hexutil.Big)(big.NewInt(1000)), }, config: nil, - expectErr: fmt.Errorf("block #%d %w", genBlocks+1, ethereum.NotFound), + expectErr: fmt.Errorf("block #%d not found", genBlocks+1), //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, @@ -430,10 +392,8 @@ 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, got %v", i, testspec.expectErr, err) + if !reflect.DeepEqual(err, testspec.expectErr) { + t.Errorf("test %d: error mismatch, want %v, git %v", i, testspec.expectErr, err) } } else { if err != nil { @@ -495,17 +455,58 @@ func TestTraceTransaction(t *testing.T) { }) { t.Error("Transaction tracing result is different") } +} +func TestTraceTransactionHistorical(t *testing.T) { + t.Parallel() - // test TraceTransaction for a historical transaction - result2, err := api.TraceTransaction(context.Background(), common.HexToHash("0xACDC"), nil) - resBytes, _ := json.Marshal(result2) - have2 := string(resBytes) + // Initialize test accounts + accounts := newAccounts(2) + genesis := &core.Genesis{ + Config: params.AllOptimismProtocolChanges, + Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + target := common.Hash{} + signer := types.HomesteadSigner{} + backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) + b.AddTx(tx) + target = tx.Hash() + }) + defer backend.mockHistorical.AssertExpectations(t) + defer backend.chain.Stop() + backend.mockHistorical.ExpectTraceTransaction( + target, + nil, + logger.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []logger.StructLogRes{}, + }, + nil) + api := NewAPI(backend) + result, err := api.TraceTransaction(context.Background(), target, nil) if err != nil { - t.Errorf("want no error, have %v", err) + t.Errorf("Failed to trace transaction %v", err) } - want2 := `[{"result":"0x8888"}]` - if have2 != want2 { - t.Errorf("test result mismatch, have\n%v\n, want\n%v\n", have2, want2) + var have *logger.ExecutionResult + spew.Dump(result) + if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil { + t.Errorf("failed to unmarshal result %v", err) + } + if !reflect.DeepEqual(have, &logger.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []logger.StructLogRes{}, + }) { + t.Error("Transaction tracing result is different") } } @@ -553,12 +554,7 @@ func TestTraceBlock(t *testing.T) { // Trace non-existent block { blockNumber: rpc.BlockNumber(genBlocks + 1), - expectErr: fmt.Errorf("block #%d %w", genBlocks+1, ethereum.NotFound), - }, - // Optimism: Trace block on the historical chain - { - blockNumber: rpc.BlockNumber(999), - want: `[{"result":"0xabba"}]`, + expectErr: fmt.Errorf("block #%d not found", genBlocks+1), }, // Trace latest block { @@ -578,7 +574,7 @@ func TestTraceBlock(t *testing.T) { t.Errorf("test %d, want error %v", i, tc.expectErr) continue } - if err.Error() != tc.expectErr.Error() { + if !reflect.DeepEqual(err, tc.expectErr) { t.Errorf("test %d: error mismatch, want %v, get %v", i, tc.expectErr, err) } continue @@ -595,6 +591,59 @@ func TestTraceBlock(t *testing.T) { } } +func TestTraceBlockHistorical(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{ + Config: params.AllOptimismProtocolChanges, + Alloc: core.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + accounts[2].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + genBlocks := 10 + signer := types.HomesteadSigner{} + backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) + b.AddTx(tx) + }) + defer backend.mockHistorical.AssertExpectations(t) + defer backend.chain.Stop() + api := NewAPI(backend) + + var expectErr error + var config *TraceConfig + blockNumber := rpc.BlockNumber(3) + want := `[{"result":{"failed":false,"gas":21000,"returnValue":"","structLogs":[]}}]` + var ret []*txTraceResult + _ = json.Unmarshal([]byte(want), &ret) + + backend.mockHistorical.ExpectTraceBlockByNumber(blockNumber, config, ret, nil) + + result, err := api.TraceBlockByNumber(context.Background(), blockNumber, config) + if expectErr != nil { + if err == nil { + t.Errorf("want error %v", expectErr) + } + if !reflect.DeepEqual(err, expectErr) { + t.Errorf("error mismatch, want %v, get %v", expectErr, err) + } + } + if err != nil { + t.Errorf("want no error, have %v", err) + } + have, _ := json.Marshal(result) + if string(have) != want { + t.Errorf("result mismatch, have\n%v\n, want\n%v\n", string(have), want) + } +} + func TestTracingWithOverrides(t *testing.T) { t.Parallel() // Initialize test accounts diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 66c703a68726..96ea2e2a065c 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -199,6 +199,14 @@ var genesis = &core.Genesis{ BaseFee: big.NewInt(params.InitialBaseFee), } +var genesisForHistorical = &core.Genesis{ + Config: params.AllOptimismProtocolChanges, + Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, + ExtraData: []byte("test genesis"), + Timestamp: 9000, + BaseFee: big.NewInt(params.InitialBaseFee), +} + var testTx1 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{ Nonce: 0, Value: big.NewInt(12), @@ -219,7 +227,7 @@ type mockHistoricalBackend struct{} func (m *mockHistoricalBackend) Call(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *ethapi.StateOverride) (hexutil.Bytes, error) { num, ok := blockNrOrHash.Number() - if ok && num == 100 { + if ok && num == 1 { return hexutil.Bytes("test"), nil } return nil, ethereum.NotFound @@ -227,7 +235,7 @@ func (m *mockHistoricalBackend) Call(ctx context.Context, args ethapi.Transactio func (m *mockHistoricalBackend) EstimateGas(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) { num, ok := blockNrOrHash.Number() - if ok && num == 100 { + if ok && num == 1 { return hexutil.Uint64(12345), nil } return 0, ethereum.NotFound @@ -268,11 +276,11 @@ func newMockHistoricalBackend(t *testing.T) string { return fmt.Sprintf("http://%s", listener.Addr().String()) } -func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { +func newTestBackend(t *testing.T, enableHistoricalState bool) (*node.Node, []*types.Block) { histAddr := newMockHistoricalBackend(t) // Generate test chain. - blocks := generateTestChain() + blocks := generateTestChain(enableHistoricalState) // Create node n, err := node.New(&node.Config{}) @@ -280,9 +288,17 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { t.Fatalf("can't create new node: %v", err) } // Create Ethereum Service - config := ðconfig.Config{Genesis: genesis} + var actualGenesis *core.Genesis + if enableHistoricalState { + actualGenesis = genesisForHistorical + } else { + actualGenesis = genesis + } + config := ðconfig.Config{Genesis: actualGenesis} config.Ethash.PowMode = ethash.ModeFake - config.RollupHistoricalRPC = histAddr + if enableHistoricalState { + config.RollupHistoricalRPC = histAddr + } ethservice, err := eth.New(n, config) if err != nil { t.Fatalf("can't create new ethereum service: %v", err) @@ -297,7 +313,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { return n, blocks } -func generateTestChain() []*types.Block { +func generateTestChain(enableHistoricalState bool) []*types.Block { generate := func(i int, g *core.BlockGen) { g.OffsetTime(5) g.SetExtra([]byte("test")) @@ -307,12 +323,27 @@ func generateTestChain() []*types.Block { g.AddTx(testTx2) } } - _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 2, generate) - return append([]*types.Block{genesis.ToBlock()}, blocks...) + var actualGenesis *core.Genesis + if enableHistoricalState { + actualGenesis = genesisForHistorical + } else { + actualGenesis = genesis + } + _, blocks, _ := core.GenerateChainWithGenesis(actualGenesis, ethash.NewFaker(), 2, generate) + return append([]*types.Block{actualGenesis.ToBlock()}, blocks...) +} + +func TestEthClientHistoricalBackend(t *testing.T) { + backend, _ := newTestBackend(t, true) + client, _ := backend.Attach() + defer backend.Close() + defer client.Close() + + testHistoricalRPC(t, client) } func TestEthClient(t *testing.T) { - backend, chain := newTestBackend(t) + backend, chain := newTestBackend(t, false) client, _ := backend.Attach() defer backend.Close() defer client.Close() @@ -644,14 +675,6 @@ func testCallContract(t *testing.T, client *rpc.Client) { if _, err := ec.PendingCallContract(context.Background(), msg); err != nil { t.Fatalf("unexpected error: %v", err) } - // Historical - histVal, err := ec.CallContract(context.Background(), msg, big.NewInt(100)) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if string(histVal) != "test" { - t.Fatalf("expected %s to equal test", string(histVal)) - } } func testAtFunctions(t *testing.T, client *rpc.Client) { @@ -776,16 +799,35 @@ func testEstimateGas(t *testing.T, client *rpc.Client) { if gas != 21000 { t.Fatalf("unexpected gas price: %v", gas) } +} + +func testHistoricalRPC(t *testing.T, client *rpc.Client) { + ec := NewClient(client) - // historical case + // Estimate Gas RPC + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + Value: big.NewInt(1), + } var res hexutil.Uint64 - err = client.CallContext(context.Background(), &res, "eth_estimateGas", toCallArg(msg), rpc.BlockNumberOrHashWithNumber(100)) + err := client.CallContext(context.Background(), &res, "eth_estimateGas", toCallArg(msg), rpc.BlockNumberOrHashWithNumber(1)) if err != nil { t.Fatalf("unexpected error: %v", err) } if res != 12345 { t.Fatalf("invalid result: %d", res) } + + // Call Contract RPC + histVal, err := ec.CallContract(context.Background(), msg, big.NewInt(1)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(histVal) != "test" { + t.Fatalf("expected %s to equal test", string(histVal)) + } } func sendTransaction(ec *Client) error { diff --git a/go.mod b/go.mod index eda8e663c6bb..aeac587f4cb0 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/rs/cors v1.7.0 github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 - github.com/stretchr/testify v1.7.2 + github.com/stretchr/testify v1.8.1 github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef @@ -97,6 +97,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tklauser/numcpus v0.2.2 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect diff --git a/go.sum b/go.sum index 91ba28e7743c..ec1d7d2de1db 100644 --- a/go.sum +++ b/go.sum @@ -390,14 +390,21 @@ github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57N github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344 h1:m+8fKfQwCAy1QjzINvKe/pYtLjo2dl59x2w9YSEJxuY= github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 181bcb0470a2..6e647cdf81d0 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -28,7 +28,6 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/tyler-smith/go-bip39" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -51,8 +50,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -var ErrHeaderNotFound = fmt.Errorf("header %w", ethereum.NotFound) - // EthereumAPI provides an API to access Ethereum related information. type EthereumAPI struct { b Backend @@ -1045,12 +1042,22 @@ func (e *revertError) ErrorData() interface{} { // Note, this function doesn't make and changes in the state/blockchain and is // useful to execute and retrieve values. func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { + header, err := s.b.HeaderByNumberOrHash(ctx, blockNrOrHash) + if err == nil && header != nil && s.b.ChainConfig().IsOptimismPreBedrock(header.Number) { + if s.b.HistoricalRPCService() != nil { + var histResult hexutil.Bytes + err := s.b.HistoricalRPCService().CallContext(ctx, &histResult, "eth_call", args, blockNrOrHash, overrides) + if err != nil { + return nil, fmt.Errorf("historical backend error: %w", err) + } + return histResult, nil + } else { + return nil, rpc.ErrNoHistoricalFallback + } + } + result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap()) - if errors.Is(err, ethereum.NotFound) && s.b.HistoricalRPCService() != nil { - var histResult hexutil.Bytes - err = s.b.HistoricalRPCService().CallContext(ctx, &histResult, "eth_call", args, blockNrOrHash, overrides) - return histResult, err - } else if err != nil { + if err != nil { return nil, err } // If the result contains a revert reason, try to unpack and return it. @@ -1188,16 +1195,21 @@ func (s *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, b bNrOrHash = *blockNrOrHash } - res, err := DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) - if errors.Is(err, ethereum.NotFound) && s.b.HistoricalRPCService() != nil { - var result hexutil.Uint64 - err := s.b.HistoricalRPCService().CallContext(ctx, &result, "eth_estimateGas", args, blockNrOrHash) - return result, err - } else if err != nil { - return 0, err + header, err := s.b.HeaderByNumberOrHash(ctx, bNrOrHash) + if err == nil && header != nil && s.b.ChainConfig().IsOptimismPreBedrock(header.Number) { + if s.b.HistoricalRPCService() != nil { + var result hexutil.Uint64 + err := s.b.HistoricalRPCService().CallContext(ctx, &result, "eth_estimateGas", args, blockNrOrHash) + if err != nil { + return 0, fmt.Errorf("historical backend error: %w", err) + } + return result, nil + } else { + return 0, rpc.ErrNoHistoricalFallback + } } - return res, err + return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) } // RPCMarshalHeader converts the given header to the RPC output . diff --git a/rpc/errors.go b/rpc/errors.go index 9a19e9fe67f5..f8fd348986fc 100644 --- a/rpc/errors.go +++ b/rpc/errors.go @@ -64,6 +64,16 @@ const ( errcodeMarshalError = -32603 ) +var ErrNoHistoricalFallback = NoHistoricalFallbackError{} + +type NoHistoricalFallbackError struct{} + +func (e NoHistoricalFallbackError) ErrorCode() int { return -32801 } + +func (e NoHistoricalFallbackError) Error() string { + return "no historical RPC is available for this historical (pre-bedrock) execution request" +} + type methodNotFoundError struct{ method string } func (e *methodNotFoundError) ErrorCode() int { return -32601 }