Skip to content

Merge beacon payload attributes with relay duties into a slot context - #556

Open
0w3n-d wants to merge 1 commit into
od/builder-role-step1from
od/builder-slot-context-step2
Open

Merge beacon payload attributes with relay duties into a slot context#556
0w3n-d wants to merge 1 commit into
od/builder-role-step1from
od/builder-slot-context-step2

Conversation

@0w3n-d

@0w3n-d 0w3n-d commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Issue: #550 (step 2 of 6)

What this PR does

Adds SlotTracker, which merges the beacon node's payload_attributes SSE
events with the relay's proposer duties and emits one SlotContext per slot
worth building. Neither source alone is enough: the beacon node gives the
parent, timestamp, prev_randao, withdrawals and parent_beacon_block_root,
and the relay gives the proposer pubkey, fee recipient and registered gas
limit.

The building role now runs. It subscribes, polls duties every 12s, and logs
each slot it would build for. Nothing is built or submitted yet.

The fee recipient and gas limit are taken from the duty, never from the
event's suggested_fee_recipient — that field is the local validator's, and
paying it would produce a block the relay rejects.

What this PR deliberately does not do

No block assembly, no submission. The SlotContext receiver in main.rs is a
drain until step 3. SlotContext carries #[allow(dead_code)] until then.

Duties are deserialized into the relay's own BuilderGetValidatorsResponse
rather than a local struct, which keeps the two in lockstep but means this
builder targets Helix, not any mev-boost relay.

Tests

11 new, written before the implementation and signed off first. The merge
logic is pure and carries all of them; the SSE and HTTP calls stay thin and
are exercised by running the role.

  • Merging (3): an event plus a matching duty yields a context; the fee
    recipient and gas limit come from the duty, not the event; an event with no
    duty is skipped.
  • Which events to act on (4): a repeated event is skipped, since the beacon
    node re-emits whenever it recomputes; a new parent for the same slot is
    not a duplicate and must rebuild, because the relay keys attributes by
    (parent_hash, slot); an older slot is discarded, as an SSE reconnect
    replays; a missing parent_beacon_block_root skips the slot, since EIP-4788
    makes it mandatory.
  • Duty cache (2): a refresh replaces the previous set; past slots are pruned so
    duties do not accumulate across epochs.
  • Wire contract (2): fixtures for a real get_validators response and a real
    beacon SSE payload.

The get_validators fixture already earned its place — it caught that
Filtering serializes as "global", not 0.

114 pass in the crate.

Reviewer checklist

  • CI (lint, unit-test) is green
  • Matches the linked issue/step
  • No unexplained scope creep or unrelated files touched

The beacon node supplies the consensus fields and the relay supplies the
proposer. Neither alone is enough to build.

Take the fee recipient and gas limit from the duty. The event carries a
`suggested_fee_recipient`, but it is the local validator's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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