Skip to content

Commit 85663ea

Browse files
committed
eth: return iterator errors from debug_storageRangeAt
1 parent 26d0b21 commit 85663ea

2 files changed

Lines changed: 75 additions & 0 deletions

File tree

eth/api_debug.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,13 @@ func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Add
312312
next := common.BytesToHash(it.Key)
313313
result.NextKey = &next
314314
}
315+
// Iterator.Next returns false on both exhaustion and error, so a failure to
316+
// resolve a trie node mid-range would otherwise be reported as a complete
317+
// result (a nil NextKey claims all keys were returned). Surface the error
318+
// instead of silently truncating.
319+
if it.Err != nil {
320+
return StorageRangeResult{}, it.Err
321+
}
315322
return result, nil
316323
}
317324

eth/api_debug_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"github.com/ethereum/go-ethereum/crypto"
4343
"github.com/ethereum/go-ethereum/params"
4444
"github.com/ethereum/go-ethereum/rpc"
45+
"github.com/ethereum/go-ethereum/trie"
4546
"github.com/ethereum/go-ethereum/triedb"
4647
"github.com/holiman/uint256"
4748
"github.com/stretchr/testify/assert"
@@ -278,6 +279,73 @@ func TestStorageRangeAt(t *testing.T) {
278279
}
279280
}
280281

282+
// TestStorageRangeAtMissingNode ensures that storageRangeAt returns an error,
283+
// rather than a silently truncated result, when a storage trie node cannot be
284+
// resolved mid-range. The iterator reports the end of a range and an I/O
285+
// failure the same way (Next returns false), so without checking the iterator
286+
// error a partial result would be returned with a nil NextKey, falsely claiming
287+
// that every key was included.
288+
func TestStorageRangeAtMissingNode(t *testing.T) {
289+
t.Parallel()
290+
291+
// Build a state where an account has enough storage entries that its storage
292+
// trie contains internal nodes (so there is a non-root node to remove).
293+
var (
294+
mdb = rawdb.NewMemoryDatabase()
295+
tdb = triedb.NewDatabase(mdb, &triedb.Config{Preimages: true})
296+
db = state.NewDatabase(tdb, nil)
297+
sdb, _ = state.New(types.EmptyRootHash, db)
298+
addr = common.Address{0x01}
299+
)
300+
for i := 0; i < 64; i++ {
301+
sdb.SetState(addr, common.BytesToHash([]byte{byte(i)}), common.BytesToHash([]byte{byte(i + 1)}))
302+
}
303+
root, _ := sdb.Commit(params.Rules{}, 0)
304+
305+
// Flush the committed nodes to the underlying disk so a freshly opened trie
306+
// database is forced to resolve them from there.
307+
if err := tdb.Commit(root, false); err != nil {
308+
t.Fatal(err)
309+
}
310+
311+
// Locate a non-root node of the account's storage trie.
312+
sdb, _ = state.New(root, db)
313+
storageRoot := sdb.GetStorageRoot(addr)
314+
id := trie.StorageTrieID(root, crypto.Keccak256Hash(addr.Bytes()), storageRoot)
315+
str, err := trie.NewStateTrie(id, tdb)
316+
if err != nil {
317+
t.Fatal(err)
318+
}
319+
nodeIt, err := str.NodeIterator(nil)
320+
if err != nil {
321+
t.Fatal(err)
322+
}
323+
var victim common.Hash
324+
for nodeIt.Next(true) {
325+
if h := nodeIt.Hash(); h != (common.Hash{}) && h != storageRoot {
326+
victim = h
327+
break
328+
}
329+
}
330+
if victim == (common.Hash{}) {
331+
t.Fatal("could not find a non-root storage trie node to remove")
332+
}
333+
334+
// Remove the node from disk, simulating a missing/corrupt trie node, then
335+
// re-open the state on a fresh trie database so the deletion is not masked by
336+
// an in-memory cache.
337+
rawdb.DeleteTrieNode(mdb, common.Hash{}, nil, victim, tdb.Scheme())
338+
339+
tdb2 := triedb.NewDatabase(mdb, &triedb.Config{Preimages: true})
340+
sdb2, err := state.New(root, state.NewDatabase(tdb2, nil))
341+
if err != nil {
342+
t.Fatal(err)
343+
}
344+
if _, err := storageRangeAt(sdb2, root, addr, nil, 1000); err == nil {
345+
t.Fatal("expected an error when a storage trie node is missing, got nil")
346+
}
347+
}
348+
281349
func TestGetModifiedAccounts(t *testing.T) {
282350
t.Parallel()
283351

0 commit comments

Comments
 (0)