-
Notifications
You must be signed in to change notification settings - Fork 21.5k
eth: make traceChain avoid OOM on long-running tracing #23736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,6 +53,13 @@ const ( | |
| // and reexecute to produce missing historical state necessary to run a specific | ||
| // trace. | ||
| defaultTraceReexec = uint64(128) | ||
|
|
||
| // defaultTracechainMemLimit is the size of the triedb, at which traceChain | ||
| // switches over and tries to use a disk-backed database instead of building | ||
| // on top of memory. | ||
| // For non-archive nodes, this limit _will_ be overblown, as disk-backed tries | ||
| // will only be found every ~15K blocks or so. | ||
| defaultTracechainMemLimit = common.StorageSize(500 * 1024 * 1024) | ||
| ) | ||
|
|
||
| // Backend interface provides the common API services (that are provided by | ||
|
|
@@ -67,7 +74,10 @@ type Backend interface { | |
| ChainConfig() *params.ChainConfig | ||
| Engine() consensus.Engine | ||
| ChainDb() ethdb.Database | ||
| StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) | ||
| // StateAtBlock returns the state corresponding to the stateroot of the block. | ||
| // N.B: For executing transactions on block N, the required stateRoot is block N-1, | ||
| // so this method should be called with the parent. | ||
| StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error) | ||
| StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) | ||
| } | ||
|
|
||
|
|
@@ -320,6 +330,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config | |
| } | ||
| close(results) | ||
| }() | ||
| var preferDisk bool | ||
| // Feed all the blocks both into the tracer, as well as fast process concurrently | ||
| for number = start.NumberU64(); number < end.NumberU64(); number++ { | ||
| // Stop tracing if interruption was requested | ||
|
|
@@ -349,18 +360,24 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config | |
| } | ||
| // Prepare the statedb for tracing. Don't use the live database for | ||
| // tracing to avoid persisting state junks into the database. | ||
| statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false) | ||
| statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false, preferDisk) | ||
| if err != nil { | ||
| failed = err | ||
| break | ||
| } | ||
| if statedb.Database().TrieDB() != nil { | ||
| if trieDb := statedb.Database().TrieDB(); trieDb != nil { | ||
| // Hold the reference for tracer, will be released at the final stage | ||
| statedb.Database().TrieDB().Reference(block.Root(), common.Hash{}) | ||
| trieDb.Reference(block.Root(), common.Hash{}) | ||
|
|
||
| // Release the parent state because it's already held by the tracer | ||
| if parent != (common.Hash{}) { | ||
| statedb.Database().TrieDB().Dereference(parent) | ||
| trieDb.Dereference(parent) | ||
| } | ||
| // Prefer disk if the trie db memory grows too much | ||
| s1, s2 := trieDb.Size() | ||
| if !preferDisk && (s1+s2) > defaultTracechainMemLimit { | ||
| log.Info("Switching to prefer-disk mode for tracing", "size", s1+s2) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the accumulated in memory nodes exceed the limit, shouldn't we flush them into disk and then retrieve a disk-based statedb?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's basically what it does. We don't want to push junk, but we start telling the backend "hey, if you happen to find the state on disk, can you please use that instead of using this accumulated junk?"
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha. It can be helpful in the current codebase since we periodically push the state. |
||
| preferDisk = true | ||
| } | ||
| } | ||
| parent = block.Root() | ||
|
|
@@ -496,7 +513,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config | |
| if config != nil && config.Reexec != nil { | ||
| reexec = *config.Reexec | ||
| } | ||
| statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true) | ||
| statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
@@ -557,7 +574,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac | |
| if config != nil && config.Reexec != nil { | ||
| reexec = *config.Reexec | ||
| } | ||
| statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true) | ||
| statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
@@ -646,7 +663,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block | |
| if config != nil && config.Reexec != nil { | ||
| reexec = *config.Reexec | ||
| } | ||
| statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true) | ||
| statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
@@ -810,7 +827,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc | |
| if config != nil && config.Reexec != nil { | ||
| reexec = *config.Reexec | ||
| } | ||
| statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true) | ||
| statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.