Skip to content

feat(blockchain): pre-build proposer block one interval early#445

Draft
MegaRedHand wants to merge 4 commits into
mainfrom
feat/proposer-prebuild-aggregation
Draft

feat(blockchain): pre-build proposer block one interval early#445
MegaRedHand wants to merge 4 commits into
mainfrom
feat/proposer-prebuild-aggregation

Conversation

@MegaRedHand

Copy link
Copy Markdown
Collaborator

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

  • Interval 4 (of slot S-1): if one of our validators proposes slot S, prebuild_block builds + signs + merges the full block against the current (read-only) head and stashes it as a PreparedBlock.
  • Interval 0 (of slot S): propose_block publishes 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.
  • Shared assemble_signed_block / process_and_publish_block helpers back both the pre-build and the fallback path.
  • Signing stays on the actor throughout: XMSS keys are stateful (sign_block_root takes &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 via store.head().

Validation (local devnet: 4 ethlambda nodes, 1 aggregator)

  • Finalization healthy (finalized lag ~3 slots).
  • Near-100% pre-build hit rate; zero stale discards, zero skipped ticks, zero errors.

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 warnings clean; lib tests pass, including the new block_builder::prebuild_tests covering the usability predicate.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant