Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reading built blocks

audience: integrators

Reading candidate blocks is the atelier organism’s job; reading which of those blocks a proposer accepted is the relay organism’s. This page shows what each surface gives you and how to verify it.

Who reads from atelier and relay

  • Proposers / sequencers that consume candidate blocks from the lattice and ship them on-chain. They read atelier for the block body and use relay to track whether the proposer (or the sequencer internally) accepted it.
  • Analytics agents that replay or index the lattice’s output. They read both organisms read-only.
  • Audit agents that cross-check the lattice’s public commit logs against on-chain state. Same.

Searchers who want to confirm their bundle landed typically do not need atelier directly — they read tally::Refunds for the slot and trust the tally committee’s signature. atelier is for consumers that need the block body itself.

Open the handles

use atelier::Atelier;
use relay::Relay;

let mut candidates = Atelier::<Block>::read (&network, &ETH_MAINNET.atelier).await?;
let mut accepted   = Relay::<Header>::watch (&network, &ETH_MAINNET.relay  ).await?;

Both are read-only Streams. You do not need committee membership to open them; a ticket admitting you to the lattice’s read-side surface is sufficient.

What an atelier Block carries

pub struct Block {
    pub slot:            u64,
    pub header:          Header,     // standard chain header
    pub body:            Vec<Tx2718>, // canonical tx list
    pub builder_sig:     BlsAggSig,  // see below
    pub committee_roster: Vec<BlsPub>, // at-commit-time committee pubkeys
    pub hints_applied:   Vec<HintId>, // co-builder hints folded in
}

builder_sig is the atelier committee’s BLS aggregate signature over blake3(slot ‖ header ‖ body ‖ hints_applied). committee_roster is the set of committee public keys that signed, as of the commit moment.

Verifying the committee signature

atelier::Config carries the expected committee public keys and the expected TDX MR_TD. The verification function is pure:

if !atelier::verify(&candidate, &ETH_MAINNET.atelier) {
    eprintln!("invalid atelier sig on slot {}", candidate.slot);
    continue;
}

verify checks:

  1. builder_sig aggregates to a valid signature under committee_roster’s keys over the hash above.
  2. committee_roster is a majority-subset of the pubkey list pinned in ETH_MAINNET.atelier.
  3. The hash in builder_sig matches the actual slot / header / body / hints_applied fields.

If you additionally compile with tee-tdx, the organism’s ticket validator has already checked TDX quotes on every committee member’s PeerEntry before the bond was formed; you do not verify MR_TDs in verify itself (they are enforced at admission time).

A verify failure means either the committee rotated its roster and you are on stale pubkeys, or the block was committed by a committee that does not match the pinned config. Both are cause to abort the consuming action.

Reading relay acknowledgements

Relay::<Header>::watch gives you the committed AcceptedHeaders collection as a stream:

pub struct AcceptedHeader {
    pub slot:        u64,
    pub header:      Header,
    pub bid:         u128,
    pub proposer:    ProposerId,
    pub ack_evidence: Vec<u8>, // proposer-signed payload
}

while let Some(ack) = accepted.next().await {
    println!("slot {}: proposer {:?} accepted at bid {}",
             ack.slot, ack.proposer, ack.bid);
}

ack_evidence is what the relay committee recorded from the proposer. On L1 with MEV-Boost, it is a proposer-signed payload the relay committee collected over the standard MEV-Boost submission API; on an L2, it is the sequencer’s equivalent.

Relay commits AcceptedHeaders[S] when a majority of its committee agrees the proposer acknowledged. A majority-malicious relay committee can forge an acceptance; tally’s on-chain inclusion watcher is the ground truth that cross-checks.

What a “slot” means on L1 vs L2

  • L1 PBS. Slot is the proposer’s slot number; one header per slot; AcceptedHeader corresponds to the proposer’s MEV-Boost acceptance for that slot.
  • L2 with centralized sequencer. Slot is the sequencer’s block number (or sub-block if the lattice ships at a finer cadence); AcceptedHeader is the sequencer’s internal accept of the candidate.
  • L2 with decentralized sequencer. Slot is the chain’s leader-rotation index; AcceptedHeader is the elected leader’s accept for that slot.

atelier::Block::header is the chain’s native header type in every case; your code does not need to branch on chain type unless you are decoding ack_evidence directly.

Handling gaps

A lattice that fails to commit a Candidates[S] for some slot — because the slot’s window expired with insufficient hints, or because the committee was degraded — simply does not emit an entry. You will see a gap in the stream. Do not assume the lattice has retried; the next slot’s entry is the next item in the stream. Fill gaps from the chain itself when your use case requires a dense sequence.

Next reading