Skip to content

feat(builder): observe beacon node blocks - #48

Draft
krisoshea-eth wants to merge 8 commits into
unstablefrom
krisoshea/lod-12-api-02-consume-bn-block-events-and-retrieve-fork-correct
Draft

feat(builder): observe beacon node blocks#48
krisoshea-eth wants to merge 8 commits into
unstablefrom
krisoshea/lod-12-api-02-consume-bn-block-events-and-retrieve-fork-correct

Conversation

@krisoshea-eth

@krisoshea-eth krisoshea-eth commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Motivation

The Lodestar Builder has no source-BN observation path for learning that a proposer selected a bid. API-02 requires this signal to come from the Builder's trusted beacon node REST API, without adding a libp2p subscription.

Current behavior

  • The Builder does not subscribe to beacon-node block events.
  • It does not retrieve the signed fork-correct block associated with an event.
  • Downstream bid-selection work has no bounded, deduplicated block-observation seam.

Proposed behavior

  • Add a BlockObserver that subscribes only to the standard block SSE topic.
  • Deduplicate block roots before asynchronous work and retain a FIFO window of 256 roots.
  • Retrieve each new post-Gloas block with getBlockV2, passing the Builder abort signal to both the event stream and HTTP request.
  • Retry 404, server, timeout, and transport failures for up to six attempts with five 200 ms delays. Do not retry other 4xx responses, cancellation, response decoding, or structural failures.
  • Use the Lodestar retry() utility because the API client's built-in retries do not provide the status predicate needed here and would also retry deterministic 4xx responses.
  • Keep the per-request timeout owned by the injected API client. This PR bounds attempts and explicit retry delays, but does not define a total wall-clock or slot-relative deadline. SELECT-01 and REV-01 own that deadline policy.
  • Treat response version metadata as fork authority and require a Gloas-compatible block body before extracting signedExecutionPayloadBid.
  • Verify that the returned block slot matches the event slot before dispatching it. Independent block-root hashing remains deferred to recovery and verification work; the observer uses the source BN's standard root-addressed endpoint.
  • Preserve the exact fork-specific post-Gloas signed-bid object, including Heze extensions, exact-width values, and BUILDER_INDEX_SELF_BUILD.
  • Dispatch observations concurrently through individually isolated runOnBlock callbacks so one slow or failing consumer cannot block another. Callback cancellation is silent; genuine callback failures are logged.
  • Require consumers needing complete observation to register before start(), because roots observed before registration remain consumed until FIFO eviction.
  • Log the subscription attempt without claiming a connection is established. Log abort-driven stream closure at debug and unexpected permanent closure at error. REL-01 owns resubscription, replay, and recovery.
  • Start the observer after the Builder clock and existing initialization gates. On this branch, the SSE subscription is the concrete long-lived duty that keeps the Builder active. After feat(builder): add readiness gating and builder identity resolution ChainSafe/lodestar#9781 merges, the wiring and wording will be reconciled with its readiness and status duties.

This PR does not add p2p, block_gossip, canonical-chain filtering, local-bid matching, reveal behavior, metrics, reconnect, replay, restart recovery, multi-BN failover, or a new API endpoint.

API audit

The standard block event contains slot, beacon block root, and execution optimism, but not fork version, Builder index, or execution block hash. getBlockV2 supplies the signed fork-correct block and Eth-Consensus-Version metadata, so the standard flow is sufficient for correctness at the cost of one fetch per newly observed post-Gloas block. head and head_v2 are not substitutes because they describe the current head and can omit an imported non-head block; head_v2 also reports payload status rather than selected-bid identity.

The sibling execution_payload event already uses builder_index, block_hash, and block_root, but is emitted only after a signed payload envelope is received and imported. It therefore carries the relevant selection identity too late to trigger reveal. A future enriched block event or dedicated bid_included event could reuse those existing field names.

Lodestar emits the block event after state transition and fork-choice import. Its root lookup checks fork choice for presence, then serves the block from the seen-block input cache or database, with cache pruning after the database write. Lodestar therefore has no expected event-before-block 404 window. The Beacon API does not guarantee equivalent ordering across clients, so the Builder still applies bounded 404 retry as a cross-client precaution. This implementation provides evidence for ethereum/beacon-APIs#599, but no API change is required for correctness.

On Node 24.13.0, Lodestar uses its npm eventsource fallback. Connection establishment and BN unavailability surface asynchronously through the stream error callback; the eventstream promise rejection covers setup failures rather than connectivity. The current Builder CLI supplies one BN URL, although the shared API client would otherwise pin SSE to its first URL while allowing ordinary REST fallbacks. The Beacon API event-stream contract defines neither an SSE id nor Last-Event-ID resumption, so no conforming client can be assumed to provide exact replay. ENV-01 owns connected/reconnecting shutdown evidence, REL-01 owns bounded same-source reconciliation, and the existing deferred multi-BN and advanced replay issues retain the wider work.

The detailed audit and decision record are in Builder docs PR #13. The implementation evidence was posted to beacon-APIs #599.

Baseline: ChainSafe/lodestar unstable at 713b21812905bb34e547ca85d5f630060179da67.

Specification: consensus-specs v1.7.0-alpha.13.

Project issue: #12.

Testing

  • pnpm vitest run --project unit packages/builder/test/unit/services/blockObserver.test.ts
  • pnpm --filter @lodestar/builder test:unit
  • pnpm --filter @lodestar/builder check-types
  • pnpm --filter @lodestar/builder lint
  • pnpm --filter @lodestar/builder build
  • pnpm --filter @lodestar/builder check-build
  • git diff --check

Using Node 24.13.0, all focused checks pass. The observer suite contains 25 tests. The complete Builder package suite contains 31 passing tests, including the six existing Builder signer tests.

Coverage includes subscription wiring and attempt logging, unexpected-event defense, Gloas and Heze fork-correct signed-bid identity, returned-slot consistency, sequential and concurrent duplicates, bounded 404 and 5xx recovery, persistent 404 exhaustion, retry classification, deterministic cancellation, fork gates, response decoding failures, metadata/body disagreement, FIFO eviction, self-build, expected and unexpected stream closure, stream and setup failures, concurrent callback isolation, callback cancellation, and shared abort-signal use.

The clean baseline passed the existing Builder type-check and six Builder unit tests after building the Builder dependency graph.

AI Assistance Disclosure

  • External Contributors: I have read the contributor guidelines and disclosed my usage of AI below.

AI assistance was used during codebase research, implementation drafting, testing, and review. The submitted code and PR text were reviewed and revised by the authors, with manual edits and technical decisions incorporated throughout. The authors take responsibility for the final result.

@linear-code

linear-code Bot commented Aug 10, 2026

Copy link
Copy Markdown

LOD-12

LOD-48

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 06909248-a08f-4c64-8744-e5c22e2b45c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch krisoshea/lod-12-api-02-consume-bn-block-events-and-retrieve-fork-correct

Comment @coderabbitai help to get the list of available commands.

@krisoshea-eth
krisoshea-eth marked this pull request as ready for review August 10, 2026 20:40
@krisoshea-eth krisoshea-eth self-assigned this Aug 10, 2026
@krisoshea-eth krisoshea-eth moved this from Todo to In Review in Lodestar EIP-7732 Builder Aug 10, 2026
@krisoshea-eth
krisoshea-eth marked this pull request as draft August 10, 2026 21:03
@krisoshea-eth krisoshea-eth changed the title feat(builder): consume beacon node block events and retrieve fork-correct blocks feat(builder): observe beacon node blocks Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

1 participant