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
atelierfor the block body and userelayto 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, Ð_MAINNET.atelier).await?;
let mut accepted = Relay::<Header>::watch (&network, Ð_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, Ð_MAINNET.atelier) {
eprintln!("invalid atelier sig on slot {}", candidate.slot);
continue;
}
verify checks:
builder_sigaggregates to a valid signature undercommittee_roster’s keys over the hash above.committee_rosteris a majority-subset of the pubkey list pinned inETH_MAINNET.atelier.- The hash in
builder_sigmatches 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;
AcceptedHeadercorresponds 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);
AcceptedHeaderis the sequencer’s internal accept of the candidate. - L2 with decentralized sequencer. Slot is the chain’s
leader-rotation index;
AcceptedHeaderis 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
- Receiving refunds and attributions — where
tallygives you the “which submissions contributed to this block” side. - atelier organism spec — the full public-surface + state-machine spec.
- threat-model — atelier — what TDX attestation gets you and what it does not.