Major refactor

This commit is contained in:
Sina Mahmoodi 2026-02-10 17:19:19 +00:00
parent 7a5fffa380
commit 1a6808e169
4 changed files with 134 additions and 140 deletions

View file

@ -53,6 +53,10 @@ import (
// allowed to produce in order to speed up calculations. // allowed to produce in order to speed up calculations.
const estimateGasErrorRatio = 0.015 const estimateGasErrorRatio = 0.015
// maxGetStorageSlots is the maximum total number of storage slots that can
// be requested in a single eth_getStorageValues call.
const maxGetStorageSlots = 1024
var errBlobTxNotSupported = errors.New("signing blob transactions not supported") var errBlobTxNotSupported = errors.New("signing blob transactions not supported")
var errSubClosed = errors.New("chain subscription closed") var errSubClosed = errors.New("chain subscription closed")
@ -589,6 +593,41 @@ func (api *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Addre
return res[:], state.Error() return res[:], state.Error()
} }
// 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) {
// Count total slots requested.
var totalSlots int
for _, keys := range requests {
totalSlots += len(keys)
if totalSlots > maxGetStorageSlots {
return nil, &clientLimitExceededError{message: fmt.Sprintf("too many slots (max %d)", maxGetStorageSlots)}
}
}
if totalSlots == 0 {
return nil, &invalidParamsError{message: "empty request"}
}
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
result := make(map[common.Address][]hexutil.Bytes, len(requests))
for addr, keys := range requests {
vals := make([]hexutil.Bytes, len(keys))
for i, key := range keys {
v := state.GetState(addr, key)
vals[i] = v[:]
}
if err := state.Error(); err != nil {
return nil, err
}
result[addr] = vals
}
return result, nil
}
// GetBlockReceipts returns the block receipts for the given block hash or number or tag. // 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) { func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) {
var ( var (
@ -1925,36 +1964,7 @@ type DebugAPI struct {
// NewDebugAPI creates a new instance of DebugAPI. // NewDebugAPI creates a new instance of DebugAPI.
func NewDebugAPI(b Backend) *DebugAPI { func NewDebugAPI(b Backend) *DebugAPI {
return &DebugAPI{b: b} return &DebugAPI{b: b}
}
// BatchGetStorage returns multiple storage slots for multiple accounts at the given block.
// Params: {address: [hexKey, ...], ...}, blockNrOrHash
// Returns a map {address: [value,...]} preserving the order within each key list.
func (api *DebugAPI) BatchGetStorage(ctx context.Context, req map[common.Address][]string, blockNrOrHash rpc.BlockNumberOrHash) (map[common.Address][]hexutil.Bytes, error) {
state, _, err := api.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
out := make(map[common.Address][]hexutil.Bytes, len(req))
for addr, keys := range req {
vals := make([]hexutil.Bytes, len(keys))
for i, k := range keys {
key, _, err := decodeHash(k)
if err != nil {
return nil, fmt.Errorf("unable to decode storage key for %s at index %d: %w", addr.Hex(), i, err)
}
v := state.GetState(addr, key)
vv := make([]byte, len(v))
copy(vv, v[:])
vals[i] = vv
}
out[addr] = vals
}
if err := state.Error(); err != nil {
return nil, err
}
return out, nil
} }
// GetRawHeader retrieves the RLP encoding for a single header. // GetRawHeader retrieves the RLP encoding for a single header.

View file

@ -4065,3 +4065,91 @@ func TestSendRawTransactionSync_Timeout(t *testing.T) {
t.Fatalf("expected ErrorData=%s, got %v", want, got) t.Fatalf("expected ErrorData=%s, got %v", want, got)
} }
} }
func TestGetStorageValues(t *testing.T) {
t.Parallel()
var (
addr1 = common.HexToAddress("0x1111")
addr2 = common.HexToAddress("0x2222")
slot0 = common.Hash{}
slot1 = common.BigToHash(big.NewInt(1))
slot2 = common.BigToHash(big.NewInt(2))
val0 = common.BigToHash(big.NewInt(42))
val1 = common.BigToHash(big.NewInt(100))
val2 = common.BigToHash(big.NewInt(200))
genesis = &core.Genesis{
Config: params.MergedTestChainConfig,
Alloc: types.GenesisAlloc{
addr1: {
Balance: big.NewInt(params.Ether),
Storage: map[common.Hash]common.Hash{
slot0: val0,
slot1: val1,
},
},
addr2: {
Balance: big.NewInt(params.Ether),
Storage: map[common.Hash]common.Hash{
slot2: val2,
},
},
},
}
)
api := NewBlockChainAPI(newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
b.SetPoS()
}))
latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
// Happy path: multiple addresses, multiple slots.
result, err := api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{
addr1: {slot0, slot1},
addr2: {slot2},
}, latest)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(result) != 2 {
t.Fatalf("expected 2 addresses in result, got %d", len(result))
}
if got := common.BytesToHash(result[addr1][0]); got != val0 {
t.Errorf("addr1 slot0: want %x, got %x", val0, got)
}
if got := common.BytesToHash(result[addr1][1]); got != val1 {
t.Errorf("addr1 slot1: want %x, got %x", val1, got)
}
if got := common.BytesToHash(result[addr2][0]); got != val2 {
t.Errorf("addr2 slot2: want %x, got %x", val2, got)
}
// Missing slot returns zero.
result, err = api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{
addr1: {common.HexToHash("0xff")},
}, latest)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got := common.BytesToHash(result[addr1][0]); got != (common.Hash{}) {
t.Errorf("missing slot: want zero, got %x", got)
}
// Empty request returns error.
_, err = api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{}, latest)
if err == nil {
t.Fatal("expected error for empty request")
}
// Exceeding slot limit returns error.
tooMany := make([]common.Hash, maxGetStorageSlots+1)
for i := range tooMany {
tooMany[i] = common.BigToHash(big.NewInt(int64(i)))
}
_, err = api.GetStorageValues(context.Background(), map[common.Address][]common.Hash{
addr1: tooMany,
}, latest)
if err == nil {
t.Fatal("expected error for exceeding slot limit")
}
}

