diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 6452fcf37c..22a59aab58 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -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 } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 3b72742e95..80a9036ecc 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -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}}}) +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 6a5f3c9a8a..8622109e21 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -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',