core/state, trie: switch iterator panics to error fields

This commit is contained in:
Péter Szilágyi 2016-02-16 12:37:00 +02:00
parent 151c7bef41
commit b8d59d9c98
4 changed files with 62 additions and 45 deletions

View File

@ -41,6 +41,8 @@ type NodeIterator struct {
Hash common.Hash // Hash of the current entry being iterated (nil if not standalone) Hash common.Hash // Hash of the current entry being iterated (nil if not standalone)
Entry interface{} // Current state entry being iterated (internal representation) Entry interface{} // Current state entry being iterated (internal representation)
Parent common.Hash // Hash of the first full ancestor node (nil if current is the root) Parent common.Hash // Hash of the first full ancestor node (nil if current is the root)
Error error // Failure set in case of an internal error in the iterator
} }
// NewNodeIterator creates an post-order state node iterator. // NewNodeIterator creates an post-order state node iterator.
@ -51,17 +53,26 @@ func NewNodeIterator(state *StateDB) *NodeIterator {
} }
// Next moves the iterator to the next node, returning whether there are any // Next moves the iterator to the next node, returning whether there are any
// further nodes. // further nodes. In case of an internal error this method returns false and
// sets the Error field to the encountered failure.
func (it *NodeIterator) Next() bool { func (it *NodeIterator) Next() bool {
it.step() // If the iterator failed previously, don't do anything
if it.Error != nil {
return false
}
// Otherwise step forward with the iterator and report any errors
if err := it.step(); err != nil {
it.Error = err
return false
}
return it.retrieve() return it.retrieve()
} }
// step moves the iterator to the next entry of the state trie. // step moves the iterator to the next entry of the state trie.
func (it *NodeIterator) step() { func (it *NodeIterator) step() error {
// Abort if we reached the end of the iteration // Abort if we reached the end of the iteration
if it.state == nil { if it.state == nil {
return return nil
} }
// Initialize the iterator if we've just started // Initialize the iterator if we've just started
if it.stateIt == nil { if it.stateIt == nil {
@ -70,23 +81,29 @@ func (it *NodeIterator) step() {
// If we had data nodes previously, we surely have at least state nodes // If we had data nodes previously, we surely have at least state nodes
if it.dataIt != nil { if it.dataIt != nil {
if cont := it.dataIt.Next(); !cont { if cont := it.dataIt.Next(); !cont {
if it.dataIt.Error != nil {
return it.dataIt.Error
}
it.dataIt = nil it.dataIt = nil
} }
return return nil
} }
// If we had source code previously, discard that // If we had source code previously, discard that
if it.code != nil { if it.code != nil {
it.code = nil it.code = nil
return return nil
} }
// Step to the next state trie node, terminating if we're out of nodes // Step to the next state trie node, terminating if we're out of nodes
if cont := it.stateIt.Next(); !cont { if cont := it.stateIt.Next(); !cont {
if it.stateIt.Error != nil {
return it.stateIt.Error
}
it.state, it.stateIt = nil, nil it.state, it.stateIt = nil, nil
return return nil
} }
// If the state trie node is an internal entry, leave as is // If the state trie node is an internal entry, leave as is
if !it.stateIt.Leaf { if !it.stateIt.Leaf {
return return nil
} }
// Otherwise we've reached an account node, initiate data iteration // Otherwise we've reached an account node, initiate data iteration
var account struct { var account struct {
@ -95,13 +112,12 @@ func (it *NodeIterator) step() {
Root common.Hash Root common.Hash
CodeHash []byte CodeHash []byte
} }
err := rlp.Decode(bytes.NewReader(it.stateIt.LeafBlob), &account) if err := rlp.Decode(bytes.NewReader(it.stateIt.LeafBlob), &account); err != nil {
if err != nil { return err
panic(err)
} }
dataTrie, err := trie.New(account.Root, it.state.db) dataTrie, err := trie.New(account.Root, it.state.db)
if err != nil { if err != nil {
panic(err) return err
} }
it.dataIt = trie.NewNodeIterator(dataTrie) it.dataIt = trie.NewNodeIterator(dataTrie)
if !it.dataIt.Next() { if !it.dataIt.Next() {
@ -111,10 +127,11 @@ func (it *NodeIterator) step() {
it.codeHash = common.BytesToHash(account.CodeHash) it.codeHash = common.BytesToHash(account.CodeHash)
it.code, err = it.state.db.Get(account.CodeHash) it.code, err = it.state.db.Get(account.CodeHash)
if err != nil { if err != nil {
panic(fmt.Sprintf("code %x: %v", account.CodeHash, err)) return fmt.Errorf("code %x: %v", account.CodeHash, err)
} }
} }
it.accountHash = it.stateIt.Parent it.accountHash = it.stateIt.Parent
return nil
} }
// retrieve pulls and caches the current state entry the iterator is traversing. // retrieve pulls and caches the current state entry the iterator is traversing.

View File

@ -18,7 +18,6 @@ package state
import ( import (
"bytes" "bytes"
"fmt"
"math/big" "math/big"
"testing" "testing"
@ -97,28 +96,23 @@ func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accou
} }
} }
// checkStateConsistency checks that all nodes in a state trie and indeed present. // checkStateConsistency checks that all nodes in a state trie are indeed present.
func checkStateConsistency(db ethdb.Database, root common.Hash) (failure error) { func checkStateConsistency(db ethdb.Database, root common.Hash) error {
// Capture any panics by the iterator
defer func() {
if r := recover(); r != nil {
failure = fmt.Errorf("%v", r)
}
}()
// Remove any potentially cached data from the test state creation or previous checks // Remove any potentially cached data from the test state creation or previous checks
trie.ClearGlobalCache() trie.ClearGlobalCache()
// Create and iterate a state trie rooted in a sub-node // Create and iterate a state trie rooted in a sub-node
if _, err := db.Get(root.Bytes()); err != nil { if _, err := db.Get(root.Bytes()); err != nil {
return return nil // Consider a non existent state consistent
} }
state, err := New(root, db) state, err := New(root, db)
if err != nil { if err != nil {
return return err
} }
for it := NewNodeIterator(state); it.Next(); { it := NewNodeIterator(state)
for it.Next() {
} }
return nil return it.Error
} }
// Tests that an empty state is not scheduled for syncing. // Tests that an empty state is not scheduled for syncing.

View File

@ -169,6 +169,8 @@ type NodeIterator struct {
Parent common.Hash // Hash of the first full ancestor node (nil if current is the root) Parent common.Hash // Hash of the first full ancestor node (nil if current is the root)
Leaf bool // Flag whether the current node is a value (data) node Leaf bool // Flag whether the current node is a value (data) node
LeafBlob []byte // Data blob contained within a leaf (otherwise nil) LeafBlob []byte // Data blob contained within a leaf (otherwise nil)
Error error // Failure set in case of an internal error in the iterator
} }
// NewNodeIterator creates an post-order trie iterator. // NewNodeIterator creates an post-order trie iterator.
@ -180,29 +182,38 @@ func NewNodeIterator(trie *Trie) *NodeIterator {
} }
// Next moves the iterator to the next node, returning whether there are any // Next moves the iterator to the next node, returning whether there are any
// further nodes. // further nodes. In case of an internal error this method returns false and
// sets the Error field to the encountered failure.
func (it *NodeIterator) Next() bool { func (it *NodeIterator) Next() bool {
it.step() // If the iterator failed previously, don't do anything
if it.Error != nil {
return false
}
// Otherwise step forward with the iterator and report any errors
if err := it.step(); err != nil {
it.Error = err
return false
}
return it.retrieve() return it.retrieve()
} }
// step moves the iterator to the next node of the trie. // step moves the iterator to the next node of the trie.
func (it *NodeIterator) step() { func (it *NodeIterator) step() error {
// Abort if we reached the end of the iteration // Abort if we reached the end of the iteration
if it.trie == nil { if it.trie == nil {
return return nil
} }
// Initialize the iterator if we've just started, or pop off the old node otherwise // Initialize the iterator if we've just started, or pop off the old node otherwise
if len(it.stack) == 0 { if len(it.stack) == 0 {
it.stack = append(it.stack, &nodeIteratorState{node: it.trie.root, child: -1}) it.stack = append(it.stack, &nodeIteratorState{node: it.trie.root, child: -1})
if it.stack[0].node == nil { if it.stack[0].node == nil {
panic(fmt.Sprintf("root node missing: %x", it.trie.Root())) return fmt.Errorf("root node missing: %x", it.trie.Root())
} }
} else { } else {
it.stack = it.stack[:len(it.stack)-1] it.stack = it.stack[:len(it.stack)-1]
if len(it.stack) == 0 { if len(it.stack) == 0 {
it.trie = nil it.trie = nil
return return nil
} }
} }
// Continue iteration to the next child // Continue iteration to the next child
@ -239,13 +250,14 @@ func (it *NodeIterator) step() {
node, err := it.trie.resolveHash(hash, nil, nil) node, err := it.trie.resolveHash(hash, nil, nil)
if err != nil { if err != nil {
panic(err) return err
} }
it.stack = append(it.stack, &nodeIteratorState{hash: common.BytesToHash(hash), node: node, parent: ancestor, child: -1}) it.stack = append(it.stack, &nodeIteratorState{hash: common.BytesToHash(hash), node: node, parent: ancestor, child: -1})
} else { } else {
break break
} }
} }
return nil
} }
// retrieve pulls and caches the current trie node the iterator is traversing. // retrieve pulls and caches the current trie node the iterator is traversing.

