Skip to content

Commit

Permalink
core: show db error-info in case of mismatched hash root (#26870)
Browse files Browse the repository at this point in the history
When a database failure occurs, bubble it up a into statedb, and report it in suitable places, such as during a 'bad block' report.
  • Loading branch information
rjl493456442 authored Mar 16, 2023
1 parent f86913b commit bba2a1b
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 44 deletions.
2 changes: 1 addition & 1 deletion core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
// Validate the state root against the received state root and throw
// an error if they don't match.
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root, root)
return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
}
return nil
}
Expand Down
47 changes: 10 additions & 37 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ func (s Storage) String() (str string) {
for key, value := range s {
str += fmt.Sprintf("%X : %X\n", key, value)
}

return
}

Expand All @@ -52,7 +51,6 @@ func (s Storage) Copy() Storage {
for key, value := range s {
cpy[key] = value
}

return cpy
}

Expand All @@ -68,13 +66,6 @@ type stateObject struct {
data types.StateAccount
db *StateDB

// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by StateDB.Commit.
dbErr error

// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
code Code // contract bytecode, which gets set when code is loaded
Expand All @@ -84,7 +75,7 @@ type stateObject struct {
dirtyStorage Storage // Storage entries that have been modified in the current transaction execution

// Cache flags.
// When an object is marked suicided it will be delete from the trie
// When an object is marked suicided it will be deleted from the trie
// during the "update" phase of the state transition.
dirtyCode bool // true if the code was updated
suicided bool
Expand Down Expand Up @@ -123,13 +114,6 @@ func (s *stateObject) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &s.data)
}

// setError remembers the first non-nil error it is called with.
func (s *stateObject) setError(err error) {
if s.dbErr == nil {
s.dbErr = err
}
}

func (s *stateObject) markSuicided() {
s.suicided = true
}
Expand Down Expand Up @@ -214,23 +198,23 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
start := time.Now()
tr, err := s.getTrie(db)
if err != nil {
s.setError(err)
s.db.setError(err)
return common.Hash{}
}
enc, err = tr.TryGet(key.Bytes())
if metrics.EnabledExpensive {
s.db.StorageReads += time.Since(start)
}
if err != nil {
s.setError(err)
s.db.setError(err)
return common.Hash{}
}
}
var value common.Hash
if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
s.setError(err)
s.db.setError(err)
}
value.SetBytes(content)
}
Expand Down Expand Up @@ -296,7 +280,7 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) {
)
tr, err := s.getTrie(db)
if err != nil {
s.setError(err)
s.db.setError(err)
return nil, err
}
// Insert all the pending updates into the trie
Expand All @@ -311,15 +295,15 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) {
var v []byte
if (value == common.Hash{}) {
if err := tr.TryDelete(key[:]); err != nil {
s.setError(err)
s.db.setError(err)
return nil, err
}
s.db.StorageDeleted += 1
} else {
// Encoding []byte cannot fail, ok to ignore the error.
v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
if err := tr.TryUpdate(key[:], v); err != nil {
s.setError(err)
s.db.setError(err)
return nil, err
}
s.db.StorageUpdated += 1
Expand Down Expand Up @@ -351,7 +335,6 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) {
func (s *stateObject) updateRoot(db Database) {
tr, err := s.updateTrie(db)
if err != nil {
s.setError(fmt.Errorf("updateRoot (%x) error: %w", s.address, err))
return
}
// If nothing changed, don't bother with hashing anything
Expand All @@ -372,9 +355,6 @@ func (s *stateObject) commitTrie(db Database) (*trie.NodeSet, error) {
if err != nil {
return nil, err
}
if s.dbErr != nil {
return nil, s.dbErr
}
// If nothing changed, don't bother with committing anything
if tr == nil {
return nil, nil
Expand All @@ -385,7 +365,7 @@ func (s *stateObject) commitTrie(db Database) (*trie.NodeSet, error) {
}
root, nodes := tr.Commit(false)
s.data.Root = root
return nodes, err
return nodes, nil
}

// AddBalance adds amount to s's balance.
Expand Down Expand Up @@ -457,7 +437,7 @@ func (s *stateObject) Code(db Database) []byte {
}
code, err := db.ContractCode(s.addrHash, common.BytesToHash(s.CodeHash()))
if err != nil {
s.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
}
s.code = code
return code
Expand All @@ -475,7 +455,7 @@ func (s *stateObject) CodeSize(db Database) int {
}
size, err := db.ContractCodeSize(s.addrHash, common.BytesToHash(s.CodeHash()))
if err != nil {
s.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
}
return size
}
Expand Down Expand Up @@ -519,10 +499,3 @@ func (s *stateObject) Balance() *big.Int {
func (s *stateObject) Nonce() uint64 {
return s.data.Nonce
}

// Value is never called, but must be present to allow stateObject to be used
// as a vm.Account interface that also satisfies the vm.ContractRef
// interface. Interfaces are awesome.
func (s *stateObject) Value() *big.Int {
panic("Value on stateObject should never be called")
}
10 changes: 6 additions & 4 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@ type StateDB struct {
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by StateDB.Commit.
// during a database read is memoized here and will eventually be
// returned by StateDB.Commit. Notably, this error is also shared
// by all cached state objects in case the database failure occurs
// when accessing state of accounts.
dbErr error

// The refund counter, also used by state transitioning.
Expand Down Expand Up @@ -187,6 +189,7 @@ func (s *StateDB) setError(err error) {
}
}

// Error returns the memorized database failure occurred earlier.
func (s *StateDB) Error() error {
return s.dbErr
}
Expand Down Expand Up @@ -478,13 +481,11 @@ func (s *StateDB) SetTransientState(addr common.Address, key, value common.Hash)
if prev == value {
return
}

s.journal.append(transientStorageChange{
account: &addr,
key: key,
prevalue: prev,
})

s.setTransientState(addr, key, value)
}

Expand Down Expand Up @@ -957,6 +958,7 @@ func (s *StateDB) clearJournalAndRefund() {

// Commit writes the state to the underlying in-memory trie database.
func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
// Short circuit in case any database failure occurred earlier.
if s.dbErr != nil {
return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
}
Expand Down
2 changes: 0 additions & 2 deletions core/vm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ type StateDB interface {

AddLog(*types.Log)
AddPreimage(common.Hash, []byte)

ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error
}

// CallContext provides a basic interface for the EVM calling conventions. The EVM
Expand Down

0 comments on commit bba2a1b

Please sign in to comment.