eth/tracers: avoid unsyncronized mutations on trie database (#23632)

This PR fixes an issue in traceChain, where the statedb Commit operation was performed asynchronously with dereference-operations agains the underlying trie.Database instance. Due to how the reference counting works within the trie database (where parent count is recursively updated when new parents are added), doing dereferencing in the middle of Commit can cause the refcount to become wrong, leading to an inconsistent state. 

This was fixed by doing Commit/Deref from the same routine.
This commit is contained in:
Martin Holst Swende 2021-09-28 13:06:19 +02:00 committed by GitHub
parent 92c5d104d0
commit 3531ca2246
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 19 additions and 7 deletions

@ -119,7 +119,8 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state
// Finalize the state so any modifications are written to the trie
root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(current.Number()))
if err != nil {
return nil, err
return nil, fmt.Errorf("stateAtBlock commit failed, number %d root %v: %w",
current.NumberU64(), current.Root().Hex(), err)
}
statedb, err = state.New(root, database, nil)
if err != nil {

@ -290,7 +290,11 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
}()
}
// Start a goroutine to feed all the blocks into the tracers
begin := time.Now()
var (
begin = time.Now()
derefTodo []common.Hash // list of hashes to dereference from the db
derefsMu sync.Mutex // mutex for the derefs
)
go func() {
var (
@ -324,6 +328,14 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
return
default:
}
// clean out any derefs
derefsMu.Lock()
for _, h := range derefTodo {
statedb.Database().TrieDB().Dereference(h)
}
derefTodo = derefTodo[:0]
derefsMu.Unlock()
// Print progress logs if long enough time elapsed
if time.Since(logged) > 8*time.Second {
logged = time.Now()
@ -382,12 +394,11 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
Hash: res.block.Hash(),
Traces: res.results,
}
// Schedule any parent tries held in memory by this task for dereferencing
done[uint64(result.Block)] = result
// Dereference any parent tries held in memory by this task
if res.statedb.Database().TrieDB() != nil {
res.statedb.Database().TrieDB().Dereference(res.rootref)
}
derefsMu.Lock()
derefTodo = append(derefTodo, res.rootref)
derefsMu.Unlock()
// Stream completed traces to the user, aborting on the first error
for result, ok := done[next]; ok; result, ok = done[next] {
if len(result.Traces) > 0 || next == end.NumberU64() {