View file

@ -1,105 +0,0 @@
package ethapi
import (
"context"
"testing"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
// storageBackend composes backendMock and overrides StateAndHeaderByNumberOrHash
// to return a prepared in-memory StateDB for testing.
type storageBackend struct {
*backendMock
state *state.StateDB
}
func (b *storageBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
return b.state, nil, nil
}
// Ensure storageBackend still satisfies the Backend interface by forwarding
// the remaining methods via the embedded backendMock (compile-time check).
var _ Backend = (*storageBackend)(nil)
func TestDebugBatchGetStorage(t *testing.T) {
t.Parallel()
// Prepare an in-memory state database and set some storage values.
sdb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
addr1 := common.HexToAddress("0x1000000000000000000000000000000000000001")
addr2 := common.HexToAddress("0x2000000000000000000000000000000000000002")
keyA := common.HexToHash("0x01")
keyB := common.HexToHash("0x02")
keyC := common.HexToHash("0x03")
valA := common.HexToHash("0xaaa")
valB := common.HexToHash("0xbbb")
valC := common.HexToHash("0xccc")
sdb.SetState(addr1, keyA, valA)
sdb.SetState(addr1, keyB, valB)
sdb.SetState(addr2, keyC, valC)
// Wire the API with a backend that returns our state.
base := newBackendMock()
b := &storageBackend{backendMock: base, state: sdb}
api := NewDebugAPI(b)
// Build request: addr1 asks [keyB, keyA] (order test), addr2 asks [keyC, missing]
req := map[common.Address][]string{
addr1: {"0x02", "0x01"},
addr2: {"0x03", "0x04"},
}
got, err := api.BatchGetStorage(context.Background(), req, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
if err != nil {
t.Fatalf("BatchGetStorage returned error: %v", err)
}
// Validate addr1 order and values
if len(got[addr1]) != 2 {
t.Fatalf("addr1 length mismatch: got %d want 2", len(got[addr1]))
}
if hexutil.Bytes(got[addr1][0]).String() != hexutil.Bytes(valB[:]).String() {
t.Fatalf("addr1[0] mismatch: got %s want %s", hexutil.Bytes(got[addr1][0]).String(), hexutil.Bytes(valB[:]).String())
}
if hexutil.Bytes(got[addr1][1]).String() != hexutil.Bytes(valA[:]).String() {
t.Fatalf("addr1[1] mismatch: got %s want %s", hexutil.Bytes(got[addr1][1]).String(), hexutil.Bytes(valA[:]).String())
}
// Validate addr2 values (existing and zero for missing)
if len(got[addr2]) != 2 {
t.Fatalf("addr2 length mismatch: got %d want 2", len(got[addr2]))
}
if hexutil.Bytes(got[addr2][0]).String() != hexutil.Bytes(valC[:]).String() {
t.Fatalf("addr2[0] mismatch: got %s want %s", hexutil.Bytes(got[addr2][0]).String(), hexutil.Bytes(valC[:]).String())
}
if hexutil.Bytes(got[addr2][1]).String() != hexutil.Bytes(common.Hash{}[:]).String() {
t.Fatalf("addr2[1] mismatch: got %s want %s (zero)", hexutil.Bytes(got[addr2][1]).String(), hexutil.Bytes(common.Hash{}[:]).String())
}
}
// Ensure backendMock compiles in this file by referencing needed imports
// (no-ops; prevents unused import errors if interface evolves).
var (
_ = accounts.Account{}
_ = core.ChainEvent{}
_ vm.Config
_ ethdb.Database
_ event.Subscription
_ params.ChainConfig
)

View file

@ -415,11 +415,6 @@ web3._extend({
call: 'debug_storageRangeAt', call: 'debug_storageRangeAt',
params: 5, params: 5,
}), }),
new web3._extend.Method({
name: 'batchGetStorage',
call: 'debug_batchGetStorage',
params: 2,
}),
new web3._extend.Method({ new web3._extend.Method({
name: 'getModifiedAccountsByNumber', name: 'getModifiedAccountsByNumber',
call: 'debug_getModifiedAccountsByNumber', call: 'debug_getModifiedAccountsByNumber',
@ -572,6 +567,12 @@ web3._extend({
params: 3, params: 3,
inputFormatter: [web3._extend.formatters.inputAddressFormatter, null, web3._extend.formatters.inputBlockNumberFormatter] inputFormatter: [web3._extend.formatters.inputAddressFormatter, null, web3._extend.formatters.inputBlockNumberFormatter]
}), }),
new web3._extend.Method({
name: 'getStorageValues',
call: 'eth_getStorageValues',
params: 2,
inputFormatter: [null, web3._extend.formatters.inputBlockNumberFormatter]
}),
new web3._extend.Method({ new web3._extend.Method({
name: 'createAccessList', name: 'createAccessList',
call: 'eth_createAccessList', call: 'eth_createAccessList',