feat(blockchain): pre-build proposer block one interval early#445
Draft
MegaRedHand wants to merge 4 commits into
Draft
feat(blockchain): pre-build proposer block one interval early#445MegaRedHand wants to merge 4 commits into
MegaRedHand wants to merge 4 commits into
Conversation
Proposers do heavy leanVM work (compact + merge_type_1s_into_type_2, ~2s) synchronously at interval 0 today. This starts that work at the previous slot's interval 4 on spawn_blocking workers (Phase A build, actor-side XMSS sign, Phase B merge), stores the result, and publishes it at interval 0 with a head/justified revalidation; on any mismatch or miss it falls back to the existing synchronous build, so there is no regression. Also fixes a finalization-breaking bug surfaced on devnet: the prebuild captured its head via get_proposal_head, which is not read-only (it ticks the store clock). Called one interval early it advanced the clock prematurely and stalled finalization. Capture the head read-only via store.head() instead.
…actor Replace the two-phase off-thread pre-build (spawn_blocking workers + PrebuildBuilt/PrebuildReady messages + session/cancel machinery) with a single synchronous build on the actor at the previous slot's interval 4. Removing the worker hop eliminates the timing race: the block is fully assembled before the interval-0 tick can consume it, so nearly every proposal is a hit instead of racing the merge against the 800ms window. Shared assemble_signed_block / process_and_publish_block helpers back both the synchronous proposal path and the pre-build. Blocking the actor during the build is acceptable: it has no other consensus-critical duty between interval 4 and the next slot. Devnet (4 nodes, 1 aggregator): finalization healthy (fin lag ~3), ~near-100% prebuild hit rate, zero skipped ticks, zero errors.
…n block_builder Remove the hit/miss/stale pre-build counters (block_building_time and the proposal phase histograms already cover the timing), and move PreparedBlock and prebuilt_block_is_usable from the standalone prebuild module into block_builder alongside PostBlockCheckpoints, which is where the rest of the block-building types live.
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.
What
Proposers do the heavy leanVM block-building work (attestation compaction + the Type-1 → Type-2 merge, ~1–2s) synchronously at interval 0 today, squeezed into the slot's first interval. This builds the next slot's block one interval early — at the previous slot's interval 4, synchronously on the blockchain actor — and publishes it at interval 0 after a revalidation. On any mismatch or failure it falls back to the existing synchronous build, so there is no regression.
How
prebuild_blockbuilds + signs + merges the full block against the current (read-only) head and stashes it as aPreparedBlock.propose_blockpublishes the prepared block iff it is still valid (prebuilt_block_is_usable: same slot/proposer, parent still the canonical head, and the built justified slot not behind the store's). Otherwise it rebuilds synchronously.assemble_signed_block/process_and_publish_blockhelpers back both the pre-build and the fallback path.sign_block_roottakes&mut self), so the build cannot be moved off-thread without risking OTS-index reuse.Finalization fix (included)
Capturing the pre-build head via
get_proposal_head— which is not read-only, it ticks the store clock — one interval early advanced the clock prematurely and stalled finalization. Fixed by capturing the head read-only viastore.head().Validation (local devnet: 4 ethlambda nodes, 1 aggregator)
Tradeoff
Building synchronously blocks the actor for the build duration at interval 4. This is acceptable: between interval 4 and the next slot the actor has no other consensus-critical duty, and the devnet run showed zero skipped ticks.
Tests
make fmt+cargo clippy -p ethlambda-blockchain --all-targets -- -D warningsclean; lib tests pass, including the newblock_builder::prebuild_testscovering the usability predicate.