Skip to main content

Module receiver

Module receiver 

Source
Expand description

The CTB receiver for one broadcaster (docs/rhdl_ubft-ctb.md).

Problem. A Byzantine broadcaster can send different messages for the same index to different receivers. Upstream uBFT stops that with tail consistent broadcast, but answers every Byzantine input with throw (U10), spins on a tail gap (U09) and trusts remote lengths (U21).

Idea. The same algorithm as a step function with explicit ports, so every Byzantine case has a defined next state:

  • fast path: every other receiver echoes the digest we computed;
  • slow path: the broadcaster’s signature verifies, our register write is visible, and every other receiver’s register is checked;
  • a proven Byzantine broadcaster (duplicate or out-of-order index, duplicate or invalid signature, equivocation, oversized length) is quarantined: this instance delivers nothing more, and instances for other broadcasters are untouched;
  • an index that leaves the tail undelivered becomes a gap beat in the delivery stream, in order, never a silent skip or a spin;
  • every response carries the (slot, index) it was asked for, and a response for an entry that has since left the tail is dropped;
  • catch-up (R-26, U09/N17): with CatchupIn::enable, an index that would become a gap is first fetched from the other receivers (each answers from its own tail: data plus the broadcaster’s signature). A fetched message enters its normal tail entry and must pass every check a received one does (digest, the broadcaster’s signature, echoes or our register write plus every other receiver’s register), so it can never split receivers. A fetched entry whose signature fails is dropped (the answering receiver lied; the broadcaster is not blamed). Only when no verified copy arrives within CatchupIn::wait cycles does the index become a gap, as before (docs/rhdl_ubft-ctb-catchup.md).

Re-initialization. cfg.init bumps a 32-bit generation that every request carries; a response of another generation is dropped. That fences responses, not memory: a register write issued before init may still land afterwards. It cannot be mistaken for evidence: its signature binds the old session, so in the new session it is either older/other-slot (checked) or fails verification (checked), never a conflict. The caller must still drain the register writer before init (the writer’s own drain contract), so two writes to one register are never in flight at once.

Bounds. T (tail, a power of two, at most 16) and P (payload words, at most 16) are build constants. Indices are 32-bit; an index at or above 0xFFFF_0000 is refused (no wrap).

Modules§

fault
Fault codes (bits of RxState::fault); any one quarantines.

Structs§

CatchupIn
Catch-up settings, sampled every cycle (default: disabled, which is the pre-R-26 behaviour).
DataIn
A data message from the broadcaster, already length-checked by the wire parser (the core checks again).
Delivery
One delivery beat, in index order: message idx, or a gap: indices lo..idx are lost (they left the tail undelivered, or never arrived although later ones did). Beats cover every index exactly once.
DigestIn
Digest of an entry’s data (from the SHA-512 ring, truncated to 32 bytes, as little-endian words: byte j of the digest is byte j % 8 of word j / 8).
DigestReq
Ask the SHA-512 ring for an entry’s digest.
EchoIn
An echo: receiver from saw digest at idx.
EchoOut
Send our echo to every other receiver.
Entry
One tail slot.
FetchAnsIn
An answer to our fetch from receiver from: message idx with the broadcaster’s signature, as from holds it. Untrusted until it passes the checks; taken only from the receiver currently asked.
FetchAnsOut
Our answer to receiver to’s fetch.
FetchReqIn
Receiver from asks us (as a responder) for message idx of this broadcaster; epoch is the asker’s, echoed back.
FetchReqOut
Ask receiver to for message idx. One receiver at a time, rotating on every re-send, so a fast liar cannot starve an honest copy.
ReadAnswerIn
The answer to our read of receiver from’s register for slot, asked on behalf of our entry idx. ok: the read completed (a failed read is retried, never counted). empty: the register was never written.
ReadReq
Read receiver from’s register for slot, for our entry idx.
RxConfig
One-time configuration.
RxIn
Inputs. Every request output is taken when its *_ready is set in the same cycle.
RxOut
Outputs.
RxState
State.
SigIn
The broadcaster’s signature for idx.
VerdictIn
A verification result. conflict: the check of another receiver’s register, else the broadcaster’s signature on our own entry.
VerifyReq
Verify sig under the broadcaster’s key for the CTB context of digest (role 2; see super::ctb_context).
WriteDoneIn
Our register write for (slot, idx) is visible at a majority.
WriteReq
Write our register for slot: (idx, digest, sig).

Constants§

MAX_REPLICAS
Most replicas (receiver bitmasks are 16 bits).

Functions§

empty_entry
A zero entry, for use inside kernels.
idle_delivery
An idle delivery beat, for kernels.
idle_digest_req
An idle digest request, for kernels.
idle_fans
An idle fetch answer, for kernels.
others_mask
Every replica id below n except me and b.
rx_step
One cycle of the receiver.