core/state: return error when storage trie can't be opened (#26350)

This changes the StorageTrie method to return an error when the trie
is not available. It used to return an 'empty trie' in this case, but that's
not possible anymore under PBSS.
This commit is contained in:
rjl493456442 2022-12-21 17:21:21 +08:00 committed by GitHub
parent b818e73ef3
commit 01808421e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 41 deletions

View File

@ -168,7 +168,12 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
} }
if !conf.SkipStorage { if !conf.SkipStorage {
account.Storage = make(map[common.Hash]string) account.Storage = make(map[common.Hash]string)
storageIt := trie.NewIterator(obj.getTrie(s.db).NodeIterator(nil)) tr, err := obj.getTrie(s.db)
if err != nil {
log.Error("Failed to load storage trie", "err", err)
continue
}
storageIt := trie.NewIterator(tr.NodeIterator(nil))
for storageIt.Next() { for storageIt.Next() {
_, content, _, err := rlp.Split(storageIt.Value) _, content, _, err := rlp.Split(storageIt.Value)
if err != nil { if err != nil {

View File

@ -148,7 +148,10 @@ func (s *stateObject) touch() {
} }
} }
func (s *stateObject) getTrie(db Database) Trie { // getTrie returns the associated storage trie. The trie will be opened
// if it's not loaded previously. An error will be returned if trie can't
// be loaded.
func (s *stateObject) getTrie(db Database) (Trie, error) {
if s.trie == nil { if s.trie == nil {
// Try fetching from prefetcher first // Try fetching from prefetcher first
// We don't prefetch empty tries // We don't prefetch empty tries
@ -158,15 +161,14 @@ func (s *stateObject) getTrie(db Database) Trie {
s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root) s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root)
} }
if s.trie == nil { if s.trie == nil {
var err error tr, err := db.OpenStorageTrie(s.db.originalRoot, s.addrHash, s.data.Root)
s.trie, err = db.OpenStorageTrie(s.db.originalRoot, s.addrHash, s.data.Root)
if err != nil { if err != nil {
s.trie, _ = db.OpenStorageTrie(s.db.originalRoot, s.addrHash, common.Hash{}) return nil, err
s.setError(fmt.Errorf("can't create storage trie: %v", err))
} }
s.trie = tr
} }
} }
return s.trie return s.trie, nil
} }
// GetState retrieves a value from the account storage trie. // GetState retrieves a value from the account storage trie.
@ -221,7 +223,12 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
// If the snapshot is unavailable or reading from it fails, load from the database. // If the snapshot is unavailable or reading from it fails, load from the database.
if s.db.snap == nil || err != nil { if s.db.snap == nil || err != nil {
start := time.Now() start := time.Now()
enc, err = s.getTrie(db).TryGet(key.Bytes()) tr, err := s.getTrie(db)
if err != nil {
s.setError(err)
return common.Hash{}
}
enc, err = tr.TryGet(key.Bytes())
if metrics.EnabledExpensive { if metrics.EnabledExpensive {
s.db.StorageReads += time.Since(start) s.db.StorageReads += time.Since(start)
} }
@ -304,23 +311,29 @@ func (s *stateObject) finalise(prefetch bool) {
} }
// updateTrie writes cached storage modifications into the object's storage trie. // updateTrie writes cached storage modifications into the object's storage trie.
// It will return nil if the trie has not been loaded and no changes have been made // It will return nil if the trie has not been loaded and no changes have been
func (s *stateObject) updateTrie(db Database) Trie { // made. An error will be returned if the trie can't be loaded/updated correctly.
func (s *stateObject) updateTrie(db Database) (Trie, error) {
// Make sure all dirty slots are finalized into the pending storage area // Make sure all dirty slots are finalized into the pending storage area
s.finalise(false) // Don't prefetch anymore, pull directly if need be s.finalise(false) // Don't prefetch anymore, pull directly if need be
if len(s.pendingStorage) == 0 { if len(s.pendingStorage) == 0 {
return s.trie return s.trie, nil
} }
// Track the amount of time wasted on updating the storage trie // Track the amount of time wasted on updating the storage trie
if metrics.EnabledExpensive { if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now()) defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
} }
// The snapshot storage map for the object // The snapshot storage map for the object
var storage map[common.Hash][]byte var (
storage map[common.Hash][]byte
hasher = s.db.hasher
)
tr, err := s.getTrie(db)
if err != nil {
s.setError(err)
return nil, err
}
// Insert all the pending updates into the trie // Insert all the pending updates into the trie
tr := s.getTrie(db)
hasher := s.db.hasher
usedStorage := make([][]byte, 0, len(s.pendingStorage)) usedStorage := make([][]byte, 0, len(s.pendingStorage))
for key, value := range s.pendingStorage { for key, value := range s.pendingStorage {
// Skip noop changes, persist actual changes // Skip noop changes, persist actual changes
@ -331,12 +344,18 @@ func (s *stateObject) updateTrie(db Database) Trie {
var v []byte var v []byte
if (value == common.Hash{}) { if (value == common.Hash{}) {
s.setError(tr.TryDelete(key[:])) if err := tr.TryDelete(key[:]); err != nil {
s.setError(err)
return nil, err
}
s.db.StorageDeleted += 1 s.db.StorageDeleted += 1
} else { } else {
// Encoding []byte cannot fail, ok to ignore the error. // Encoding []byte cannot fail, ok to ignore the error.
v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:])) v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
s.setError(tr.TryUpdate(key[:], v)) if err := tr.TryUpdate(key[:], v); err != nil {
s.setError(err)
return nil, err
}
s.db.StorageUpdated += 1 s.db.StorageUpdated += 1
} }
// If state snapshotting is active, cache the data til commit // If state snapshotting is active, cache the data til commit
@ -358,37 +377,47 @@ func (s *stateObject) updateTrie(db Database) Trie {
if len(s.pendingStorage) > 0 { if len(s.pendingStorage) > 0 {
s.pendingStorage = make(Storage) s.pendingStorage = make(Storage)
} }
return tr return tr, nil
} }
// UpdateRoot sets the trie root to the current root hash of // UpdateRoot sets the trie root to the current root hash of. An error
// will be returned if trie root hash is not computed correctly.
func (s *stateObject) updateRoot(db Database) { 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 // If nothing changed, don't bother with hashing anything
if s.updateTrie(db) == nil { if tr == nil {
return return
} }
// Track the amount of time wasted on hashing the storage trie // Track the amount of time wasted on hashing the storage trie
if metrics.EnabledExpensive { if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now()) defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now())
} }
s.data.Root = s.trie.Hash() s.data.Root = tr.Hash()
} }
// commitTrie submits the storage changes into the storage trie and re-computes // commitTrie submits the storage changes into the storage trie and re-computes
// the root. Besides, all trie changes will be collected in a nodeset and returned. // the root. Besides, all trie changes will be collected in a nodeset and returned.
func (s *stateObject) commitTrie(db Database) (*trie.NodeSet, error) { func (s *stateObject) commitTrie(db Database) (*trie.NodeSet, error) {
// If nothing changed, don't bother with hashing anything tr, err := s.updateTrie(db)
if s.updateTrie(db) == nil { if err != nil {
return nil, nil return nil, err
} }
if s.dbErr != nil { if s.dbErr != nil {
return nil, s.dbErr return nil, s.dbErr
} }
// If nothing changed, don't bother with committing anything
if tr == nil {
return nil, nil
}
// Track the amount of time wasted on committing the storage trie // Track the amount of time wasted on committing the storage trie
if metrics.EnabledExpensive { if metrics.EnabledExpensive {
defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now()) defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now())
} }
root, nodes, err := s.trie.Commit(false) root, nodes, err := tr.Commit(false)
if err == nil { if err == nil {
s.data.Root = root s.data.Root = root
} }

