diff --git a/internal/ethapi/debug_api.go b/internal/ethapi/debug_api.go new file mode 100644 index 0000000000..b52aa80545 --- /dev/null +++ b/internal/ethapi/debug_api.go @@ -0,0 +1,63 @@ +package ethapi + +import ( + "context" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" +) + +type StorageBatchRequest struct { + Address common.Address `json:"address"` + Slots []common.Hash `json:"slots"` +} + +type StorageBatchResponse map[string]string + +type DebugAPI struct { + b Backend +} + +type Backend interface { + BlockByNumber(ctx context.Context, number rpc.BlockNumber) (Block, error) + StateAt(root common.Hash) (StateDB, error) +} + +type Block interface { + Root() common.Hash +} + +type StateDB interface { + GetState(addr common.Address, slot common.Hash) common.Hash +} + +func (api *DebugAPI) BatchGetStorageAt(ctx context.Context, reqs []StorageBatchRequest, blockNr rpc.BlockNumber) (map[string]StorageBatchResponse, error) { + res := make(map[string]StorageBatchResponse) + block, err := api.b.BlockByNumber(ctx, blockNr) + if err != nil { return nil, err } + + statedb, err := api.b.StateAt(block.Root()) + if err != nil { return nil, err } + + var wg sync.WaitGroup + var mu sync.Mutex + + for _, req := range reqs { + wg.Add(1) + go func(req StorageBatchRequest) { + defer wg.Done() + batchRes := make(StorageBatchResponse) + for _, slot := range req.Slots { + value := statedb.GetState(req.Address, slot) + batchRes[slot.Hex()] = value.Hex() + } + mu.Lock() + res[req.Address.Hex()] = batchRes + mu.Unlock() + }(req) + } + + wg.Wait() + return res, nil +} diff --git a/internal/ethapi/debug_api_test.go b/internal/ethapi/debug_api_test.go new file mode 100644 index 0000000000..4f16cb87a2 --- /dev/null +++ b/internal/ethapi/debug_api_test.go @@ -0,0 +1,36 @@ +package ethapi + +import ( + "context" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" +) + +type dummyBackend struct{} +func (b *dummyBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (Block, error) { return &dummyBlock{}, nil } +func (b *dummyBackend) StateAt(root common.Hash) (StateDB, error) { return &dummyState{}, nil } + +type dummyBlock struct{} +func (b *dummyBlock) Root() common.Hash { return common.Hash{} } + +type dummyState struct{} +func (s *dummyState) GetState(addr common.Address, slot common.Hash) common.Hash { + return common.HexToHash("0x1234") +} + +func TestBatchGetStorageAt(t *testing.T) { + api := &DebugAPI{b: &dummyBackend{}} + reqs := []StorageBatchRequest{ + {Address: common.HexToAddress("0xabc"), Slots: []common.Hash{common.HexToHash("0x0"), common.HexToHash("0x1")}}, + {Address: common.HexToAddress("0xdef"), Slots: []common.Hash{common.HexToHash("0x0")}}, + } + + res, err := api.BatchGetStorageAt(context.Background(), reqs, "latest") + if err != nil { t.Fatal(err) } + + if res["0xabc"]["0x0"] != "0x0000000000000000000000000000000000001234" { + t.Errorf("unexpected value for 0xabc 0x0: %s", res["0xabc"]["0x0"]) + } +}