Repository navigation
perf(beacon): precompute the epoch transition off the import path - #652
Draft
MegaRedHand wants to merge 2 commits into
Draft
MegaRedHand wants to merge 2 commits into
MegaRedHand wants to merge 2 commits into
Conversation
The first block of every epoch ran process_epoch inline on the chain actor,
then paid the post-epoch rehash for its state-root check. The transition
depends only on the parent, which is known before the block arrives.
A blocking worker now clones the head state, advances it to the first slot of
the next epoch, flushes and hashes it, and the actor stores it under
CheckpointState { epoch, root }, the key fork_choice::checkpoint_state already
derives for that checkpoint. Triggers: an import that makes a last-slot block
head, and three quarters into a last slot (covers a skipped last slot). Both
are skipped while syncing; at most one worker runs.
fork_choice::on_block resumes from the cached state for an epoch-crossing
block and applies the block with the new stf::apply_block. A miss, including
a block that races the worker, takes the previous path unchanged.
Adds lean_beacon_epoch_precompute_{lookups_total,seconds,started_total}.
MegaRedHand
added a commit
that referenced
this pull request
Oct 2, 2026
…636-638-gloas-live
Conflicted files:
- crates/blockchain/src/lib.rs
- crates/blockchain/state_transition/src/beacon/fork_choice.rs
- crates/blockchain/state_transition/src/metrics.rs
- docs/metrics.md
Gloas: the precompute stays enabled, because the base state cannot differ.
In this branch a gloas block's post-state never holds its own payload: the
child applies it (process_parent_execution_payload) inside process_block,
after process_slots, and on_execution_payload_envelope only records and
checks, it stores no second state. The store keeps one state per root and
both on_block (get_state(parent_root)) and checkpoint_state read it, and the
worker clones get_state(head_root) and caches the result under
CheckpointState { E+1, head_root }, so the entry is process_slots of the very
state an import of a child of that root clones. The fulu-to-gloas boundary
needs no case either: the upgrade runs inside process_slots on both paths.
A new unit test (a_gloas_precompute_starts_from_the_state_an_import_would)
checks, for a gloas parent, that the worker's result equals
checkpoint_state's for the key, that an import finds it, and that advancing
the rest of a skipped first slot from it equals plain process_slots.
Other adaptations:
- transition_block and stf::apply_block take the ActiveBalanceCache that
#648 added to state_transition; on_block passes the store's own cache.
- on_block's gloas-specific code between the parent read and the
transition does not use the cloned state, so removing that early clone
(as #652 does) needed nothing else.
- lib.rs: the NewBlock handler keeps the envelope settling that follows the
import and triggers the precompute after it, so a head moved by a settled
envelope's block is seen.
- metrics.rs and docs/metrics.md: both sides' metrics kept.
- Test fixtures of #652 follow this branch's BlockInputs (operations) and
the extra argument.
- docs/beacon_stf.md notes why gloas shares the key.
4 tasks
MegaRedHand
added a commit
that referenced
this pull request
Oct 5, 2026
…36-638-gloas-live Brings gloas validator duties (produceBlockV4, envelope publication, PTC duties and payload attestations, gloas attestation data and aggregates, VC gloas support) onto the deployment branch, keeping every behavior of #626, #633, #636, #638, #646, #647-#652, #656, #658-#660 and the sync-committee and liveness endpoints. Conflict resolutions keep both sides: the attestation pool stays in Store (tmp) while the payload attestation pool is threaded through P2P and the RPC handles (feature); the aggregate endpoints keep tmp's liveness recording and attesting indices and add the feature's fork-header check and gloas pooling; the VC tests and fake execution client serve both fulu blobs and gloas V6. Semantic fixes: a. POST /eth/v2/beacon/blocks (gloas) calls publish_beacon_block(block, Vec::new()): gloas columns travel with the envelope. RecordingNetwork implements publish_beacon_block(block, sidecars) and both new methods. b. produceBlockV4 appends client versions to the graffiti exactly like produceBlockV3 (graffiti::execution_client_version run alongside the payload build, with_client_versions, Extension<OwnVersion>) and logs it. c. Attestation data, aggregate_attestation keep require_execution_client and require_validated for gloas slots; payload_attestation_data now applies the same two rules (503 without an execution client, or when the voted block's payload is unvalidated). d. Proposer duties v1 and v2 serve gloas epochs from a fulu or gloas state's proposer_lookahead; nothing refuses gloas any more; v2 keeps its dependent root. e. The VC's per-validator ProposerSettings apply to gloas proposals (graffiti in the BlockRequest, fee recipient compared with the bid's); a test pins the graffiti. VC tests updated to the ProposerSettings constructors. f. gloas production reads the attestation pool from Store and calls the stf with the ActiveBalanceCache the perf work added; fulu production and pack_operations are untouched (gloas blocks carry no pooled operations). g. Chain events are emitted by the chain actor only, so nothing on the RPC publish paths needed to move; gloas imports reach it unchanged. h. Cargo.lock unchanged; cargo check --locked passes.
This branch has not been deployed
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.
Motivation
The first block of every epoch runs
process_epochinline on the chain actor (fork_choice::on_block, thenstf::state_transition, thenprocess_slots), followed by the post-epoch rehash the state-root check needs. In the import replay such blocks take ~1.5 s against ~0.58 s for an ordinary block (measured on the replay).The transition depends only on the parent, which is known by the end of the previous epoch's last slot, so it can run ahead of time on a blocking worker, off the import path.
Design
Eas head: precompute(E+1, head_root). Covers a late last-slot block.E, derived from the configured slot duration. The once-per-slot tick arms a delayed self-message on last slots only (send_after). Starts a worker only if none is running and the key is not cached. Covers a skipped last slot, where the head is an earlier block.spawn_blocking: clone the head state,process_slotsto the first slot ofE+1, flush pending writes,hash_tree_rootso tree-node hashes are memoized, then message the state back. At most one worker in flight; deduped by(epoch, root)through the cache.CacheKey::CheckpointState { epoch: E+1, root: head_root }.checkpoint_statederives exactly that value for the checkpoint (the checkpoint block's post-state advanced to the epoch's first slot), so attestation targets forE+1hit it too. A unit test asserts equalhash_tree_root.on_block: when the parent's slot is before the block epoch's first slot and the block is at or after it, look upCheckpointState { block_epoch, parent_root }. A hit clones the entry, advances any remaining skipped slots, and applies the block with the newstf::apply_block(the part ofstate_transitionafterprocess_slots;process_slotsrejects a target equal to the current slot, so a boundary block cannot go throughstate_transition).Forks:
process_slotsperforms the fork upgrade at the boundary, so nothing here is fork-specific.New metrics (documented in
docs/metrics.md):lean_beacon_epoch_precompute_lookups_total{result}(epoch-crossing imports only),lean_beacon_epoch_precompute_seconds,lean_beacon_epoch_precompute_started_total{trigger}.Not in this PR
compute_pulled_up_tip).Testing
ethlambda-storagelib,ethlambda-state-transitionlib,ethlambda-blockchainlib, beacon spec suite (mainnet), clippy andcargo check -p ethlambda: see counts below.checkpoint_state's for the same key; trigger decisions as a pure function (head, timer, syncing, stale head, in-flight, already cached) and the three-quarter delay.Context, so the trigger decision is tested as a pure function instead.Results: storage lib 138 passed (1 ignored); state-transition lib 327 passed (16 ignored); blockchain lib 217 passed; beacon spec suite (mainnet) 5734 passed, 4 failed (the 4 expected gossip matched_fixture_cases on the empty gossip dir), 152 ignored; clippy (workspace, and state-transition with beacon-spec-tests) clean; cargo check -p ethlambda clean.