internal/ethapi: default block parameter to latest on state methods (#35100)

Make the `Block` parameter optional on the six state-reading methods,
defaulting to `latest` when omitted:

- `eth_getBalance`
- `eth_getCode`
- `eth_getStorageAt`
- `eth_getTransactionCount`
- `eth_getProof`
- `eth_getStorageValues`

This implements the behavior proposed in https://github.com/ethereum/execution-apis/pull/812.

---------

Co-authored-by: Sina M <1591639+s1na@users.noreply.github.com>
This commit is contained in:
Chase Wright 2026-06-03 05:35:12 -05:00 committed by GitHub
parent 80d9ba5d97
commit 6b451a4245
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 116 additions and 23 deletions

View file

@ -328,11 +328,21 @@ func (api *BlockChainAPI) BlockNumber() hexutil.Uint64 {
return hexutil.Uint64(header.Number.Uint64())
}
// blockNrOrHashOrLatest resolves an optional block selector, defaulting to the
// latest block when the parameter was omitted by the caller (nil).
func blockNrOrHashOrLatest(blockNrOrHash *rpc.BlockNumberOrHash) rpc.BlockNumberOrHash {
if blockNrOrHash != nil {
return *blockNrOrHash
}
return rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
}
// GetBalance returns the amount of wei for the given address in the state of the
// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta
// block numbers are also allowed.
func (api *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
// block numbers are also allowed. When the block parameter is omitted, it
// defaults to the latest block.
func (api *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash *rpc.BlockNumberOrHash) (*hexutil.Big, error) {
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHashOrLatest(blockNrOrHash))
if state == nil || err != nil {
return nil, err
}
@ -371,7 +381,8 @@ func (n *proofList) Delete(key []byte) error {
}
// GetProof returns the Merkle-proof for a given account and optionally some storage keys.
func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) {
// When the block parameter is omitted, it defaults to the latest block.
func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash *rpc.BlockNumberOrHash) (*AccountResult, error) {
if len(storageKeys) > maxGetProofKeys {
return nil, &invalidParamsError{fmt.Sprintf("too many storage keys requested (max %d, got %d)", maxGetProofKeys, len(storageKeys))}
}
@ -388,7 +399,7 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
return nil, &invalidParamsError{fmt.Sprintf("%v: %q", err, hexKey)}
}
}
statedb, header, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
statedb, header, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHashOrLatest(blockNrOrHash))
if statedb == nil || err != nil {
return nil, err
}
@ -584,8 +595,9 @@ func (api *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHas
}
// GetCode returns the code stored at the given address in the state for the given block number.
func (api *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
// When the block parameter is omitted, it defaults to the latest block.
func (api *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHashOrLatest(blockNrOrHash))
if state == nil || err != nil {
return nil, err
}
@ -595,9 +607,10 @@ func (api *BlockChainAPI) GetCode(ctx context.Context, address common.Address, b
// GetStorageAt returns the storage from the state at the given address, key and
// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block
// numbers are also allowed.
func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, hexKey string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
// numbers are also allowed. When the block parameter is omitted, it defaults to
// the latest block.
func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, hexKey string, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHashOrLatest(blockNrOrHash))
if state == nil || err != nil {
return nil, err
}
@ -610,8 +623,9 @@ func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Addre
}
// GetStorageValues returns multiple storage slot values for multiple accounts
// at the given block.
func (api *BlockChainAPI) GetStorageValues(ctx context.Context, requests map[common.Address][]common.Hash, blockNrOrHash rpc.BlockNumberOrHash) (map[common.Address][]hexutil.Bytes, error) {
// at the given block. When the block parameter is omitted, it defaults to the
// latest block.
func (api *BlockChainAPI) GetStorageValues(ctx context.Context, requests map[common.Address][]common.Hash, blockNrOrHash *rpc.BlockNumberOrHash) (map[common.Address][]hexutil.Bytes, error) {
// Count total slots requested.
var totalSlots int
for _, keys := range requests {
@ -624,7 +638,7 @@ func (api *BlockChainAPI) GetStorageValues(ctx context.Context, requests map[com
return nil, &invalidParamsError{message: "empty request"}
}
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHashOrLatest(blockNrOrHash))
if state == nil || err != nil {
return nil, err
}
@ -1483,10 +1497,12 @@ func (api *TransactionAPI) GetRawTransactionByBlockHashAndIndex(ctx context.Cont
return nil
}
// GetTransactionCount returns the number of transactions the given address has sent for the given block number
func (api *TransactionAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) {
// GetTransactionCount returns the number of transactions the given address has sent for the given block number.
// When the block parameter is omitted, it defaults to the latest block.
func (api *TransactionAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash *rpc.BlockNumberOrHash) (*hexutil.Uint64, error) {
bnh := blockNrOrHashOrLatest(blockNrOrHash)
// Ask transaction pool for the nonce which includes pending transactions
if blockNr, ok := blockNrOrHash.Number(); ok && blockNr == rpc.PendingBlockNumber {
if blockNr, ok := bnh.Number(); ok && blockNr == rpc.PendingBlockNumber {
nonce, err := api.b.GetPoolNonce(ctx, address)
if err != nil {
return nil, err
@ -1494,7 +1510,7 @@ func (api *TransactionAPI) GetTransactionCount(ctx context.Context, address comm
return (*hexutil.Uint64)(&nonce), nil
}
// Resolve block number and use its state to ask for the nonce
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, bnh)
if state == nil || err != nil {
return nil, err
}

View file

@ -4216,7 +4216,7 @@ func TestGetStorageValues(t *testing.T) {
result, err := api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{
addr1: {slot0, slot1},
addr2: {slot2},
}, latest)
}, &latest)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -4236,7 +4236,7 @@ func TestGetStorageValues(t *testing.T) {
// Missing slot returns zero.
result, err = api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{
addr1: {common.HexToHash("0xff")},
}, latest)
}, &latest)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -4245,7 +4245,7 @@ func TestGetStorageValues(t *testing.T) {
}
// Empty request returns error.
_, err = api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{}, latest)
_, err = api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{}, &latest)
if err == nil {
t.Fatal("expected error for empty request")
}
@ -4257,8 +4257,85 @@ func TestGetStorageValues(t *testing.T) {
}
_, err = api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{
addr1: tooMany,
}, latest)
}, &latest)
if err == nil {
t.Fatal("expected error for exceeding slot limit")
}
}
// TestStateMethodsDefaultToLatest verifies that the state-reading methods
// default the optional block parameter to "latest".
func TestStateMethodsDefaultToLatest(t *testing.T) {
t.Parallel()
var (
accounts = newAccounts(2)
slot = common.HexToHash("0x01")
val = common.HexToHash("0x42")
code = []byte{0x60, 0x00, 0x60, 0x00}
genesis = &core.Genesis{
Config: params.MergedTestChainConfig,
Alloc: types.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {
Balance: big.NewInt(2 * params.Ether),
Nonce: 7,
Code: code,
Storage: map[common.Hash]common.Hash{slot: val},
},
},
}
acc = accounts[1].addr
ctx = context.Background()
)
backend := newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
b.SetPoS()
})
srv := rpc.NewServer()
if err := srv.RegisterName("eth", NewBlockChainAPI(backend)); err != nil {
t.Fatal(err)
}
if err := srv.RegisterName("eth", NewTransactionAPI(backend, new(AddrLocker))); err != nil {
t.Fatal(err)
}
client := rpc.DialInProc(srv)
defer client.Close()
// call invokes method twice: once omitting the block param and once passing
// "latest" explicitly. Both must succeed and return identical results.
call := func(name string, dst func() any, explicit []any, omitted []any) {
t.Helper()
gotOmitted := dst()
if err := client.CallContext(ctx, gotOmitted, name, omitted...); err != nil {
t.Fatalf("%s with omitted block: unexpected error: %v", name, err)
}
gotLatest := dst()
if err := client.CallContext(ctx, gotLatest, name, explicit...); err != nil {
t.Fatalf("%s with explicit latest: unexpected error: %v", name, err)
}
o, _ := json.Marshal(gotOmitted)
l, _ := json.Marshal(gotLatest)
if !bytes.Equal(o, l) {
t.Errorf("%s: omitted-block result %s != latest result %s", name, o, l)
}
}
call("eth_getBalance",
func() any { return new(hexutil.Big) },
[]any{acc, "latest"}, []any{acc})
call("eth_getCode",
func() any { return new(hexutil.Bytes) },
[]any{acc, "latest"}, []any{acc})
call("eth_getTransactionCount",
func() any { return new(hexutil.Uint64) },
[]any{acc, "latest"}, []any{acc})
call("eth_getStorageAt",
func() any { return new(hexutil.Bytes) },
[]any{acc, slot, "latest"}, []any{acc, slot})
call("eth_getProof",
func() any { return new(AccountResult) },
[]any{acc, []string{slot.Hex()}, "latest"}, []any{acc, []string{slot.Hex()}})
call("eth_getStorageValues",
func() any { return new(map[common.Address][]hexutil.Bytes) },
[]any{map[common.Address][]common.Hash{acc: {slot}}, "latest"},
[]any{map[common.Address][]common.Hash{acc: {slot}}})
}

View file

@ -566,13 +566,13 @@ web3._extend({
name: 'getProof',
call: 'eth_getProof',
params: 3,
inputFormatter: [web3._extend.formatters.inputAddressFormatter, null, web3._extend.formatters.inputBlockNumberFormatter]
inputFormatter: [web3._extend.formatters.inputAddressFormatter, null, web3._extend.formatters.inputDefaultBlockNumberFormatter]
}),
new web3._extend.Method({
name: 'getStorageValues',
call: 'eth_getStorageValues',
params: 2,
inputFormatter: [null, web3._extend.formatters.inputBlockNumberFormatter]
inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter]
}),
new web3._extend.Method({
name: 'createAccessList',