diff --git a/eth/api.go b/eth/api.go index 8afa21a389..e9379e625e 100644 --- a/eth/api.go +++ b/eth/api.go @@ -17,6 +17,7 @@ package eth import ( + "bytes" "compress/gzip" "context" "errors" @@ -334,6 +335,70 @@ func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, return results, nil } +type AccountRangeResult struct { + Preimages []common.Hash `json:"preimages"` + Next common.Hash `json:"nextKey"` +} + +func accountRange(st state.Trie, start *common.Hash, maxResults int) (AccountRangeResult, error) { + it := trie.NewIterator(st.NodeIterator(start[:])) + result := AccountRangeResult{Preimages: []common.Hash{}, Next: common.Hash{}} + zeros := make([]byte, common.HashLength) + + if bytes.Equal(start.Bytes()[:], zeros) { + start = nil + } + + if maxResults > 100 { + maxResults = 100 + } + + // dont return the first account preimage + if !it.Next() && start != &(common.Hash{}) { + return result, nil + } + + for i := 0; i < maxResults-1 && it.Next(); i++ { + result.Preimages = append(result.Preimages, common.BytesToHash(it.Key)) + } + + if it.Next() { + result.Next = common.BytesToHash(it.Key) + } + + return result, nil +} + +//enumerate and return account preimages in the state at the current block +func (api *PrivateDebugAPI) AccountRange(ctx context.Context, startAddr *common.Hash, maxResults int) (AccountRangeResult, error) { + var statedb *state.StateDB = nil + var err error = nil + var block = api.eth.blockchain.CurrentBlock() + + if len(block.Transactions()) == 0 { + parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return AccountRangeResult{}, fmt.Errorf("parent %x not found", block.ParentHash()) + } + statedb, err = api.computeStateDB(parent, defaultTraceReexec) + if err != nil { + return AccountRangeResult{}, err + } + } else { + _, _, statedb, err = api.computeTxEnv(block.Hash(), len(block.Transactions())-1, 0) + if err != nil { + return AccountRangeResult{}, err + } + } + + trie, err := statedb.Database().OpenTrie(block.Header().Root) + if err != nil { + return AccountRangeResult{}, err + } + + return accountRange(trie, startAddr, maxResults) +} + // StorageRangeResult is the result of a debug_storageRangeAt API call. type StorageRangeResult struct { Storage storageMap `json:"storage"` diff --git a/eth/api_test.go b/eth/api_test.go index cdd5bb8e34..a67a6f8352 100644 --- a/eth/api_test.go +++ b/eth/api_test.go @@ -19,7 +19,9 @@ package eth import ( "reflect" "testing" + "math/big" + "github.com/ethereum/go-ethereum/crypto" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" @@ -28,6 +30,51 @@ import ( var dumper = spew.ConfigState{Indent: " "} +func TestAccountRange(t *testing.T) { + state, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) + expectedPreimages := make(map[common.Hash]interface{}) + + // create some accounts + for i := 0; i < 256; i++ { + address := common.Address{byte(i)} + obj := state.GetOrNewStateObject(address) + expectedPreimages[crypto.Keccak256Hash(address.Bytes())] = true + obj.AddBalance(big.NewInt(22)) + obj.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) + + //root = state.Commit(false) + } + + root, err := state.Commit(false) + if err != nil { + t.Fatalf("%s", err) + } + + state.Finalise(false) + + trie, err := state.Database().OpenTrie(root) + if err != nil { + t.Fatalf("%s", err) + } + + zeroHash := crypto.Keccak256Hash(common.Hash{}.Bytes()) + result, err := accountRange(trie, &zeroHash, 256) + if err != nil { + t.Fatalf("%s", err) + } + + if len(result.Preimages) != 99 { + t.Fatalf("maximum number of results returned should be 99, returned %d", len(result.Preimages)) + } + + for _, v := range result.Preimages { + if _, ok := expectedPreimages[v]; !ok { + t.Fatalf("expected to find preimage %s in result", v.String()) + } + } + +} + func TestStorageRangeAt(t *testing.T) { // Create a state where account 0x010000... has a few storage entries. var (