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
gapbeat 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 withinCatchupIn::waitcycles 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§
- Catchup
In - 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: indiceslo..idxare lost (they left the tail undelivered, or never arrived although later ones did). Beats cover every index exactly once. - Digest
In - Digest of an entry’s data (from the SHA-512 ring, truncated to 32 bytes,
as little-endian words: byte
jof the digest is bytej % 8of wordj / 8). - Digest
Req - Ask the SHA-512 ring for an entry’s digest.
- EchoIn
- An echo: receiver
fromsawdigestatidx. - EchoOut
- Send our echo to every other receiver.
- Entry
- One tail slot.
- Fetch
AnsIn - An answer to our fetch from receiver
from: messageidxwith the broadcaster’s signature, asfromholds it. Untrusted until it passes the checks; taken only from the receiver currently asked. - Fetch
AnsOut - Our answer to receiver
to’s fetch. - Fetch
ReqIn - Receiver
fromasks us (as a responder) for messageidxof this broadcaster;epochis the asker’s, echoed back. - Fetch
ReqOut - Ask receiver
tofor messageidx. One receiver at a time, rotating on every re-send, so a fast liar cannot starve an honest copy. - Read
Answer In - The answer to our read of receiver
from’s register forslot, asked on behalf of our entryidx.ok: the read completed (a failed read is retried, never counted).empty: the register was never written. - ReadReq
- Read receiver
from’s register forslot, for our entryidx. - RxConfig
- One-time configuration.
- RxIn
- Inputs. Every request output is taken when its
*_readyis set in the same cycle. - RxOut
- Outputs.
- RxState
- State.
- SigIn
- The broadcaster’s signature for
idx. - Verdict
In - A verification result.
conflict: the check of another receiver’s register, else the broadcaster’s signature on our own entry. - Verify
Req - Verify
sigunder the broadcaster’s key for the CTB context ofdigest(role 2; seesuper::ctb_context). - Write
Done In - Our register write for
(slot, idx)is visible at a majority. - Write
Req - 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
nexceptmeandb. - rx_step
- One cycle of the receiver.