Skip to content

feat: move blobs from consensus txs to p2p so they can be pruned properly - #3149

Open
fridrik01 wants to merge 1 commit into
mainfrom
blobreactor-v2
Open

feat: move blobs from consensus txs to p2p so they can be pruned properly#3149
fridrik01 wants to merge 1 commit into
mainfrom
blobreactor-v2

Conversation

@fridrik01

@fridrik01 fridrik01 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

issue: #2665
replaces: #2938

Context
Blobs are carried today as a CometBFT transaction, so every block permanently stores its blob sidecars in CometBFT's block store even though sidecars only need to live for the Data Availability (DA) window. The result is unprunable disk growth, up to ~768 KiB of blob data per block (6 blobs), which at a 2-second block time is roughly 30 GiB per day at full blob usage, plus every syncing node re-downloading historical sidecars it will immediately discard.

An earlier attempt (#2938) solved this by teaching CometBFT itself about blobs, but that required maintaining a large fork of CometBFT's consensus core that we did not write, is fairly complex and that is now well behind upstream.

What this PR does
Blobs never enter CometBFT. A proposal carries a single transaction, the signed beacon block, and blob sidecars travel on their own p2p channel, handled by a new blob reactor registered through CometBFT's public reactor API.

This is Ethereum's own model. Blocks on one channel, blob sidecars on another, each pruned independently. It works safely because the block body already commits to its blobs (KZG commitments) and sidecars carry inclusion proofs against the block header, so blobs are self-verifying no matter which peer delivers them and peers never need to be trusted.

With the reactor as the delivery path, BeaconKit's availability store becomes the only copy of blob data, and the existing DA-window pruning genuinely reclaims disk.

How blobs reach nodes

  • At the tip. The proposer pushes sidecars to its peers at the same moment it hands the block to CometBFT. Nodes that verify them forward them once, and cheap "have" announcements keep duplicate traffic low, so the sidecars flood the network alongside the proposal. A validator that got the proposal but not the blobs falls back, in order, to its own execution client (engine_getBlobsV2, usually a local hit since blob transactions gossip through the EL mempool with their blobs attached) and then to a targeted request to peers. Everything retrieved is fully verified against the block before the node votes. No blobs in time means rejecting the proposal, the same posture as any invalid proposal, so finality still implies that 2/3+ of voting power held and verified the data.

  • While syncing. Blocks arriving through block sync no longer contain blobs. A background fetcher requests missing in-window sidecars from peers in bulk, verifies them, and persists them, backed by a crash-safe on-disk retry queue. Two rules are strict by design. A response with fewer sidecars than the block commits to is a failure, never an "empty success", and requests are retried until the slot leaves the DA window, never dropped after a retry cap. A node does not report itself as synced while in-window fetches are pending, so "synced within the DA window" keeps implying "holds the blobs".

Every node answers blob requests from its availability store, bounded by per-peer rate limits, response size caps, and peer scoring.

Activation

The transition is gated by a new blob-consensus.enable-height chain-spec parameter. Below the height blobs ride as the second consensus transaction exactly as today. At and above it, proposals carry one transaction and the reactor is authoritative. Decoding honors each layout at its historical heights, so replay from genesis works across the boundary.

This PR enables the feature on devnet only. Mainnet and Bepolia specs keep it disabled (height 0), so behavior on public networks is unchanged until an activation height is scheduled.

Trade-off vs #2938

CometBFT no longer enforces blob delivery before prevote, so there is a small liveness risk if a validator cannot fetch a block's blobs within a round. In practice blocks without blobs are unaffected, and a validator that misses the push almost always finds the blobs in its own execution client. In exchange we drop the unmaintained consensus fork entirely, and we fix two sync-path issues the original design had, a fixed retry cap that could silently drop a fetch and leave a permanent DA gap, and empty peer responses being counted as success.

Testing

  • Unit: Reactor protocol and scheduling, the crash-safe retry queue, the execution-client reconstructor, and the verification decision tree, including adversarial cases (short or empty responses rejected, count mismatches rejected, poisoned pushes evicted, retries persisting to window expiry).
  • Simulated (CI): A multi-node blob-fetch integration test over real p2p connections, and a suite driving the chain across the enable-height boundary through the full ABCI flow.
  • Devnet: A chaos harness (testing/blobs/blob-sync-local-test.sh) that runs a local multi-node devnet, sends blob transactions, and repeatedly restarts random nodes, asserting each catches up and serves the same sidecars as a healthy reference node.

Still left to deploy this to devnet-core/devnet-cli.

Operator notes

  • New required chain-spec key: Chain-spec files must add a [blob-consensus] section with enable-height (0 disables the feature), consistent with all other required spec fields.
  • Availability-store key format changed to disambiguate duplicate KZG commitments within a block.
  • Pre-fork blobs already inside CometBFT's block store are not reclaimed by this change.

@fridrik01 fridrik01 self-assigned this Jul 22, 2026
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.08528% with 419 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.26%. Comparing base (2059e2a) to head (be843f8).

Files with missing lines Patch % Lines
da/blobreactor/reactor.go 69.28% 53 Missing and 29 partials ⚠️
da/blobreactor/requests.go 71.88% 40 Missing and 21 partials ⚠️
beacon/blockchain/blob_queue.go 63.15% 29 Missing and 20 partials ⚠️
beacon/blockchain/blob_fetcher.go 74.82% 25 Missing and 11 partials ⚠️
da/blobreactor/push.go 80.35% 24 Missing and 9 partials ⚠️
da/blobreactor/messages.go 85.95% 14 Missing and 3 partials ⚠️
node-api/handlers/beacon/blobs.go 15.00% 13 Missing and 4 partials ⚠️
beacon/blockchain/blob_retrieval.go 75.00% 14 Missing and 1 partial ⚠️
beacon/blockchain/process_proposal.go 48.27% 12 Missing and 3 partials ⚠️
da/blobreactor/serve.go 76.27% 8 Missing and 6 partials ⚠️
... and 16 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3149      +/-   ##
==========================================
+ Coverage   63.11%   64.26%   +1.15%     
==========================================
  Files         372      385      +13     
  Lines       15549    16965    +1416     
==========================================
+ Hits         9814    10903    +1089     
- Misses       4775     4993     +218     
- Partials      960     1069     +109     
Files with missing lines Coverage Δ
beacon/blockchain/common.go 70.58% <100.00%> (+28.28%) ⬆️
beacon/blockchain/service.go 84.00% <100.00%> (+3.51%) ⬆️
chain/spec.go 75.47% <100.00%> (+3.39%) ⬆️
config/config.go 85.00% <100.00%> (+0.38%) ⬆️
config/spec/devnet.go 100.00% <100.00%> (ø)
config/spec/mainnet.go 100.00% <100.00%> (ø)
config/spec/testnet.go 100.00% <100.00%> (ø)
consensus/cometbft/service/blobconsensus/config.go 100.00% <100.00%> (ø)
consensus/cometbft/service/cache/cache.go 88.23% <ø> (ø)
consensus/cometbft/service/process_proposal.go 85.36% <100.00%> (+15.36%) ⬆️
... and 33 more

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fridrik01
fridrik01 marked this pull request as ready for review July 23, 2026 11:09
@fridrik01
fridrik01 requested a review from a team as a code owner July 23, 2026 11:09
Copilot AI review requested due to automatic review settings July 23, 2026 11:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR moves blob sidecar distribution out of CometBFT blocks and onto a dedicated P2P reactor channel (with EL reconstruction + peer fetch fallback), so blob data lives only in the availability store and can be pruned correctly after the DA window. The change is fork-gated via a new blob-consensus.enable-height chain-spec parameter (enabled on devnet only), and it updates sync reporting + APIs so “synced” continues to imply “has all in-window blobs”.

Changes:

  • Introduces a CometBFT Reactor-based blob sidecar gossip/request protocol (da/blobreactor) and wires it into Comet service startup and proposal flow.
  • Adds tiered sidecar retrieval (push cache → local EL blob pool via engine_getBlobsV2 → peer by-root fetch) plus a crash-safe background fetcher/queue for sync/catch-up.
  • Updates availability storage semantics (slot replacement + commitment||index keys) and sync/API behavior to reflect pending in-window blob fetches.

Reviewed changes

Copilot reviewed 78 out of 78 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
testing/simulated/utils.go Makes simulated chain advancement tolerant to 1-tx vs 2-tx proposal layouts across the enable-height boundary.
testing/simulated/simcomet.go Injects blob reactor into simulated Comet service and exposes pending-blob-fetch status.
testing/simulated/components.go Adds DI providers for blob reactor/reconstructor/fetcher and a blob-consensus-enabled sim chain spec.
testing/simulated/blob_fetcher_integration_test.go New integration test for background blob fetch lifecycle over real P2P switches.
testing/simulated/blob_consensus_test.go New simulated suite validating tx layout transition across enable height through ABCI flow.
testing/networks/80094/spec.toml Adds [blob-consensus] section (disabled) for mainnet spec file.
testing/networks/80069/spec.toml Adds [blob-consensus] section (disabled) for testnet spec file.
testing/files/spec.toml Adds [blob-consensus] section (enabled at 2) for local testing spec.
storage/filedb/range_db.go Rejects writes below pruning lower bound to prevent resurrecting/clobbering pruned indices.
storage/filedb/range_db_test.go Adds coverage for rejecting stale writes below the pruning lower bound.
node-core/types/node.go Extends ConsensusService interface with HasPendingBlobFetches.
node-core/types/mocks/consensus_service.mock.go Regenerates consensus service mock with HasPendingBlobFetches.
node-core/components/cometbft_service.go Wires blob reactor dependency into CometBFT service provider.
node-core/components/chain_service.go Wires blob reactor/reconstructor/fetcher into blockchain service provider.
node-core/components/blob_reactor.go New DI providers for blob reactor, EL reconstructor, and background blob fetcher.
node-api/handlers/node/node.go Marks node as syncing if blob fetches are pending (in addition to sync distance).
node-api/handlers/node/node_test.go Updates node syncing handler tests for HasPendingBlobFetches.
node-api/handlers/node/mocks/backend.mock.go Regenerates node handler backend mock with HasPendingBlobFetches.
node-api/handlers/node/backend.go Extends node handler backend interface with HasPendingBlobFetches.
node-api/handlers/beacon/blobs.go DA-window validation relative to head slot and “blobs pending” not-found behavior.
node-api/backend/getters.go Delegates HasPendingBlobFetches from API backend to node backend.
kurtosis/src/services/spamoor/launcher.star Adds startup delay before blob spam command to allow RPC readiness.
go.mod Adds direct dependencies needed by new code paths (gogoproto, x/time).
execution/client/metrics.go Adds metric timing for EL blob retrieval calls.
execution/client/ethclient/engine.go Implements engine_getBlobsV2 JSON-RPC call wrapper.
execution/client/ethclient/constants.go Advertises engine_getBlobsV2 capability constant.
execution/client/engine.go Exposes EngineClient.GetBlobsV2 with capability gating and metrics/timeout handling.
engine-primitives/errors/errors.go Adds ErrGetBlobsUnsupported for missing EL blob API support.
da/store/store.go Updates blob store keying to include blob index and makes Persist slot-idempotent by clearing slot first.
da/store/store_test.go Adds tests for pruning/write race rejection, duplicate commitments, and Persist idempotency.
da/kzg/proof.go Adds BlobProofProver interface to compute canonical blob proofs for sidecar reconstruction.
da/kzg/noop/noop.go Implements ComputeBlobProof for the noop verifier.
da/kzg/gokzg/gokzg.go Implements ComputeBlobProof using go-kzg context.
da/blobreactor/serve.go New request serving logic for by-root and by-range sidecar requests.
da/blobreactor/requests.go New request/response tracking, peer selection/scoring hooks, and fetch APIs.
da/blobreactor/push.go New push-cache + gossip logic (push/have) with bounded caching and verification.
da/blobreactor/metrics.go New metrics helpers and error-status classification for reactor operations.
da/blobreactor/messages.go New SSZ message types and envelope for Comet reactor channel transport.
da/blobreactor/messages_test.go Tests SSZ round-trips and validation for reactor message types.
da/blobreactor/interfaces.go Defines minimal interfaces for blob store, verifier, and telemetry sink integration.
da/blobreactor/export_test.go Exposes internal helpers/hooks to facilitate deterministic reactor tests.
da/blobreactor/errors.go Defines reactor-level errors (no peers, all peers failed, slot not requested).
da/blobreactor/config.go Adds reactor config for per-peer request timeout and overall fetch deadline.
da/blob/reconstructor.go New EL-based sidecar reconstructor (engine_getBlobsV2 + local proof recomputation).
da/blob/reconstructor_test.go Tests canonical sidecar reconstruction and rejects malformed EL responses.
consensus/cometbft/service/service.go Registers blob reactor via Comet custom reactors and wires chainSpec into service struct.
consensus/cometbft/service/process_proposal.go Caches verified sidecars alongside cached state for FinalizeBlock path.
consensus/cometbft/service/prepare_proposal.go Switches proposal tx layout at enable height and broadcasts sidecars via reactor.
consensus/cometbft/service/node_api_support.go Implements HasPendingBlobFetches via blockchain pending queue depth.
consensus/cometbft/service/interfaces.go Adds BlobReactorI interface used by Comet service.
consensus/cometbft/service/finalize_block.go Refactors cached-finalization path and sources sidecars from cache post-transition.
consensus/cometbft/service/encoding/interfaces.go Extends ABCIRequest interface with GetHeight() for height-aware decoding.
consensus/cometbft/service/cache/cache.go Extends cached-state element to include verified sidecars.
consensus/cometbft/service/blobconsensus/config.go New chain-spec config surface for blob consensus activation gating.
config/template/template.go Adds [beacon-kit.blob-reactor] config template entries.
config/spec/testnet.go Ensures blob-consensus defaults are explicitly set for Bepolia spec data.
config/spec/mainnet.go Ensures blob-consensus defaults are explicitly set for mainnet spec data.
config/spec/devnet.go Enables blob-consensus transition early on devnet spec data.
config/spec/creator_test.go Extends spec-creator validation tests to require blob-consensus.enable-height.
config/config.go Adds blob reactor config to node config defaults and schema.
cmd/beacond/defaults.go Includes blob reactor/reconstructor/fetcher providers in default component set.
chain/spec.go Adds blob-consensus getters and IsBlobConsensusEnabled to chain spec interface/impl.
chain/data.go Adds blob-consensus section to chain SpecData (mapstructure key blob-consensus).
beacon/blockchain/service.go Adds blob distribution dependencies, starts/stops fetcher, and adjusts orphan pruning behavior.
beacon/blockchain/process_proposal.go Returns verified sidecars from ProcessProposal and fetches them when consensus tx no longer carries them.
beacon/blockchain/payload_test.go Updates tests to satisfy new blockchain service constructor dependencies.
beacon/blockchain/interfaces.go Extends interfaces for new blob requester/reconstructor/fetcher and updated ProcessProposal/FinalizeSidecars signatures.
beacon/blockchain/finalize_sidecars_test.go New unit tests for FinalizeSidecars decision tree and pruning behavior across transition.
beacon/blockchain/finalize_block.go Updates FinalizeBlock to pass signed block into sidecar finalization and wires head slot into fetcher.
beacon/blockchain/common.go Height-aware decoding: legacy 2-tx vs blob-consensus 1-tx proposal layouts.
beacon/blockchain/common_test.go New tests validating ParseBeaconBlock behavior across enable height.
beacon/blockchain/blob_retrieval.go New tiered tip-of-chain sidecar retrieval and binding verification helpers.
beacon/blockchain/blob_queue.go New crash-safe on-disk retry queue for background blob fetch requests.
beacon/blockchain/blob_queue_test.go Tests for queue correctness, durability, expiry, corruption handling, and metachar paths.
beacon/blockchain/blob_fetcher.go New background fetcher draining queue via by-range requests with strict verification and no retry cap.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread da/store/store.go
Comment thread engine-primitives/errors/errors.go Outdated
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.

2 participants