diff --git a/eth/api_backend.go b/eth/api_backend.go index 3c2672f0a0..5d3afb093a 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -171,7 +172,7 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B return nil, nil, err } if header == nil { - return nil, nil, errors.New("header not found") + return nil, nil, ethapi.ErrHeaderNotFound } stateDb, err := b.eth.BlockChain().StateAt(header.Root) return stateDb, header, err @@ -382,8 +383,8 @@ func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Blo return b.eth.stateAtTransaction(block, txIndex, reexec) } -func (b *EthAPIBackend) SequencerRPCService() *rpc.Client { - return b.eth.seqRPCService +func (b *EthAPIBackend) HistoricalRPCService() *rpc.Client { + return b.eth.historicalRPCService } func (b *EthAPIBackend) Genesis() *types.Block { diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index f2f4a5765d..774192529f 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -22,10 +22,15 @@ import ( "errors" "fmt" "math/big" + "net" + "net/http" "reflect" "testing" "time" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" @@ -211,7 +216,62 @@ var testTx2 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), & To: &common.Address{2}, }) +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 { + return hexutil.Bytes("test"), nil + } + return nil, ethapi.ErrHeaderNotFound +} + +func (m *mockHistoricalBackend) EstimateGas(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) { + num, ok := blockNrOrHash.Number() + if ok && num == 100 { + return hexutil.Uint64(12345), nil + } + return 0, ethapi.ErrHeaderNotFound +} + +func newMockHistoricalBackend(t *testing.T) string { + s := rpc.NewServer() + err := node.RegisterApis([]rpc.API{ + { + Namespace: "eth", + Service: new(mockHistoricalBackend), + Public: true, + Authenticated: false, + }, + }, nil, s) + if err != nil { + t.Fatalf("error creating mock historical backend: %v", err) + } + + hdlr := node.NewHTTPHandlerStack(s, []string{"*"}, []string{"*"}, nil) + mux := http.NewServeMux() + mux.Handle("/", hdlr) + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("error creating mock historical backend listener: %v", err) + } + + go func() { + httpS := &http.Server{Handler: mux} + httpS.Serve(listener) + + t.Cleanup(func() { + httpS.Shutdown(context.Background()) + }) + }() + + return fmt.Sprintf("http://%s", listener.Addr().String()) +} + func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { + histAddr := newMockHistoricalBackend(t) + // Generate test chain. blocks := generateTestChain() @@ -223,6 +283,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { // Create Ethereum Service config := ðconfig.Config{Genesis: genesis} config.Ethash.PowMode = ethash.ModeFake + config.RollupHistoricalRPC = histAddr ethservice, err := eth.New(n, config) if err != nil { t.Fatalf("can't create new ethereum service: %v", err) @@ -294,6 +355,9 @@ func TestEthClient(t *testing.T) { "TransactionSender": { func(t *testing.T) { testTransactionSender(t, client) }, }, + "EstimateGas": { + func(t *testing.T) { testEstimateGas(t, client) }, + }, } t.Parallel() @@ -585,6 +649,14 @@ 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) { @@ -692,6 +764,35 @@ func testTransactionSender(t *testing.T, client *rpc.Client) { } } +func testEstimateGas(t *testing.T, client *rpc.Client) { + ec := NewClient(client) + + // EstimateGas + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + Value: big.NewInt(1), + } + gas, err := ec.EstimateGas(context.Background(), msg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gas != 21000 { + t.Fatalf("unexpected gas price: %v", gas) + } + + // historical case + var res hexutil.Uint64 + err = client.CallContext(context.Background(), &res, "eth_estimateGas", toCallArg(msg), rpc.BlockNumberOrHashWithNumber(100)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if res != 12345 { + t.Fatalf("invalid result: %d", res) + } +} + func sendTransaction(ec *Client) error { chainID, err := ec.ChainID(context.Background()) if err != nil { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 6f7be496b9..ad39cd3464 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -48,6 +48,8 @@ import ( "github.com/tyler-smith/go-bip39" ) +var ErrHeaderNotFound = errors.New("header not found") + // EthereumAPI provides an API to access Ethereum related information. type EthereumAPI struct { b Backend @@ -1014,7 +1016,11 @@ func (e *revertError) ErrorData() interface{} { // useful to execute and retrieve values. func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap()) - if err != nil { + if errors.Is(err, ErrHeaderNotFound) && 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 { return nil, err } // If the result contains a revert reason, try to unpack and return it. @@ -1151,7 +1157,17 @@ func (s *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, b if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash } - return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) + + res, err := DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) + if errors.Is(err, ErrHeaderNotFound) && 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 + } + + return res, err } // RPCMarshalHeader converts the given header to the RPC output . diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 56bd2c2805..355c8b896f 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -86,7 +86,7 @@ type Backend interface { ChainConfig() *params.ChainConfig Engine() consensus.Engine - SequencerRPCService() *rpc.Client + HistoricalRPCService() *rpc.Client Genesis() *types.Block // eth/filters needs to be initialized from this backend type, so methods needed by diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index ce3ca8230f..ef6ba15d70 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -339,6 +339,6 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) return nil } -func (b *backendMock) Engine() consensus.Engine { return nil } -func (b *backendMock) SequencerRPCService() *rpc.Client { return nil } -func (b *backendMock) Genesis() *types.Block { return nil } +func (b *backendMock) Engine() consensus.Engine { return nil } +func (b *backendMock) HistoricalRPCService() *rpc.Client { return nil } +func (b *backendMock) Genesis() *types.Block { return nil } diff --git a/les/api_backend.go b/les/api_backend.go index 00abee3f1b..cad9ad5494 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -330,8 +330,8 @@ func (b *LesApiBackend) StateAtTransaction(ctx context.Context, block *types.Blo return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) } -func (b *LesApiBackend) SequencerRPCService() *rpc.Client { - return b.eth.seqRPCService +func (b *LesApiBackend) HistoricalRPCService() *rpc.Client { + return b.eth.historicalRPCService } func (b *LesApiBackend) Genesis() *types.Block {