Repository navigation
perf(fork): add fork_prefetch to warm new fork blocks - #17673
Merged
Merged
Conversation
A fork of `latest` starts with an empty cache on each new head, so every cold account or storage read blocks the EVM for one RPC round trip. With `fork_prefetch = true` (or `--fork-prefetch`), a fork that starts on a block with an empty disk cache reads the keys of the newest older cache file of the same chain, fetches them at the new block with 32 requests in flight, and inserts them only where the cache has no value. Values come from the pinned block, so results do not change; an unused key only costs a request. The setting defaults to off and follows the disk caching settings. Measured on ethereum.reth.rs with each run on a new head, alternating modes in one binary: - sky-ecosystem/dss-allocator (108 tests, all suites fork latest), 6 pairs: 20.33 s -> 9.74 s per run (-10.59 s +- 0.22 s). EVM cold RPCs 534 storage + 57 account -> 5-77 + 4-7, with 592 keys prefetched. - Uniswap/permit2 integration, 10 pairs: 5.18 s -> 2.35 s per run (-2.83 s +- 0.09 s over the 8 pairs without an endpoint stall).
Read the predicted keys through the fork's SharedBackend on blocking workers instead of a separate fetcher. The prefetch now uses the backend's own request path, deduplication and cache writes, so it no longer copies the account fetch policy, request parameters or code hash conversion.
figtracer
marked this pull request as ready for review
October 19, 2026 20:04
figtracer
requested review from
0xrusowsky,
DaniPopes,
grandizzy,
mablr,
mattsse and
stevencartavia
as code owners
October 19, 2026 20:04
fork_prefetch to warm new fork blocks
A failed speculative read is not an error, but the backend logs one per failure, and every remaining read fails once the run ends. Run the prefetch workers without a tracing subscriber and stop each one at its first failed read.
mablr
reviewed
Oct 21, 2026
The prefetch workers hold backend clones, and the last clone flushes the disk cache. Keep the worker handles in the fork handler, and on shutdown stop them and wait after the backend handlers are dropped, so the flush finishes before the shutdown completes. Start prefetch only for forks that follow `latest`. Rolls and forks pinned to a block do not start workers.
mablr
reviewed
Oct 21, 2026
Finding and decoding the previous cache file ran inside the fork handler's poll, so it stalled every other backend's reads and the fork creation. A background coordinator now loads the keys and starts the workers. It holds its own backend clone and done sender, so shutdown still waits for it before the cache flush completes.
mablr
approved these changes
Oct 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Before: a fork of
lateststarts with an empty cache on each new head, so every cold account or storage read blocks the EVM for one RPC round trip. CI suites that forklatestpay this on every run.After: with
fork_prefetch = true, a new fork block fetches the keys that earlier runs read, in parallel and in the background, so most reads hit the cache.Change
When a fork starts on a block with an empty disk cache, it takes the keys of the newest older cache file on the same chain and reads them through the fork backend on 32 background workers. The backend fetches and caches each key like an EVM read, so results do not change, and an unused key costs one request. The
fork_prefetchsetting, the--fork-prefetchflag, andFOUNDRY_FORK_PREFETCHturn it on; it is off by default and follows the disk caching settings.Results
Each run forks a new head of
ethereum.reth.rs, and the modes alternate in one release binary.On dss-allocator, cold RPCs from the EVM drop from 534 storage and 57 account reads to 5–77 and 4–7, for 592 prefetched keys. Two permit2 pairs are left out because of slow TCP connects to the endpoint, which happened in both modes.
Downstream
This adds a
fork_prefetchconfig key (defaultfalse) toforge configoutput and a--fork-prefetchflag to the shared EVM arguments. Default behavior does not change. The book update is foundry-rs/book#2179.