From 9f9cb0f71e540f963670ba895548f2067eb893cb Mon Sep 17 00:00:00 2001 From: jeevan-sid Date: Thu, 5 Feb 2026 19:15:18 +0530 Subject: [PATCH] feat: batch get storage --- internal/ethapi/api.go | 19 +++++++++++++++ internal/ethapi/api_test.go | 46 +++++++++++++++++++++++++++++++++++++ internal/web3ext/web3ext.go | 5 ++++ 3 files changed, 70 insertions(+) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 5fbe7db694..ed9458470b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -589,6 +589,25 @@ func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Addre return res[:], state.Error() } +// GetStorageAtBatch returns multiple storage slots for the keys from the state at the given address, +// block number. +func (api *BlockChainAPI) BatchGetStorageAt(ctx context.Context, address common.Address, hexKeys []string, blockNrOrHash rpc.BlockNumberOrHash) ([]hexutil.Bytes, error) { + state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if state == nil || err != nil { + return nil, err + } + results := make([]hexutil.Bytes, len(hexKeys)) + for i, hexKey := range hexKeys { + key, _, err := decodeStorageKey(hexKey) + if err != nil { + return nil, &invalidParamsError{fmt.Sprintf("index %d: %v: %q", i, err, hexKey)} + } + res := state.GetState(address, key) + results[i] = res[:] + } + return results, state.Error() +} + // GetBlockReceipts returns the block receipts for the given block hash or number or tag. func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { var ( diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 837df8b662..9a0fcb42c4 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -4065,3 +4065,49 @@ func TestSendRawTransactionSync_Timeout(t *testing.T) { t.Fatalf("expected ErrorData=%s, got %v", want, got) } } + +func TestBatchGetStorageAt(t *testing.T) { + t.Parallel() + + addr := common.HexToAddress("0xdeadbeef") + key1 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001") + key2 := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000002") + val1 := common.HexToHash("0x11") + val2 := common.HexToHash("0x22") + + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + addr: { + Balance: big.NewInt(1000), + Storage: map[common.Hash]common.Hash{ + key1: val1, + key2: val2, + }, + }, + }, + } + + backend := newTestBackend(t, 1, genesis, ethash.NewFaker(), nil) + api := NewBlockChainAPI(backend) + keys := []string{ + key1.Hex(), + key2.Hex(), + } + bn := rpc.LatestBlockNumber + blockNr := rpc.BlockNumberOrHash{BlockNumber: &bn} + results, err := api.BatchGetStorageAt(context.Background(), addr, keys, blockNr) + if err != nil { + t.Fatalf("BatchGetStorageAt failed: %v", err) + } + + if len(results) != 2 { + t.Fatalf("Expected 2 results, got %d", len(results)) + } + if !bytes.Equal(results[0], val1.Bytes()) { + t.Errorf("Slot 1 mismatch: have %x, want %x", results[0], val1.Bytes()) + } + if !bytes.Equal(results[1], val2.Bytes()) { + t.Errorf("Slot 2 mismatch: have %x, want %x", results[1], val2.Bytes()) + } +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 0aedffe230..f226bb0cd3 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -415,6 +415,11 @@ web3._extend({ call: 'debug_storageRangeAt', params: 5, }), + new web3._extend.Method({ + name: 'batchGetStorageAt', + call: 'debug_batchGetStorageAt', + params: 3, + }), new web3._extend.Method({ name: 'getModifiedAccountsByNumber', call: 'debug_getModifiedAccountsByNumber',