View File

@ -18,7 +18,6 @@ package trie
import ( import (
"bytes" "bytes"
"fmt"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -80,25 +79,20 @@ func checkTrieContents(t *testing.T, db Database, root []byte, content map[strin
} }
} }
// checkTrieConsistency checks that all nodes in a trie and indeed present. // checkTrieConsistency checks that all nodes in a trie are indeed present.
func checkTrieConsistency(db Database, root common.Hash) (failure error) { func checkTrieConsistency(db Database, root common.Hash) error {
// Capture any panics by the iterator
defer func() {
if r := recover(); r != nil {
failure = fmt.Errorf("%v", r)
}
}()
// Remove any potentially cached data from the test trie creation or previous checks // Remove any potentially cached data from the test trie creation or previous checks
globalCache.Clear() globalCache.Clear()
// Create and iterate a trie rooted in a subnode // Create and iterate a trie rooted in a subnode
trie, err := New(root, db) trie, err := New(root, db)
if err != nil { if err != nil {
return return nil // // Consider a non existent state consistent
} }
for it := NewNodeIterator(trie); it.Next(); { it := NewNodeIterator(trie)
for it.Next() {
} }
return nil return it.Error
} }
// Tests that an empty trie is not scheduled for syncing. // Tests that an empty trie is not scheduled for syncing.