opt(sync): Bounded look-ahead depth for the PFN/VFN fast-sync (recovery) execute pipeline#762
Open
AshinGau wants to merge 1 commit into
Open
opt(sync): Bounded look-ahead depth for the PFN/VFN fast-sync (recovery) execute pipeline#762AshinGau wants to merge 1 commit into
AshinGau wants to merge 1 commit into
Conversation
…ry) execute pipeline
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.
Summary
Speeds up PFN/VFN fast-sync (catch-up) block application by removing the lockstep between
consensus and execution in the recovery path, and parallelizing block-retrieval verification on the
fetch path. The new look-ahead behavior is opt-in via a config field and defaults to the original
strictly-serial behavior, so existing nodes are unchanged unless tuned.
On the internal test box (8 cores, gap = 8192, from block 0, 10K→150K window) sustained catch-up
throughput improved from ~496 blk/s (serial) to ~630 blk/s at lookahead=4 (the knee), up to
~663 blk/s at lookahead=64, with 0 state-root mismatches across the sweep.
Motivation
During fast-forward sync the recovery apply path fed execution strictly one block at a time:
Consensus and execution never overlapped (
reth_blockchain_tree_in_mem_state_num_blocks ≈ 0, theexecutor sat idle waiting for the next feed). This capped catch-up throughput well below execution
capacity, while the live validator path was already pipelined.
What's in this PR
Bounded look-ahead pipeline for the recovery apply path (
block_storage/block_store.rs).The recovery branch now feeds up to
Kblocks to execution before draining results, soconsensus→execution runs continuously instead of lockstep-per-block.
set_commit_blocks,per-block persist ack, and the per-block state-root comparison are preserved; only the feed is
pipelined.
recover_blockscommits up to the highest committable QC in a singlesend_for_executioncall so the pipeline can look ahead across the whole path.send_for_executionwas also refactored from one ~240-line function into a thin dispatcher plusfocused helpers (
commit_recovery_path/pipeline_recovery_blocks/drain_recovery_block/commit_live_path).Parallelize block-retrieval signature verification
(
consensus-types/src/block_retrieval.rs).BlockRetrievalResponse::verifysplit the cheapsequential chain-linkage check from the per-block BLS aggregate verify (~1.8 ms/block) and runs
the latter with
rayon. A single fast-sync response can carry up to 1000 blocks, so the serialverify was the dominant per-chunk cost on the requester.
Pipeline fast-sync chunk verify off the fetch loop (
block_storage/sync_manager.rs,network.rs).request_blockgains averify: boolflag;retrieve_block_for_idfetches eachchunk unverified and verifies it on a blocking thread via a bounded (depth-4) pipeline, so chunk
N's verify overlaps chunk N+1's fetch. Every chunk is still verified before its blocks are
returned/used; a failed verify bails before use. The zero-start-id (epoch-anchored first chunk)
guard is unchanged.
New config knob
consensus.fast_sync_execute_lookahead(see Configuration). Replaces theprior
FAST_SYNC_EXECUTE_LOOKAHEADenvironment variable. Threaded fromConsensusConfigthroughepoch_managerintoBlockStore.Dependency bumps
gaptose9544c8 → b9e5c60— adds thefast_sync_execute_lookaheadfield toConsensusConfig(gravity-aptos change; config-crate-only,
api-typesunchanged).greth(gravity-reth)1aec7b75 → 6ea2ba32.Configuration
The look-ahead depth is a node-local consensus setting, a sibling of
max_blocks_per_receiving_request.Set it in the node's config YAML (e.g.
public_full_node.yaml) underconsensus::usize, default1. The field is#[serde(default)], so omitting it keepsthe default — existing configs need no change.
1= original strictly-serial recovery (byte-for-byte the previous behavior).4–8recommended for PFN/VFN catch-up nodes (4is the throughput knee; higher addsdiminishing returns and more in-memory pipeline depth / memory use — avoid very large values on
memory-constrained hosts).
Correctness
The look-ahead pipelines the feed, not the commit. Safety is preserved because:
persists happen strictly in block order.
expected hash from the ledger DB (
drain_recovery_block). Any execution divergence surfaces hereas a graceful error (
ensure!) instead of silently committing a bad root.in-memory state chain) — the same mechanism the live validator path already relies on; feeding
ahead does not change execution inputs.
lookahead = 1is the original behavior (feed→drain→feed→drain), and the recovery-vs-livedispatch keeps the live path identical.
recover_blockshighest-QC jump is end-state equivalent:commit_callbackprunes up to thelast block and raises
highest_commit_certmonotonically, and all committable QCs are in the sameepoch as the latest ledger info, so one call reaches the same end state as the old per-QC sequence.
Benchmarks
K-sweep, from block 0, 10K→150K window,
max-persist-gap=8192, internal test box (8 cores):fast_sync_execute_lookaheadin_memdepthBeyond the knee, throughput is bounded by per-block execution (merklize / state-root), not the
consensus→execution handoff. Validated at small state (0–~200K); high-state validation is follow-up.
Testing
BlockStoreconstructor argument(
round_manager_test,round_manager_fuzzing,test_utils), all passing1(serial).reth_blockchain_tree_canonical_chain_heightover time atlookahead= 1/4/16/64.Notes / follow-ups
expected vs computed). A mismatch is a deterministic corruption signal that a restart will
re-hit at the same block; consider making it fatal so the node fails fast rather than continuing
with in-flight, uncommitted blocks left in the buffer. (In-flight uncommitted blocks are
in-memory only and are safely discarded on restart.)