trie/bintrie: fix NodeIterator Empty node handling

Fix three issues in the binary trie NodeIterator:

1. Empty nodes now properly backtrack to parent and continue iteration
   instead of terminating the entire walk early.

2. HashedNode resolver handles nil data (all-zeros hash) gracefully by
   treating it as Empty rather than panicking.

3. Parent update after node resolution guards against stack underflow
   when resolving the root node itself.
This commit is contained in:
tellabg 2026-03-19 19:46:01 +01:00
parent 35b91092c5
commit 6cc4dc5f10

View file

@ -119,10 +119,17 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
return it.Next(descend)
case HashedNode:
// resolve the node
data, err := it.trie.nodeResolver(it.Path(), common.Hash(node))
resolverPath := it.Path()
data, err := it.trie.nodeResolver(resolverPath, common.Hash(node))
if err != nil {
panic(err)
}
if data == nil {
// Empty/nil node — treat as Empty, backtrack
it.current = Empty{}
it.stack[len(it.stack)-1].Node = it.current
return it.Next(descend)
}
it.current, err = DeserializeNodeWithHash(data, len(it.stack)-1, common.Hash(node))
if err != nil {
panic(err)
@ -130,16 +137,25 @@ func (it *binaryNodeIterator) Next(descend bool) bool {
// update the stack and parent with the resolved node
it.stack[len(it.stack)-1].Node = it.current
parent := &it.stack[len(it.stack)-2]
if parent.Index == 0 {
parent.Node.(*InternalNode).left = it.current
} else {
parent.Node.(*InternalNode).right = it.current
if len(it.stack) >= 2 {
parent := &it.stack[len(it.stack)-2]
if parent.Index == 0 {
parent.Node.(*InternalNode).left = it.current
} else {
parent.Node.(*InternalNode).right = it.current
}
}
return it.Next(descend)
case Empty:
// do nothing
return false
// Empty node - go back to parent and continue
if len(it.stack) <= 1 {
it.lastErr = errIteratorEnd
return false
}
it.stack = it.stack[:len(it.stack)-1]
it.current = it.stack[len(it.stack)-1].Node
it.stack[len(it.stack)-1].Index++
return it.Next(descend)
default:
panic("invalid node type")
}