View File

@ -339,13 +339,19 @@ func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) {
// GetStorageProof returns the Merkle proof for given storage slot. // GetStorageProof returns the Merkle proof for given storage slot.
func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) { func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) {
var proof proofList trie, err := s.StorageTrie(a)
trie := s.StorageTrie(a) if err != nil {
if trie == nil { return nil, err
return proof, errors.New("storage trie for requested address does not exist")
} }
err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) if trie == nil {
return proof, err return nil, errors.New("storage trie for requested address does not exist")
}
var proof proofList
err = trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof)
if err != nil {
return nil, err
}
return proof, nil
} }
// GetCommittedState retrieves a value from the given account's committed storage trie. // GetCommittedState retrieves a value from the given account's committed storage trie.
@ -362,15 +368,18 @@ func (s *StateDB) Database() Database {
return s.db return s.db
} }
// StorageTrie returns the storage trie of an account. // StorageTrie returns the storage trie of an account. The return value is a copy
// The return value is a copy and is nil for non-existent accounts. // and is nil for non-existent accounts. An error will be returned if storage trie
func (s *StateDB) StorageTrie(addr common.Address) Trie { // is existent but can't be loaded correctly.
func (s *StateDB) StorageTrie(addr common.Address) (Trie, error) {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject == nil { if stateObject == nil {
return nil return nil, nil
} }
cpy := stateObject.deepCopy(s) cpy := stateObject.deepCopy(s)
cpy.updateTrie(s.db) if _, err := cpy.updateTrie(s.db); err != nil {
return nil, err
}
return cpy.getTrie(s.db) return cpy.getTrie(s.db)
} }
@ -654,7 +663,11 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common
if so == nil { if so == nil {
return nil return nil
} }
it := trie.NewIterator(so.getTrie(db.db).NodeIterator(nil)) tr, err := so.getTrie(db.db)
if err != nil {
return err
}
it := trie.NewIterator(tr.NodeIterator(nil))
for it.Next() { for it.Next() {
key := common.BytesToHash(db.trie.GetKey(it.Key)) key := common.BytesToHash(db.trie.GetKey(it.Key))

View File

@ -417,7 +417,10 @@ func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash,
} }
defer release() defer release()
st := statedb.StorageTrie(contractAddress) st, err := statedb.StorageTrie(contractAddress)
if err != nil {
return StorageRangeResult{}, err
}
if st == nil { if st == nil {
return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress) return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress)
} }

View File

@ -209,7 +209,11 @@ func TestStorageRangeAt(t *testing.T) {
}, },
} }
for _, test := range tests { for _, test := range tests {
result, err := storageRangeAt(state.StorageTrie(addr), test.start, test.limit) tr, err := state.StorageTrie(addr)
if err != nil {
t.Error(err)
}
result, err := storageRangeAt(tr, test.start, test.limit)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }

View File

@ -660,8 +660,10 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st
if state == nil || err != nil { if state == nil || err != nil {
return nil, err return nil, err
} }
storageTrie, err := state.StorageTrie(address)
storageTrie := state.StorageTrie(address) if err != nil {
return nil, err
}
storageHash := types.EmptyRootHash storageHash := types.EmptyRootHash
codeHash := state.GetCodeHash(address) codeHash := state.GetCodeHash(address)
storageProof := make([]StorageResult, len(storageKeys)) storageProof := make([]StorageResult, len(storageKeys))