Skip to main content

rhdl_ubft/
protocol.rs

1//! The consensus controller: ubft_new's four machines, fixed and faster.
2//!
3//! rhdl_ubft keeps ubft_new's structure state for state: a LEADER machine
4//! that proposes, a FAST path (two rounds of votes), a SLOW path (signed
5//! certify shares, then commit votes) and a VIEW change. It also keeps the
6//! mailbox layout ([`crate::layout`]) and the `wait` timeout counted in
7//! polls. What changes is how each state does its work, and seven bugs.
8//!
9//! | ubft_new (a7537bd) | rhdl_ubft |
10//! |---|---|
11//! | the fast path sends each vote to one queue pair, then moves on | [`crate::broadcast`]: every replica, nothing dropped |
12//! | the leader never votes, so `2f + 1` of `2f + 1` is unreachable and every slot falls to the slow path | the leader votes like everyone else |
13//! | after its first slow decision the leader returns as a follower; nobody proposes again | the leader returns to LEADER |
14//! | `k = slot + 1` repeats every `G_T` slots; prepares and votes are never cleared, so a reused slot decides stale values | `k` is the slot's sequence number; votes carry `(view, k)` |
15//! | the slow path never broadcasts `certify_valid`, so no peer verifies a share | it does |
16//! | view-change and new-view records are sent without their valid flags | the flags are sent |
17//! | the signed summary is `view<<48 ^ slot<<32 ^ k<<16 ^ m` (collides: `k=1,m=0` and `k=2,m=196608`), and the library hashes one byte of it | `(view, slot, k, m)` as four 32-bit words, all 16 bytes signed |
18//! | a vote is the word `1`: it binds nothing, so an equivocating leader can split the replicas | a vote is `(tag(view, k), m)`; a quorum counts only votes for the replica's own `m` |
19//! | the prepare carries no view, so an old view's proposal is voted on in the next | the prepare is `(k, m, view)` |
20//! | the new leader picks the highest `k` it is told about and everyone installs it | every replica picks the value reported by `f + 1` records (see View change) |
21//! | each mailbox word is its own HBM read | one burst per poll ([`crate::fetch`]) |
22//! | signing and verifying stop the kernel (3.07M and 10.4M cycles) | a service on streams ([`crate::setup`]) |
23//!
24//! # A slot, fast path
25//!
26//! 1. The leader takes the slot's proposal from `input`, RDMA-writes
27//!    `(k, m, view)` into `prepare` on its `N − 1` peers and writes it into
28//!    its own HBM. (Every broadcast below is followed by the same local write: the
29//!    RoCE stack has no connection to the replica itself.)
30//! 2. Every replica polls `prepare` until `k` arrives for this view, then
31//!    writes its `will_cert` vote `(tag(view, k), m)` on every replica.
32//! 3. With `2f + 1` votes for its own `(view, k, m)` it writes `will_commit`.
33//! 4. With `2f + 1` matching `will_commit` votes it decides: `(k, m)` goes to
34//!    `output` and `remoteUpdate`.
35//!
36//! A poll that does not find what it waits for counts toward `wait`. Past
37//! it, the replica falls back to the slow path for the same slot.
38//!
39//! # A slot, slow path
40//!
41//! The replica signs the summary through the service, writes its 64-byte
42//! share and then `certify_valid` on every replica, and verifies each
43//! peer's share as its flag appears, one at a time. With `2f + 1` valid
44//! shares (its own included) it writes `commit`, and with `2f + 1` commits
45//! it decides. A timeout here starts a view change.
46//!
47//! # View change
48//!
49//! The view advances and the leader rotates (`view mod N`). Every replica
50//! posts a record of what it voted for in the slot: `(view, slot | phase
51//! << 16, k, m)`, phase 0 nothing, 1 `will_cert`, 2 `will_commit` or
52//! `commit`. Once it holds `2f + 1` records for the view, each replica reads
53//! them all and decides for itself: the `(k, m)` reported by at least
54//! `f + 1` records with phase ≥ 1, the highest such `k`. It installs that in
55//! its own `prepare` and runs the slot again on the fast path. If no value
56//! has `f + 1` reports, none can have been decided, and the new leader
57//! proposes from its queue.
58//!
59//! **Status: a value-binding prototype, not uBFT's recovery.** The
60//! original uBFT (LPD-EPFL/ubft `504eba0`) recovers through certified
61//! commit histories: every fast commit is turned into a CB-broadcast slow
62//! commit before a view is sealed, and the new view takes the highest
63//! commit view per instance from quorum-certified histories
64//! (docs/rhdl_ubft-original-baseline.md). The rule here uses each replica's
65//! latest uncertified self-report instead. Atlas's counterexample shows two
66//! honest replicas can choose different candidates when a faulty one writes
67//! different records to each, and an all-`n` quorum loses liveness to one
68//! silent replica. The original fast-to-slow bridge, tail-CB and certified
69//! histories are scheduled to replace it.
70//!
71//! The argument this prototype was built on, for uBFT's `n = 2f + 1`: a decision needs `2f + 1 = n`
72//! matching votes, so every replica voted for the decided `(k, m)`, and the
73//! `f + 1` honest ones report it. At most `f` lying records cannot reach
74//! `f + 1` for another value. No replica trusts the leader's choice.
75//!
76//! # Trust model
77//!
78//! - A replica writes only its own vote and record slots in a peer's
79//!   mailbox. This is the disaggregated-memory assumption uBFT rests on. It
80//!   must be enforced on the receive side, per sending queue pair. **Not
81//!   enforced yet:** DSig's on-chip fabric checks only the destination
82//!   region, and a per-sender slot guard is pending (Atlas's proposal).
83//!   Until it lands, every claim below assumes it.
84//! - Slow-path shares are signed. The other mailboxes are protected by the
85//!   point above.
86//! - The argument above covers odd `n` (uBFT's `2f + 1`). For even `n` the
87//!   quorums overlap less, and this page claims nothing.
88//! - Liveness needs the replicas to be correct and the network to deliver
89//!   within `wait` polls. A Byzantine replica can stall progress (it can
90//!   always refuse to vote, since the quorum is everyone), but under the
91//!   model above it cannot make two replicas decide differently.
92
93use rhdl::prelude::*;
94
95use crate::broadcast::{BroadcastOut, BroadcastReq};
96use crate::control::{Args, Status};
97use crate::fetch::{FetchOut, FetchReq};
98use crate::ports::{Axis64, Axis256, Axis512, idle256, idle512};
99use crate::share::{ShareOut, ShareReq};
100use crate::fetch::idle_fetch_req;
101use crate::writer::idle_write_req;
102use crate::broadcast::idle_bcast_req;
103use crate::writer::{WriteOut, WriteReq};
104
105/// Submachines (ubft_new's `SubMachine`).
106pub mod sub {
107    /// Proposing.
108    pub const LEADER: u128 = 3;
109    /// Fast path.
110    pub const FAST: u128 = 0;
111    /// Slow path.
112    pub const SLOW: u128 = 1;
113    /// View change.
114    pub const VIEW: u128 = 2;
115}
116
117/// Controller states. The names follow ubft_new's (`FAST_WAIT_PREPARE`
118/// and so on); a `POLL_*` state starts a fetch and its `WAIT_*` state reads
119/// the result.
120pub mod st {
121    /// Stopped.
122    pub const IDLE: u128 = 0;
123    /// Computing the mailbox bases.
124    pub const BASES: u128 = 1;
125    /// LEADER_INIT.
126    pub const L_INIT: u128 = 8;
127    /// LEADER_PREPARE: waiting for the slot's proposal.
128    pub const L_WAIT_PROP: u128 = 9;
129    /// Sending the prepare.
130    pub const L_BCAST: u128 = 10;
131    /// FAST_INIT.
132    pub const F_INIT: u128 = 16;
133    /// FAST_WAIT_PREPARE.
134    pub const F_POLL_PREP: u128 = 17;
135    /// Reading the prepare.
136    pub const F_WAIT_PREP: u128 = 18;
137    /// FAST_BCAST_WILL_CERTIFY.
138    pub const F_BC_WC: u128 = 19;
139    /// FAST_WAIT_WILL_CERTIFY_QUORUM.
140    pub const F_POLL_WC: u128 = 20;
141    /// Counting will_cert votes.
142    pub const F_WAIT_WC: u128 = 21;
143    /// FAST_BCAST_WILL_COMMIT.
144    pub const F_BC_WM: u128 = 22;
145    /// FAST_WAIT_WILL_COMMIT_QUORUM.
146    pub const F_POLL_WM: u128 = 23;
147    /// Counting will_commit votes.
148    pub const F_WAIT_WM: u128 = 24;
149    /// FAST_DECIDE / SLOW_DECIDE.
150    pub const DECIDE: u128 = 25;
151    /// SLOW_INIT.
152    pub const S_INIT: u128 = 32;
153    /// SLOW_WAIT_PREPARE.
154    pub const S_POLL_PREP: u128 = 33;
155    /// Reading the prepare.
156    pub const S_WAIT_PREP: u128 = 34;
157    /// SLOW_BCAST_CERTIFY: sign request.
158    pub const S_SIGN: u128 = 35;
159    /// Waiting for the signature.
160    pub const S_SIG_WAIT: u128 = 36;
161    /// Sending the share.
162    pub const S_BC_SIG: u128 = 37;
163    /// Sending certify_valid.
164    pub const S_BC_VALID: u128 = 38;
165    /// SLOW_WAIT_CERTIFY_QUORUM: polling the flags.
166    pub const S_POLL_VALID: u128 = 39;
167    /// Reading the flags.
168    pub const S_WAIT_VALID: u128 = 40;
169    /// Verify request: header and message beats.
170    pub const S_VERIFY: u128 = 41;
171    /// Verify request: the share, streamed from HBM.
172    pub const S_VER_SHARE: u128 = 42;
173    /// Waiting for the verdict.
174    pub const S_VERDICT: u128 = 44;
175    /// SLOW_BCAST_COMMIT.
176    pub const S_BC_COMMIT: u128 = 45;
177    /// SLOW_WAIT_COMMIT_QUORUM.
178    pub const S_POLL_COMMIT: u128 = 46;
179    /// Counting commits.
180    pub const S_WAIT_COMMIT: u128 = 47;
181    /// VIEW_INIT.
182    pub const V_INIT: u128 = 48;
183    /// VIEW_BCAST_VC: our record.
184    pub const V_BC_VC: u128 = 51;
185    /// Its valid flag.
186    pub const V_BC_VCV: u128 = 52;
187    /// VIEW_WAIT_VC_QUORUM.
188    pub const V_POLL_VCV: u128 = 53;
189    /// Counting records.
190    pub const V_WAIT_VCV: u128 = 54;
191    /// Reading record `i`.
192    pub const V_POLL_REC: u128 = 55;
193    /// Keeping it.
194    pub const V_WAIT_REC: u128 = 56;
195    /// Choosing the value (`f + 1` reports).
196    pub const V_CHOOSE: u128 = 57;
197    /// VIEW_INSTALL: writing `prepare` locally.
198    pub const V_INSTALL: u128 = 63;
199}
200
201/// Inputs of the controller.
202#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
203pub struct ProtocolIn {
204    /// Start a run (one-cycle pulse; `args` valid).
205    pub start: bool,
206    /// The run's arguments.
207    pub args: Args,
208    /// The service has its keys and has been started.
209    pub setup_done: bool,
210    /// The mailbox fetcher.
211    pub fetch: FetchOut,
212    /// The broadcaster.
213    pub bcast: BroadcastOut,
214    /// The local writer.
215    pub write: WriteOut,
216    /// The share reader.
217    pub share: ShareOut,
218    /// Proposals (`input`).
219    pub input: Axis64,
220    /// `output` `tready`.
221    pub output_ready: bool,
222    /// `remoteUpdate` `tready`.
223    pub remote_ready: bool,
224    /// Signature service: sign stream `tready`.
225    pub sign_ready: bool,
226    /// Signature beats (two: `R‖S`, then the tag).
227    pub sig: Axis512,
228    /// Verify stream `tready`.
229    pub verify_ready: bool,
230    /// Verdicts.
231    pub verdict: Axis64,
232}
233
234/// Outputs of the controller.
235#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
236pub struct ProtocolOut {
237    /// Fetch request.
238    pub fetch: FetchReq,
239    /// Broadcast request.
240    pub bcast: BroadcastReq,
241    /// Local write request.
242    pub write: WriteReq,
243    /// Share read request.
244    pub share: ShareReq,
245    /// The verify stream takes the reader's beat this cycle.
246    pub share_ready: bool,
247    /// Share RAM write: enable, beat, data.
248    pub ram_we: bool,
249    /// Share RAM write address.
250    pub ram_addr: b5,
251    /// Share RAM write data.
252    pub ram_data: [b64; 8],
253    /// `input` `tready`.
254    pub input_ready: bool,
255    /// `output`.
256    pub output: Axis256,
257    /// `remoteUpdate`.
258    pub remote: Axis256,
259    /// Sign requests.
260    pub sign: Axis512,
261    /// Signature `tready`.
262    pub sig_ready: bool,
263    /// Verify requests.
264    pub verify: Axis512,
265    /// Verdict `tready`.
266    pub verdict_ready: bool,
267    /// For the host.
268    pub status: Status,
269}
270
271/// The controller's registers. `N` is the most replicas the build supports,
272/// `G` the window.
273#[derive(Clone, Copy, Debug, PartialEq, Digital)]
274pub struct ProtocolState<const N: usize, const G: usize> {
275    /// State (one of [`st`]).
276    pub st: b6,
277    /// Submachine (one of [`sub`]), for the host.
278    pub sub: b2,
279    /// The run's arguments.
280    pub args: Args,
281    /// Replicas in this run.
282    pub n: b5,
283    /// This replica.
284    pub id: b5,
285    /// Faults tolerated, `(n − 1) / 2`.
286    pub f: b5,
287    /// Quorum, `2f + 1`.
288    pub quorum: b5,
289    /// First queue pair, `(n − 1) · id` (peer `j` at rank `j` or `j − 1`).
290    pub qbase: b24,
291    /// Mailbox bases (words).
292    pub mb_prepare: b32,
293    /// `will_cert`.
294    pub mb_will_cert: b32,
295    /// `will_commit`.
296    pub mb_will_commit: b32,
297    /// `commit`.
298    pub mb_commit: b32,
299    /// `certify_valid`.
300    pub mb_certify_valid: b32,
301    /// `certify`.
302    pub mb_certify: b32,
303    /// `vc`.
304    pub mb_vc: b32,
305    /// `vc_valid`.
306    pub mb_vc_valid: b32,
307    /// `newview`.
308    pub mb_newview: b32,
309    /// `newview_valid`.
310    pub mb_newview_valid: b32,
311    /// Current view.
312    pub view: b32,
313    /// Its leader.
314    pub leader: b5,
315    /// The run must stop at the end of this cycle (time up, a bound or a
316    /// fault).
317    pub stop: bool,
318    /// The slot's sequence number (`k = seq + 1`).
319    pub seq: b32,
320    /// `seq mod G`.
321    pub slot: b4,
322    /// The slot's value, once its prepare is seen.
323    pub m: b32,
324    /// Polls without progress.
325    pub local_wait: b32,
326    /// This state's broadcast was started.
327    pub issued: bool,
328    /// This state's broadcast is done; its local record is next.
329    pub bc_done: bool,
330    /// This state's local write was started.
331    pub wissued: bool,
332    /// Unused (kept for the register layout).
333    pub second: bool,
334    /// Proposals waiting, one per window slot.
335    pub prop: [b32; G],
336    /// Which window slots hold a proposal.
337    pub prop_valid: [bool; G],
338    /// Window slot the next proposal goes to.
339    pub enq: b4,
340    /// Decision beats sent: `output`, `remoteUpdate`.
341    pub out_sent: bool,
342    /// `remoteUpdate` sent.
343    pub rem_sent: bool,
344    /// This slot took the slow path.
345    pub slowed: bool,
346    /// Signature beats received (share RAM write address).
347    pub share_beat: b5,
348    /// Peers whose share was checked this slot.
349    pub checked: [bool; N],
350    /// Valid shares counted (ours included).
351    pub good: b5,
352    /// The peer being verified.
353    pub peer: b5,
354    /// The share read was started.
355    pub reading: bool,
356    /// Verify beats sent.
357    pub vbeats: b2,
358    /// What this replica voted for in the slot: `k`, `m`, phase (0 none, 1
359    /// will_cert, 2 will_commit or commit).
360    pub voted_k: b32,
361    /// Its `m`.
362    pub voted_m: b32,
363    /// Its phase.
364    pub voted_phase: b2,
365    /// Record being read.
366    pub rec: b5,
367    /// The records: reported `k`.
368    pub rec_k: [b32; N],
369    /// Reported `m`.
370    pub rec_m: [b32; N],
371    /// The record is for this view and slot and reports a vote.
372    pub rec_ok: [bool; N],
373    /// The chosen `(k, m)` (`k` = 0: none).
374    pub nv_k: b32,
375    /// Its `m`.
376    pub nv_m: b32,
377    /// Measurements for the host.
378    pub status: Status,
379    /// Cycle the current slot started.
380    pub slot_start: b32,
381}
382
383impl<const N: usize, const G: usize> ProtocolState<N, G> {
384    /// Power-on: stopped, nothing known.
385    pub fn idle() -> Self {
386        Self {
387            st: b6(0),
388            sub: b2(0),
389            args: Args::default(),
390            n: b5(0),
391            id: b5(0),
392            f: b5(0),
393            quorum: b5(0),
394            qbase: b24(0),
395            mb_prepare: b32(0),
396            mb_will_cert: b32(0),
397            mb_will_commit: b32(0),
398            mb_commit: b32(0),
399            mb_certify_valid: b32(0),
400            mb_certify: b32(0),
401            mb_vc: b32(0),
402            mb_vc_valid: b32(0),
403            mb_newview: b32(0),
404            mb_newview_valid: b32(0),
405            view: b32(0),
406            leader: b5(0),
407            stop: false,
408            seq: b32(0),
409            slot: b4(0),
410            m: b32(0),
411            local_wait: b32(0),
412            issued: false,
413            bc_done: false,
414            wissued: false,
415            second: false,
416            prop: [b32(0); G],
417            prop_valid: [false; G],
418            enq: b4(0),
419            out_sent: false,
420            rem_sent: false,
421            slowed: false,
422            share_beat: b5(0),
423            checked: [false; N],
424            good: b5(0),
425            peer: b5(0),
426            reading: false,
427            vbeats: b2(0),
428            voted_k: b32(0),
429            voted_m: b32(0),
430            voted_phase: b2(0),
431            rec: b5(0),
432            rec_k: [b32(0); N],
433            rec_m: [b32(0); N],
434            rec_ok: [false; N],
435            nv_k: b32(0),
436            nv_m: b32(0),
437            status: Status::default(),
438            slot_start: b32(0),
439        }
440    }
441}
442
443/// The vote word for `(view, k)`: view's low 8 bits over `k`'s low 24.
444/// Never zero for `k ≥ 1`, so a cleared mailbox is never a vote.
445#[kernel]
446pub fn vote_tag(view: b32, k: b32) -> b32 {
447    ((view & b32(0xff)) << b32(24)) | (k & b32(0xff_ffff))
448}
449
450/// A run's measurements before it starts.
451#[kernel]
452pub fn fresh_status() -> Status {
453    Status {
454        finished: false,
455        shape: b32(0),
456        decided: b32(0),
457        fast: b32(0),
458        slow: b32(0),
459        views: b32(0),
460        cycles: b32(0),
461        first: b32(0),
462        last: b32(0),
463        slot_min: b32(0),
464        slot_max: b32(0),
465        slot_sum: b64(0),
466        verified: b32(0),
467        rejected: b32(0),
468        state: b32(0),
469        fault: b32(0),
470    }
471}
472
473/// Votes for `(tag, m)` among the first `n` two-word votes.
474#[kernel]
475pub fn count_votes(words: [b32; 32], n: b5, tag: b32, m: b32) -> b5 {
476    let mut c = b5(0);
477    for i in 0..16 {
478        if b5(i as u128) < n && words[2 * i] == tag && words[2 * i + 1] == m {
479            c = c + b5(1);
480        }
481    }
482    c
483}
484
485/// How many of the first `n` one-word flags equal `tag`.
486#[kernel]
487pub fn count_flags(words: [b32; 32], n: b5, tag: b32) -> b5 {
488    let mut c = b5(0);
489    for i in 0..16 {
490        if b5(i as u128) < n && words[i] == tag {
491            c = c + b5(1);
492        }
493    }
494    c
495}
496
497/// The value reported by at least `f + 1` of the valid records, if any
498/// (at most one can be: `2(f + 1) > n`).
499#[kernel]
500pub fn choose<const N: usize>(m: [b32; N], ok: [bool; N], f: b5) -> (bool, b32) {
501    let mut found = false;
502    let mut pick = b32(0);
503    for a in 0..N {
504        let mut support = b5(0);
505        for b in 0..N {
506            if ok[a] && ok[b] && m[a] == m[b] {
507                support = support + b5(1);
508            }
509        }
510        if !found && support > f {
511            found = true;
512            pick = m[a];
513        }
514    }
515    (found, pick)
516}
517
518/// The controller.
519#[allow(clippy::too_many_lines)]
520#[kernel]
521pub fn protocol_kernel<const N: usize, const G: usize, const SB: usize>(
522    i: ProtocolIn,
523    r: ProtocolState<N, G>,
524) -> (ProtocolOut, ProtocolState<N, G>) {
525    let mut s = r;
526    let mut o = ProtocolOut {
527        fetch: idle_fetch_req(),
528        bcast: idle_bcast_req(),
529        write: idle_write_req(),
530        share: ShareReq { start: false, base: b64(0), word: b32(0), beats: b5(0) },
531        share_ready: false,
532        ram_we: false,
533        ram_addr: b5(0),
534        ram_data: [b64(0); 8],
535        input_ready: false,
536        output: idle256(),
537        remote: idle256(),
538        sign: idle512(),
539        sig_ready: false,
540        verify: idle512(),
541        verdict_ready: false,
542        status: r.status,
543    };
544    let sb32 = b32(SB as u128);
545    let base = r.args.network_ptr;
546    let k = r.seq + b32(1);
547    let tag = vote_tag(r.view, k);
548    let slot32: b32 = r.slot.resize();
549    let n32: b32 = r.n.resize();
550    let id32: b32 = r.id.resize();
551    let running = r.st != b6(st::IDLE);
552
553    // ---------------------------------------------------- the run's clock
554    // (The stop itself is applied last, so no state handler can undo it.)
555    let mut stop = false;
556    let mut fault = r.status.fault;
557    if running {
558        s.status.cycles = r.status.cycles + b32(1);
559        if r.status.cycles + b32(1) >= r.args.execution_cycles {
560            stop = true;
561        }
562        // Vote words carry the view's low 8 bits and k's low 24: stop before
563        // either wraps, so a vote word is unique and nonzero within a run.
564        if r.seq >= b32(0xff_fffe) || r.view >= b32(0xff) {
565            stop = true;
566            fault = fault | b32(2);
567        }
568    }
569    if r.status.finished {
570        s.status.finished = false;
571    }
572
573    // ---------------------------------------------------- proposals in
574    let mut enq_free = false;
575    for g in 0..G {
576        if r.enq == b4(g as u128) && !r.prop_valid[g] {
577            enq_free = true;
578        }
579    }
580    o.input_ready = running && enq_free;
581    if running && enq_free && i.input.valid {
582        for g in 0..G {
583            if r.enq == b4(g as u128) {
584                s.prop[g] = i.input.data.resize();
585                s.prop_valid[g] = true;
586            }
587        }
588        s.enq = if r.enq + b4(1) == b4(G as u128) { b4(0) } else { r.enq + b4(1) };
589    }
590    let mut prop_here = false;
591    let mut prop_value = b32(0);
592    for g in 0..G {
593        if r.slot == b4(g as u128) {
594            prop_here = r.prop_valid[g];
595            prop_value = r.prop[g];
596        }
597    }
598
599    // ---------------------------------------------------- start
600    // (250,191 is ubft_new's UBFT_BASE_PTR, crate::layout::UBFT_BASE_PTR:
601    // kernels take literals.)
602    // Arguments a run cannot use: refuse to start.
603    // uBFT runs n = 2f + 1 replicas. An even n (Codex strict audit: n = 2
604    // made f = 0 and a quorum of one, so both replicas could decide alone)
605    // is refused until majority quorums come with recovery.
606    let even = (i.args.node_num & b32(1)) == b32(0);
607    let bad_args = i.args.node_num == b32(0) || i.args.node_num > b32(N as u128) || i.args.id >= i.args.node_num || even;
608    if r.st == b6(st::IDLE) && i.start && bad_args {
609        s.status = fresh_status();
610        s.status.finished = true;
611        // (Through `fault`, which is what the end of the kernel stores.)
612        fault = b32(1);
613    }
614    if r.st == b6(st::IDLE) && i.start && !bad_args {
615        s.status = fresh_status();
616        fault = b32(0);
617        s.seq = b32(0);
618        s.slot = b4(0);
619        s.view = b32(0);
620        s.enq = b4(0);
621        for g in 0..G {
622            s.prop_valid[g] = false;
623        }
624        s.issued = false;
625        s.bc_done = false;
626        s.wissued = false;
627        s.second = false;
628        s.out_sent = false;
629        s.rem_sent = false;
630        s.args = i.args;
631        s.n = i.args.node_num.resize();
632        s.id = i.args.id.resize();
633        s.st = b6(st::BASES);
634    }
635    if r.st == b6(st::BASES) && i.setup_done {
636        let g32 = b32(G as u128);
637        let gn = g32 * n32;
638        s.f = (r.n - b5(1)) >> b5(1);
639        s.quorum = (((r.n - b5(1)) >> b5(1)) << b5(1)) + b5(1);
640        // Peer j of replica id is local QP (n − 1)·id + rank(j) (no self).
641        s.qbase = (r.id.resize::<24>()) * ((r.n - b5(1)).resize::<24>());
642        // Layout v2 (crate::layout::Mailbox::v2): prepare 4 words per slot,
643        // votes and flags 2 words per (slot, node), certify G·N shares of
644        // 16·SB words, records 4 words per node.
645        let base0 = b32(250_191);
646        s.mb_prepare = base0;
647        s.mb_will_cert = base0 + g32 * b32(4);
648        s.mb_will_commit = base0 + g32 * b32(4) + gn * b32(2);
649        s.mb_commit = base0 + g32 * b32(4) + gn * b32(4);
650        s.mb_certify_valid = base0 + g32 * b32(4) + gn * b32(6);
651        s.mb_certify = base0 + g32 * b32(4) + gn * b32(8);
652        let vc = base0 + g32 * b32(4) + gn * b32(8) + gn * b32(16) * sb32;
653        s.mb_vc = vc;
654        s.mb_vc_valid = vc + n32 * b32(4);
655        s.mb_newview = vc + n32 * b32(5);
656        s.mb_newview_valid = vc + n32 * b32(5) + b32(4);
657        s.leader = b5(0);
658        s.st = if r.id == b5(0) { b6(st::L_INIT) } else { b6(st::F_INIT) };
659        s.sub = if r.id == b5(0) { b2(sub::LEADER) } else { b2(sub::FAST) };
660    }
661
662    // Shared helpers: the slot's offsets.
663    let slot_n = slot32 * n32;
664    let my_vote = slot_n + id32;
665    let peers = r.n - b5(1);
666    let n2: b6 = r.n.resize::<6>() << b6(1);
667
668    // ---------------------------------------------------- LEADER
669    if r.st == b6(st::L_INIT) {
670        s.local_wait = b32(0);
671        s.slowed = false;
672        s.voted_phase = b2(0);
673        s.slot_start = r.status.cycles;
674        s.st = b6(st::L_WAIT_PROP);
675    }
676    if r.st == b6(st::L_WAIT_PROP) && prop_here {
677        s.m = prop_value;
678        s.st = b6(st::L_BCAST);
679    }
680
681    // ---------------------------------------------------- FAST
682    if r.st == b6(st::F_INIT) {
683        s.local_wait = b32(0);
684        s.slowed = false;
685        s.voted_phase = b2(0);
686        s.slot_start = r.status.cycles;
687        s.st = b6(st::F_POLL_PREP);
688    }
689
690    // ---------------------------------------------------- polls
691    // A POLL state starts a fetch; its WAIT state (the next code) reads it.
692    let poll_states = (r.st == b6(st::F_POLL_PREP))
693        || (r.st == b6(st::F_POLL_WC))
694        || (r.st == b6(st::F_POLL_WM))
695        || (r.st == b6(st::S_POLL_PREP))
696        || (r.st == b6(st::S_POLL_VALID))
697        || (r.st == b6(st::S_POLL_COMMIT))
698        || (r.st == b6(st::V_POLL_VCV))
699        || (r.st == b6(st::V_POLL_REC));
700    let mut fword = b32(0);
701    let mut fcount = n2;
702    if r.st == b6(st::F_POLL_PREP) || r.st == b6(st::S_POLL_PREP) {
703        fword = r.mb_prepare + (slot32 << b32(2));
704        fcount = b6(4);
705    }
706    if r.st == b6(st::F_POLL_WC) {
707        fword = r.mb_will_cert + (slot_n << b32(1));
708    }
709    if r.st == b6(st::F_POLL_WM) {
710        fword = r.mb_will_commit + (slot_n << b32(1));
711    }
712    if r.st == b6(st::S_POLL_VALID) {
713        fword = r.mb_certify_valid + (slot_n << b32(1));
714    }
715    if r.st == b6(st::S_POLL_COMMIT) {
716        fword = r.mb_commit + (slot_n << b32(1));
717    }
718    if r.st == b6(st::V_POLL_VCV) {
719        fword = r.mb_vc_valid;
720        fcount = r.n.resize();
721    }
722    if r.st == b6(st::V_POLL_REC) {
723        fword = r.mb_vc + (r.rec.resize::<32>() << b32(2));
724        fcount = b6(4);
725    }
726    if poll_states && !i.fetch.busy {
727        o.fetch = FetchReq { start: true, base: base, word: fword, count: fcount };
728        s.st = r.st + b6(1);
729    }
730    let w = i.fetch.words;
731    let got = i.fetch.done;
732    // A poll that found nothing counts toward `wait`.
733    let timed_out = r.local_wait + b32(1) > r.args.wait;
734    // The prepare for this slot and view.
735    let prepared = w[0] == k && w[2] == r.view;
736
737    // ---------------------------------------------------- broadcasts
738    // Every broadcast state: send to the n − 1 peers, then record the same
739    // words in this replica's own HBM (the share excepted: our own share
740    // counts without being read back).
741    let mut is_bc = false;
742    let mut bword = b32(0);
743    let mut blen = b16(8);
744    let mut bpay = [b64(0); 8];
745    let mut bram = false;
746    let mut bbeats = b5(1);
747    let mut lwords = b3(2);
748    let mut bnext = r.st;
749    // A two-word vote (tag, m).
750    let vote = tag.resize::<64>() | (r.m.resize::<64>() << b64(32));
751    if r.st == b6(st::L_BCAST) {
752        // Fix: ubft_new's leader writes prepare, then never votes.
753        is_bc = true;
754        bword = r.mb_prepare + (slot32 << b32(2));
755        blen = b16(16);
756        bpay[0] = k.resize::<64>() | (r.m.resize::<64>() << b64(32));
757        bpay[1] = r.view.resize();
758        lwords = b3(4);
759        bnext = b6(st::F_POLL_PREP);
760    }
761    if r.st == b6(st::F_BC_WC) || r.st == b6(st::F_BC_WM) || r.st == b6(st::S_BC_VALID) || r.st == b6(st::S_BC_COMMIT) {
762        is_bc = true;
763        bpay[0] = vote;
764    }
765    if r.st == b6(st::F_BC_WC) {
766        bword = r.mb_will_cert + (my_vote << b32(1));
767        bnext = b6(st::F_POLL_WC);
768    }
769    if r.st == b6(st::F_BC_WM) {
770        bword = r.mb_will_commit + (my_vote << b32(1));
771        bnext = b6(st::F_POLL_WM);
772    }
773    if r.st == b6(st::S_BC_VALID) {
774        // Fix: ubft_new's slow path never sends this flag.
775        bword = r.mb_certify_valid + (my_vote << b32(1));
776        bnext = b6(st::S_POLL_VALID);
777    }
778    if r.st == b6(st::S_BC_COMMIT) {
779        bword = r.mb_commit + (my_vote << b32(1));
780        bnext = b6(st::S_POLL_COMMIT);
781    }
782    if r.st == b6(st::S_BC_SIG) {
783        // The share as one packet of SB beats (ubft_new: eight packets).
784        is_bc = true;
785        bword = r.mb_certify + ((my_vote * sb32) << b32(4));
786        blen = (sb32 << b32(6)).resize();
787        bram = true;
788        bbeats = b5(SB as u128);
789        lwords = b3(0);
790        bnext = b6(st::S_BC_VALID);
791    }
792    let vtag = vote_tag(r.view, b32(0xff_ffff));
793    if r.st == b6(st::V_BC_VC) {
794        // What we voted for in this slot.
795        is_bc = true;
796        bword = r.mb_vc + (id32 << b32(2));
797        blen = b16(16);
798        let slot_phase = slot32 | (r.voted_phase.resize::<32>() << b32(16));
799        bpay[0] = r.view.resize::<64>() | (slot_phase.resize::<64>() << b64(32));
800        bpay[1] = r.voted_k.resize::<64>() | (r.voted_m.resize::<64>() << b64(32));
801        lwords = b3(4);
802        bnext = b6(st::V_BC_VCV);
803    }
804    if r.st == b6(st::V_BC_VCV) {
805        // Fix: ubft_new sends the record but not its flag.
806        is_bc = true;
807        bword = r.mb_vc_valid + id32;
808        blen = b16(4);
809        bpay[0] = vtag.resize();
810        lwords = b3(1);
811        bnext = b6(st::V_POLL_VCV);
812    }
813    if is_bc {
814        let no_peers = peers == b5(0);
815        o.bcast = BroadcastReq {
816            start: !r.issued && !r.bc_done && !no_peers,
817            qbase: r.qbase,
818            n: peers,
819            word: bword,
820            len: blen,
821            payload: bpay,
822            beats: bbeats,
823            from_ram: bram,
824        };
825        if !r.issued && !r.bc_done && !no_peers && !i.bcast.busy {
826            s.issued = true;
827        }
828        if (r.issued && i.bcast.done) || (no_peers && !r.bc_done) {
829            s.issued = false;
830            s.bc_done = true;
831        }
832        if r.bc_done {
833            let mut vals = [b32(0); 4];
834            for j in 0..4 {
835                let lane = bpay[j >> 1];
836                vals[j] = if (j & 1) == 1 { (lane >> b64(32)).resize() } else { lane.resize() };
837            }
838            let finish = lwords == b3(0) || (r.wissued && i.write.done);
839            o.write = WriteReq { start: lwords != b3(0) && !r.wissued, base: base, word: bword, values: vals, count: lwords };
840            if lwords != b3(0) && !r.wissued && !i.write.busy {
841                s.wissued = true;
842            }
843            if finish {
844                s.wissued = false;
845                s.bc_done = false;
846                s.local_wait = b32(0);
847                s.st = bnext;
848                if r.st == b6(st::L_BCAST) {
849                    s.sub = b2(sub::FAST);
850                }
851                // Remember what we voted for (the view-change record).
852                if r.st == b6(st::F_BC_WC) {
853                    s.voted_k = k;
854                    s.voted_m = r.m;
855                    if r.voted_phase == b2(0) {
856                        s.voted_phase = b2(1);
857                    }
858                }
859                if r.st == b6(st::F_BC_WM) || r.st == b6(st::S_BC_COMMIT) {
860                    s.voted_k = k;
861                    s.voted_m = r.m;
862                    s.voted_phase = b2(2);
863                }
864            }
865        }
866    }
867
868    // ---------------------------------------------------- FAST results
869    if r.st == b6(st::F_WAIT_PREP) && got {
870        if prepared {
871            s.m = w[1];
872            s.local_wait = b32(0);
873            s.st = b6(st::F_BC_WC);
874        } else {
875            s.local_wait = r.local_wait + b32(1);
876            s.st = if timed_out { b6(st::S_INIT) } else { b6(st::F_POLL_PREP) };
877        }
878    }
879    let votes = count_votes(w, r.n, tag, r.m);
880    if r.st == b6(st::F_WAIT_WC) && got {
881        if votes >= r.quorum {
882            s.st = b6(st::F_BC_WM);
883        } else {
884            s.local_wait = r.local_wait + b32(1);
885            s.st = if timed_out { b6(st::S_INIT) } else { b6(st::F_POLL_WC) };
886        }
887    }
888    if r.st == b6(st::F_WAIT_WM) && got {
889        if votes >= r.quorum {
890            s.st = b6(st::DECIDE);
891        } else {
892            s.local_wait = r.local_wait + b32(1);
893            s.st = if timed_out { b6(st::S_INIT) } else { b6(st::F_POLL_WM) };
894        }
895    }
896
897    // ---------------------------------------------------- DECIDE
898    if r.st == b6(st::DECIDE) {
899        let mut d = [b64(0); 4];
900        d[0] = r.m.resize::<64>() | (k.resize::<64>() << b64(32));
901        o.output = Axis256 { valid: !r.out_sent, data: d, last: true };
902        o.remote = Axis256 { valid: !r.rem_sent, data: d, last: true };
903        let out_ok = r.out_sent || i.output_ready;
904        let rem_ok = r.rem_sent || i.remote_ready;
905        s.out_sent = out_ok;
906        s.rem_sent = rem_ok;
907        if out_ok && rem_ok {
908            s.out_sent = false;
909            s.rem_sent = false;
910            s.seq = r.seq + b32(1);
911            s.slot = if r.slot + b4(1) == b4(G as u128) { b4(0) } else { r.slot + b4(1) };
912            let took = r.status.cycles - r.slot_start;
913            s.status.decided = r.status.decided + b32(1);
914            if r.slowed {
915                s.status.slow = r.status.slow + b32(1);
916            } else {
917                s.status.fast = r.status.fast + b32(1);
918            }
919            if r.status.decided == b32(0) {
920                s.status.first = r.status.cycles;
921                s.status.slot_min = took;
922            }
923            s.status.last = r.status.cycles;
924            if r.status.decided != b32(0) && took < r.status.slot_min {
925                s.status.slot_min = took;
926            }
927            if took > r.status.slot_max {
928                s.status.slot_max = took;
929            }
930            s.status.slot_sum = r.status.slot_sum + took.resize::<64>();
931            // The leader's proposal is spent only once its slot is decided
932            // (a view change may need it again).
933            for g in 0..G {
934                if r.slot == b4(g as u128) && r.id == r.leader {
935                    s.prop_valid[g] = false;
936                }
937            }
938            // Fix: the leader goes back to proposing.
939            let lead = r.id == r.leader;
940            s.st = if lead { b6(st::L_INIT) } else { b6(st::F_INIT) };
941            s.sub = if lead { b2(sub::LEADER) } else { b2(sub::FAST) };
942        }
943    }
944
945    // ---------------------------------------------------- SLOW
946    if r.st == b6(st::S_INIT) {
947        s.local_wait = b32(0);
948        s.slowed = true;
949        s.sub = b2(sub::SLOW);
950        s.good = b5(1);
951        s.share_beat = b5(0);
952        for p in 0..N {
953            s.checked[p] = false;
954        }
955        s.st = b6(st::S_POLL_PREP);
956    }
957    if r.st == b6(st::S_WAIT_PREP) && got {
958        if prepared {
959            s.m = w[1];
960            s.local_wait = b32(0);
961            s.st = b6(st::S_SIGN);
962        } else {
963            s.local_wait = r.local_wait + b32(1);
964            s.st = if timed_out { b6(st::V_INIT) } else { b6(st::S_POLL_PREP) };
965        }
966    }
967    // The signed message, sign layout v2 (48 bytes; fix: ubft_new XORs its
968    // fields into 64 bits and signs one byte): "RUB2", the role (1: certify
969    // share), the session, then view, slot, k, m, and 16 reserved bytes. A
970    // share from another run, role or slot does not verify.
971    let mut summary = [b64(0); 8];
972    summary[0] = b64(0x3242_5552) | (b64(1) << b64(32));
973    summary[1] = r.args.session;
974    summary[2] = r.view.resize::<64>() | (slot32.resize::<64>() << b64(32));
975    summary[3] = k.resize::<64>() | (r.m.resize::<64>() << b64(32));
976    if r.st == b6(st::S_SIGN) {
977        // Bytes 0..8: the tag (the sequence number); 8..56: the message.
978        let mut beat = [b64(0); 8];
979        beat[0] = r.seq.resize();
980        for j in 0..6 {
981            beat[j + 1] = summary[j];
982        }
983        o.sign = Axis512 { valid: true, data: beat, keep: !b64(0), last: true };
984        if i.sign_ready {
985            s.st = b6(st::S_SIG_WAIT);
986        }
987    }
988    if r.st == b6(st::S_SIG_WAIT) {
989        // SB share beats into the share RAM, then the tag beat.
990        o.sig_ready = true;
991        if i.sig.valid {
992            // A share beat past the SB-th is a framing fault at once (the
993            // 5-bit counter must never wrap back into a valid length).
994            let extra = !i.sig.last && r.share_beat >= b5(SB as u128);
995            o.ram_we = !i.sig.last && !extra;
996            o.ram_addr = r.share_beat;
997            o.ram_data = i.sig.data;
998            if !i.sig.last && !extra {
999                s.share_beat = r.share_beat + b5(1);
1000            }
1001            if extra {
1002                s.share_beat = b5(0);
1003                fault = fault | b32(4);
1004                stop = true;
1005            }
1006            if i.sig.last {
1007                s.share_beat = b5(0);
1008                // Exactly SB share beats, then our tag (the sequence number).
1009                let length_ok = r.share_beat == b5(SB as u128);
1010                let tag_ok = i.sig.data[0] == r.seq.resize::<64>();
1011                if length_ok && tag_ok {
1012                    s.st = b6(st::S_BC_SIG);
1013                } else {
1014                    fault = fault | b32(4);
1015                    stop = true;
1016                }
1017            }
1018        }
1019    }
1020    if r.st == b6(st::S_WAIT_VALID) && got {
1021        // The lowest peer whose flag (tag, m) is up and whose share is
1022        // unchecked.
1023        let mut found = false;
1024        let mut pick = b5(0);
1025        for p in 0..16 {
1026            let mut unchecked = false;
1027            for q in 0..N {
1028                if p == q {
1029                    unchecked = !r.checked[q];
1030                }
1031            }
1032            let flag = w[2 * p] == tag && w[2 * p + 1] == r.m;
1033            if !found && b5(p as u128) < r.n && b5(p as u128) != r.id && flag && unchecked {
1034                found = true;
1035                pick = b5(p as u128);
1036            }
1037        }
1038        if found {
1039            s.peer = pick;
1040            s.vbeats = b2(0);
1041            s.reading = false;
1042            s.st = b6(st::S_VERIFY);
1043        } else if r.good >= r.quorum {
1044            s.st = b6(st::S_BC_COMMIT);
1045        } else {
1046            s.local_wait = r.local_wait + b32(1);
1047            s.st = if timed_out { b6(st::V_INIT) } else { b6(st::S_POLL_VALID) };
1048        }
1049    }
1050    if r.st == b6(st::S_VERIFY) {
1051        // Header (key slot = peer, tag = peer), then the message as signed.
1052        let mut hdr = [b64(0); 8];
1053        hdr[0] = r.peer.resize::<64>() | (r.peer.resize::<64>() << b64(32));
1054        let beat = if r.vbeats == b2(0) { hdr } else { summary };
1055        o.verify = Axis512 { valid: true, data: beat, keep: !b64(0), last: false };
1056        if i.verify_ready {
1057            s.vbeats = r.vbeats + b2(1);
1058            if r.vbeats == b2(1) {
1059                s.st = b6(st::S_VER_SHARE);
1060            }
1061        }
1062    }
1063    if r.st == b6(st::S_VER_SHARE) {
1064        // The peer's share streams from HBM into the verifier.
1065        let word = r.mb_certify + (((slot_n + r.peer.resize::<32>()) * sb32) << b32(4));
1066        o.share = ShareReq { start: !r.reading, base: base, word: word, beats: b5(SB as u128) };
1067        if !r.reading && !i.share.busy {
1068            s.reading = true;
1069        }
1070        o.verify = i.share.out;
1071        o.share_ready = r.reading && i.verify_ready;
1072        if r.reading && i.share.out.valid && i.verify_ready && i.share.out.last {
1073            s.reading = false;
1074            s.st = b6(st::S_VERDICT);
1075        }
1076    }
1077    if r.st == b6(st::S_VERDICT) {
1078        o.verdict_ready = true;
1079        if i.verdict.valid {
1080            // The verdict must be for the peer asked about (slot and tag).
1081            let slot_ok = ((i.verdict.data >> b64(8)) & b64(0xf)) == (r.peer.resize::<64>() & b64(0xf));
1082            let tag_ok = (i.verdict.data >> b64(32)) == r.peer.resize::<64>();
1083            if !(slot_ok && tag_ok) {
1084                fault = fault | b32(8);
1085            }
1086            let ok = slot_ok && tag_ok && (i.verdict.data & b64(0xff)) == b64(0x80);
1087            for q in 0..N {
1088                if r.peer == b5(q as u128) {
1089                    s.checked[q] = true;
1090                }
1091            }
1092            if ok {
1093                s.good = r.good + b5(1);
1094                s.status.verified = r.status.verified + b32(1);
1095            } else {
1096                s.status.rejected = r.status.rejected + b32(1);
1097            }
1098            s.st = b6(st::S_POLL_VALID);
1099        }
1100    }
1101    if r.st == b6(st::S_WAIT_COMMIT) && got {
1102        if votes >= r.quorum {
1103            s.st = b6(st::DECIDE);
1104        } else {
1105            s.local_wait = r.local_wait + b32(1);
1106            s.st = if timed_out { b6(st::V_INIT) } else { b6(st::S_POLL_COMMIT) };
1107        }
1108    }
1109
1110    // ---------------------------------------------------- VIEW
1111    if r.st == b6(st::V_INIT) {
1112        s.view = r.view + b32(1);
1113        // view mod n: the leader rotates by one per view.
1114        let mut l = r.leader + b5(1);
1115        if l == r.n {
1116            l = b5(0);
1117        }
1118        s.leader = l;
1119        s.status.views = r.status.views + b32(1);
1120        s.sub = b2(sub::VIEW);
1121        s.local_wait = b32(0);
1122        s.st = b6(st::V_BC_VC);
1123    }
1124    if r.st == b6(st::V_WAIT_VCV) && got {
1125        if count_flags(w, r.n, vtag) >= r.quorum {
1126            s.rec = b5(0);
1127            for q in 0..N {
1128                s.rec_ok[q] = false;
1129            }
1130            s.st = b6(st::V_POLL_REC);
1131        } else {
1132            s.local_wait = r.local_wait + b32(1);
1133            // Fix: a stalled view change moves on to the next view.
1134            s.st = if timed_out { b6(st::V_INIT) } else { b6(st::V_POLL_VCV) };
1135        }
1136    }
1137    if r.st == b6(st::V_WAIT_REC) && got {
1138        // A record counts if it is for this view and slot and reports a vote.
1139        let phase = (w[1] >> b32(16)) & b32(3);
1140        let ok = w[0] == r.view && (w[1] & b32(0xffff)) == slot32 && phase != b32(0) && w[2] == k;
1141        for q in 0..N {
1142            if r.rec == b5(q as u128) {
1143                s.rec_k[q] = w[2];
1144                s.rec_m[q] = w[3];
1145                s.rec_ok[q] = ok;
1146            }
1147        }
1148        s.rec = r.rec + b5(1);
1149        s.st = if r.rec + b5(1) == r.n { b6(st::V_CHOOSE) } else { b6(st::V_POLL_REC) };
1150    }
1151    if r.st == b6(st::V_CHOOSE) {
1152        // The m reported by at least f + 1 records for this slot's k.
1153        let (found, m) = choose::<N>(r.rec_m, r.rec_ok, r.f);
1154        s.nv_k = if found { k } else { b32(0) };
1155        s.nv_m = m;
1156        s.st = b6(st::V_INSTALL);
1157    }
1158    if r.st == b6(st::V_INSTALL) {
1159        // A chosen value goes into our own prepare for the new view; with
1160        // none, the new leader proposes from its queue.
1161        let install = r.nv_k != b32(0);
1162        let mut vals = [b32(0); 4];
1163        vals[0] = r.nv_k;
1164        vals[1] = r.nv_m;
1165        vals[2] = r.view;
1166        o.write = WriteReq { start: install && !r.wissued, base: base, word: r.mb_prepare + (slot32 << b32(2)), values: vals, count: b3(4) };
1167        if install && !r.wissued && !i.write.busy {
1168            s.wissued = true;
1169        }
1170        if !install || (r.wissued && i.write.done) {
1171            s.wissued = false;
1172            let lead = r.id == r.leader;
1173            s.local_wait = b32(0);
1174            // Keep what we voted for: if this view also changes before we
1175            // vote again, our record must still report it.
1176            if lead && !install {
1177                s.st = b6(st::L_INIT);
1178                s.sub = b2(sub::LEADER);
1179            } else {
1180                // Run the slot again on the fast path, in the new view.
1181                s.st = b6(st::F_POLL_PREP);
1182                s.sub = b2(sub::FAST);
1183            }
1184        }
1185    }
1186
1187    s.status.fault = fault;
1188    if stop && running {
1189        s.st = b6(st::IDLE);
1190        s.status.finished = true;
1191    }
1192    s.status.shape = b32(N as u128) | (b32(G as u128) << b32(8)) | (b32(SB as u128) << b32(24));
1193    s.status.state = r.sub.resize::<32>() | (r.st.resize::<32>() << b32(8)) | (r.view << b32(16));
1194    (o, s)
1195}