Repository navigation
feat(beacon): rebuild held blocks' data columns from the execution client's blob pool - #769
MegaRedHand wants to merge 4 commits into
Conversation
…ient's blob pool A fulu block whose custody columns have not all arrived waits on gossip and on per-slot peer asks. The blob transactions it commits to usually passed through the local execution client's pool first, so the node can often build those columns itself, sooner. Fulu's p2p-interface asks honest nodes to do so with engine_getBlobsV2, as Lighthouse and the other clients already do. On a fulu block's first data-availability hold, with an execution client configured, the chain actor spawns a task that asks engine_getBlobsV2 once (1 s, the spec's timeout, outside the retry ladder: the call races gossip), builds the custody columns still missing without verifying the KZG proofs (the spec says to skip verification for local-EL blobs), and hands them to p2p through the existing check_data_column_sidecars, now tagged with a SidecarOrigin. For ExecutionLayer sidecars p2p runs no checks; it records them in the seen cache, publishes them on their subnets, and forwards them to the chain actor, which stores them and releases the block. Those two steps are the spec's MUSTs for importing local-EL blobs, and p2p owns both. V2 only: V3 answers partially, and a column needs a cell from every blob. Gloas is not covered, since availability moves to the payload envelope.
🤖 Claude Code ReviewReview of PR 769: execution-client blob fetching (
|
🤖 Codex Code Review
Tests could not run because the required Rust toolchain attempted to write to a read-only directory. Automated review by OpenAI Codex · gpt-6.1-sol · custom prompt |
🤖 Grok Code ReviewThe execution-layer blob path is well structured, and the V2-only, one-shot, 1s timeout matches the spec. A few consensus and liveness issues should be fixed before this is relied on.
Smaller notes: Automated review by Grok (xAI) · grok-4.7 · custom prompt |
🤖 Gemini Code ReviewThe implementation of Issues
Minor
Automated review by Gemini · gemini-4-argon · custom prompt |
🤖 DeepSeek Code ReviewOverall this is a clean refactor ( Findings:
Minor:
Automated review by DeepSeek · deepseek-v4-pro · custom prompt |
🤖 Kimi Code ReviewFindings
Otherwise, the implementation looks careful: strict wire-shape validation, correct V2 all-or-nothing handling, bounded/deduplicated column construction, and seen-cache recording before publication all align well with the intended Fulu flow. Automated review by Kimi (Moonshot AI) · kimi-k3.1-pro · custom prompt |
…ient's blob pool (#769) A fulu block whose custody columns have not all arrived waits on gossip and on per-slot peer asks. The blob transactions it commits to usually passed through the local execution client's pool first, so the node can often build those columns itself, sooner. Fulu's p2p-interface asks honest nodes to do so with engine_getBlobsV2, as Lighthouse and the other clients already do. On a fulu block's first data-availability hold, with an execution client configured, the chain actor spawns a task that asks engine_getBlobsV2 once (1 s, the spec's timeout, outside the retry ladder: the call races gossip), builds the custody columns still missing without verifying the KZG proofs (the spec says to skip verification for local-EL blobs), and hands them to p2p through the existing check_data_column_sidecars, now tagged with a SidecarOrigin. For ExecutionLayer sidecars p2p runs no checks; it records them in the seen cache, publishes them on their subnets, and forwards them to the chain actor, which stores them and releases the block. Those two steps are the spec's MUSTs for importing local-EL blobs, and p2p owns both. V2 only: V3 answers partially, and a column needs a cell from every blob. Gloas is not covered, since availability moves to the payload envelope. On nightly, a Parked recheck keeps #765's ColumnArrival::Recheck, and the CLAUDE.md peer-scoring line keeps #739's slow-peer text. Columns built from the execution client write no block_events telemetry rows: #765's column rows cover gossip-accepted and fetched columns only. Cherry-picked from feat/engine-get-blobs-v2 at 39d5f07 (e0f69b3 plus a merge of main). Claude-Session: https://claude.ai/code/session_011vghNmndKPkZinHRkVTwVB
…proposer signed A held fulu block's blob fetch builds columns that carry the block's own signed header, and p2p records and publishes those with no check (SidecarOrigin::ExecutionLayer). A block reaches its hold unverified when it comes from range sync or a by-root answer, since only gossip checks the proposer signature before data availability. A peer serving a forged copy whose commitments name blobs in the local pool therefore had this node publish columns its peers reject, and take the seen-cache key of the real block's columns from the same proposer and slot. The first hold now runs precheck_block against the parent's post-state (expected proposer and signature) before asking. The tests sign their blocks over a keyed parent state, and a new one checks that an altered copy is not asked about.
🗒️ Description / Motivation
A fulu block whose custody data columns have not all arrived is held until gossip (or a per-slot peer ask) delivers them. The blob transactions such a block commits to usually passed through the local execution client's pool first, so the node can often build those columns itself, sooner. Fulu's
p2p-interface.md("Distributed blob publishing using blobs retrieved from local execution-layer client") asks honest nodes to queryengine_getBlobsV2for exactly this, and Lighthouse and the other clients already do. This adds it toethlambda beacon.What Changed
engine_getBlobsV2,BlobAndProofV2, wire-shape checks, capability listcrates/net/engineunverified_sidecars: builds only the requested columns, sharing code withverified_sidecarscrates/blockchain/state_transition/src/beacon/data_columns.rscrates/blockchain/src/beacon_blobs.rs, hook inhold_block_for_columnsSidecarOrigin { Parked, ExecutionLayer }on the existingcheck_data_column_sidecarscrates/net/apiExecutionLayersidecarscrates/net/p2p/src/beacon/engine_columns.rsdocs/beacon_engine.md(new section),docs/metrics.md,CLAUDE.mdDesign decisions:
BlockChainToP2Pmethod. The existingcheck_data_column_sidecarsgains aSidecarOrigin.Parkedkeeps today's behavior (chain checks);ExecutionLayerskips the checks and does what the spec requires instead. The seen cache and publishing live in p2p, so that is where they happen.osaka.md's timeout), outside the engine retry ladder: the call races gossip delivery of the same columns, so a retry after the ladder's backoff would land after that race is decided.from_execution_verified).--execution-endpointis set.Not covered (follow-ups): asking on a
data_column_sidecar's arrival (the spec's second trigger); gloas, where availability moves to the payload envelope; partial columns (V3).Open questions for review:
getBlobsV2per held block (up to a batch's worth concurrently, each capped at 1 s). A syncing execution client answersnullat once, so this is cheap in practice, but it is an unbounded fan-out. Alternatives: ask only for blocks near the tip (slot + N >= current_slot), or a small semaphore.publish_data_columns_gradually) to avoid a bandwidth spike.New metrics:
lean_engine_get_blobs_total{outcome},lean_engine_get_blobs_duration_seconds,lean_engine_blobs_to_columns_seconds,lean_data_columns_from_execution_layer_total{result}.Correctness / Behavior Guarantees
--execution-endpoint: the hook needs an engine client.Parkedsidecars take exactly the old path.ExecutionLayersidecar is recorded inseen_columnsbefore it is published, so a later gossip copy of the same(slot, proposer, index)is ignored, and a column gossip already accepted is neither republished nor re-forwarded.spawn_blockingfor the cells) and re-checks, just before building, that the block is still unimported and which custody columns are still missing.Tests Added / Run
ethlambda-engine: wire round trips for anullanswer (and the request's method and params), a full answer in order, a wrong proof count, a length mismatch; unit tests for a wrong blob size and anullelement.data_columns: requested columns equal the matching entries ofverified_sidecars, ascending and deduplicated; empty inputs; shape errors; a tampered proof still builds (nothing is verified).engine_columns: newly seen custody columns are recorded and forwarded in one batch; an already seen column is skipped; a non-custody column is dropped; nothing new sends nothing.beacon_blobs: against a mock execution client serving real KZG blobs and proofs: a full answer hands over exactly the custody columns; a stored column is not handed over again; anullanswer hands over nothing; no engine asks nothing; a second hold does not ask again.Not yet run against a live execution client.
Related Issues / PRs
✅ Verification Checklist
make fmt— cleanmake lint(clippy with-D warnings) — cleanmake test(test-consensusplustest-node, atrelease-fast) — not run; the per-crate tests above were run instead