Skip to main content

commonware_consensus/simplex/
mod.rs

1//! Simple and fast BFT agreement inspired by Simplex Consensus.
2//!
3//! Inspired by [Simplex Consensus](https://eprint.iacr.org/2023/463), `simplex` provides simple and fast BFT
4//! agreement with network-speed view (i.e. block time) latency and optimal finalization latency in a
5//! partially synchronous setting.
6//!
7//! # Features
8//!
9//! * Wicked Fast Block Times (2 Network Hops)
10//! * Optimal Finalization Latency (3 Network Hops)
11//! * Externalized Uptime and Fault Proofs
12//! * Require Certification Before Finalization
13//! * Decoupled Block Broadcast and Sync
14//! * Lazy Message Verification
15//! * Application-Defined Block Format
16//! * Pluggable Hashing and Cryptography
17//! * Embedded VRF (via [scheme::bls12381_threshold::vrf])
18//!
19//! # Design
20//!
21//! ## Protocol Description
22//!
23//! ### Genesis
24//!
25//! Genesis (view 0) is implicitly finalized. There is no finalization certificate for genesis;
26//! [`Config::floor`](config::Config::floor) supplies the initial finalized state. Voting begins
27//! at view 1, with the first proposal referencing genesis as its parent.
28//!
29//! ### Specification for View `v`
30//!
31//! Upon entering view `v`:
32//! * Determine leader `l` for view `v`
33//! * Set timer for leader proposal `t_l = 2Δ` and advance `t_a = 3Δ`
34//!     * If leader `l` has not been active in last `r` views, set `t_l` to 0.
35//! * If leader `l`, broadcast `notarize(c,v)`
36//!   * If can't propose container in view `v` because missing notarization/nullification for a
37//!     previous view `v_m`, request `v_m`
38//!
39//! Upon receiving first `notarize(c,v)` from `l`:
40//! * Cancel `t_l`
41//! * If the container's parent `c_parent` is finalized (or both notarized and certified) at `v_parent`
42//!   and we have nullifications for all views between `v` and `v_parent`, verify `c` and broadcast `notarize(c,v)`
43//!     * If verification of `c` fails, immediately broadcast `nullify(v)`
44//!
45//! Upon receiving `2f+1` `notarize(c,v)`:
46//! * Cancel `t_a`
47//! * Mark `c` as notarized
48//! * Broadcast `notarization(c,v)` (even if we have not verified `c`)
49//! * Attempt to certify `c` (see [Certification](#certification))
50//!     * On success: broadcast `finalize(c,v)` (if have not broadcast `nullify(v)`) and enter `v+1`
51//!     * On failure: broadcast `nullify(v)`
52//!
53//! Upon receiving `2f+1` `nullify(v)`:
54//! * Broadcast `nullification(v)`
55//! * Enter `v+1`
56//!
57//! Upon receiving `2f+1` `finalize(c,v)`:
58//! * Mark `c` as finalized (and recursively finalize its parents)
59//! * Broadcast `finalization(c,v)` (even if we have not verified `c`)
60//!
61//! Upon `t_l` or `t_a` firing:
62//! * Broadcast `nullify(v)`
63//! * Every `t_r` after `nullify(v)` broadcast that we are still in view `v`:
64//!    * Rebroadcast `nullify(v)` and either `notarization(v-1)` or `nullification(v-1)`
65//!
66//! _When `2f+1` votes of a given type (`notarize(c,v)`, `nullify(v)`, or `finalize(c,v)`) have been have been collected
67//! from unique participants, a certificate (`notarization(c,v)`, `nullification(v)`, or `finalization(c,v)`) can be assembled.
68//! These certificates serve as a standalone proof of consensus progress that downstream systems can ingest without executing
69//! the protocol._
70//!
71//! ### Joining Consensus
72//!
73//! As soon as `2f+1` nullifies or finalizes are observed for some view `v`, the `Voter` will
74//! enter `v+1`. Notarizations advance the view if-and-only-if the application certifies them.
75//! This means that a new participant joining consensus will immediately jump ahead on the previous
76//! view's nullification or finalization and begin participating in consensus at the current view.
77//!
78//! ### Certification
79//!
80//! After a payload is notarized, the application can optionally delay or prevent finalization via the
81//! [`CertifiableAutomaton::certify`](crate::CertifiableAutomaton::certify) method. By default, `certify`
82//! returns `true` for all payloads, meaning finalization proceeds immediately after notarization.
83//!
84//! Customizing `certify` is useful for systems that employ erasure coding, where participants may want
85//! to wait until they have received enough shards to reconstruct and validate the full block before
86//! voting to finalize.
87//!
88//! If `certify` returns `true`, the participant broadcasts a `finalize` vote for the payload and enters the
89//! next view. If `certify` returns `false`, the participant broadcasts `nullify` for the view instead (treating
90//! it as an immediate timeout), and will refuse to build upon the proposal or notarize proposals that build upon it.
91//! Thus, a payload can only be finalized if a quorum of participants certify it.
92//!
93//! Certification of some notarization should only be abandoned once a finalization at the same or higher view is observed.
94//! Until then (say a nullification certificate for a view arrives before certification completes), the application should continue
95//! attempting to complete certification. This increases the likelihood that we can vote on the next honest proposer's block (which
96//! may build on our in-flight certification or the nullification). If we did not do this, it is possible that different parts of
97//! the network (neither with quorum) would refuse to vote on each other's blocks (halting consensus).
98//!
99//! _The decision returned by `certify` must be deterministic and consistent across all honest participants to ensure
100//! liveness._
101//!
102//! ### Deviations from Simplex Consensus
103//!
104//! * Fetch missing notarizations/nullifications as needed rather than assuming each proposal contains
105//!   a set of all notarizations/nullifications for all historical blocks.
106//! * Introduce distinct messages for `notarize` and `nullify` rather than referring to both as a `vote` for
107//!   either a "block" or a "dummy block", respectively.
108//! * Introduce a "leader timeout" to trigger early view transitions for unresponsive leaders.
109//! * Skip "leader timeout" and "certification timeout" if a designated leader hasn't participated in
110//!   some number of views (again to trigger early view transition for an unresponsive leader).
111//! * Introduce message rebroadcast to continue making progress if messages from a given view are dropped (only way
112//!   to ensure messages are reliably delivered is with a heavyweight reliable broadcast protocol).
113//! * Treat local proposal failure as immediate timeout expiry and broadcast `nullify(v)`.
114//! * Treat local verification failure as immediate timeout expiry and broadcast `nullify(v)`.
115//! * Consider the current leader's `nullify(v)` as immediate timeout expiry and broadcast `nullify(v)`.
116//! * Upon seeing `notarization(c,v)`, instead of moving to the view `v+1` immediately, request certification from
117//!   the application (see [Certification](#certification)). Only move to view `v+1` and broadcast `finalize(c,v)`
118//!   if certification succeeds, otherwise broadcast `nullify(v)` and refuse to build upon `c`.
119//!
120//! ## Protocol Properties
121//!
122//! ### Forced Inclusion (Tail-Forking Resistance)
123//!
124//! A notarized payload in view `v` must appear in the canonical chain if no nullification
125//! certificate exists for `v`. This follows directly from the protocol rules:
126//!
127//! 1. To propose in view `v+k`, the leader must reference a certified parent in some view `v_p`
128//!    and possess nullification certificates for every view between `v_p` and `v+k`.
129//! 2. A nullification certificate for view `v` requires `2f+1` `nullify(v)` votes.
130//! 3. An honest participant only broadcasts `nullify(v)` when a timeout fires (`t_l` or `t_a`)
131//!    or when certification fails.
132//!
133//! Therefore, if view `v` completes without timeout and certification succeeds, no honest
134//! participant has broadcast `nullify(v)`. With at most `f` Byzantine participants, at most `f`
135//! `nullify(v)` votes exist, which is insufficient to form a nullification certificate. Without
136//! that certificate, no future leader can skip view `v`, and the notarized payload must be
137//! included as an ancestor in all subsequent proposals.
138//!
139//! ### Optimistic Finality
140//!
141//! The forced inclusion property provides a weaker but faster form of finality: once a
142//! notarization certificate is observed for view `v` (without any timeout having fired),
143//! the notarized payload can be treated as speculatively final. No future sequence of
144//! proposals can exclude it from the canonical chain.
145//!
146//! This "speculative finality" is available after just 2 network hops (proposal + notarization),
147//! compared to the 3 hops required for full finalization (proposal + notarization + finalization).
148//! A notarized-but-not-yet-finalized payload can only be excluded in two scenarios:
149//! `f+1` or more honest participants timed out, or certification failed. Because
150//! certification is deterministic, it either fails for all honest participants or none,
151//! so a certification failure always produces a nullification. In the common case
152//! (no faults, no timeouts), exclusion cannot happen.
153//!
154//! ### Unchained Finalization
155//!
156//! Finalization does not require consecutive honest views. When a participant certifies
157//! `notarization(c,v)`, it broadcasts `finalize(c,v)` and immediately enters `v+1`,
158//! regardless of what happens in subsequent views. These `finalize(c,v)` votes accumulate
159//! independently of the current view: even if views `v+1` through `v+k` all time out
160//! (producing nullifications), the `finalize(c,v)` votes still count toward the `2f+1`
161//! threshold needed to form `finalization(c,v)`.
162//!
163//! This means a payload notarized in view `v` can be finalized while the network is
164//! in view `v+k` for any `k >= 1`. There is no requirement that a particular view
165//! after `v` succeeds or that any subsequent leader cooperates. As long as `2f+1`
166//! participants eventually certify and broadcast `finalize(c,v)`, the finalization
167//! certificate will form.
168//!
169//! ## Architecture
170//!
171//! All logic is split into four components: the `Batcher`, the `Voter`, the `Resolver`, and the `Application` (provided by the user).
172//! The `Batcher` is responsible for collecting messages from peers and lazily verifying them when a quorum is met. The `Voter`
173//! is responsible for directing participation in the current view. The `Resolver` is responsible for
174//! fetching artifacts from previous views required to verify proposed blocks in the latest view. Lastly, the `Application`
175//! is responsible for proposing new blocks and indicating whether some block is valid.
176//!
177//! To drive great performance, all interactions between `Batcher`, `Voter`, `Resolver`, and `Application` are
178//! non-blocking. This means that, for example, the `Voter` can continue processing messages while the
179//! `Application` verifies a proposed block or the `Resolver` fetches a notarization.
180//!
181//! ```txt
182//!                            +------------+          +++++++++++++++
183//!                            |            +--------->+             +
184//!                            |  Batcher   |          +    Peers    +
185//!                            |            |<---------+             +
186//!                            +-------+----+          +++++++++++++++
187//!                                |   ^
188//!                                |   |
189//!                                |   |
190//!                                |   |
191//!                                v   |
192//! +---------------+           +---------+            +++++++++++++++
193//! |               |<----------+         +----------->+             +
194//! |  Application  |           |  Voter  |            +    Peers    +
195//! |               +---------->|         |<-----------+             +
196//! +---------------+           +--+------+            +++++++++++++++
197//!                                |   ^
198//!                                |   |
199//!                                |   |
200//!                                |   |
201//!                                v   |
202//!                            +-------+----+          +++++++++++++++
203//!                            |            +--------->+             +
204//!                            |  Resolver  |          +    Peers    +
205//!                            |            |<---------+             +
206//!                            +------------+          +++++++++++++++
207//! ```
208//!
209//! ### Batched Verification
210//!
211//! Unlike other consensus constructions that verify all incoming messages received from peers, for schemes
212//! where [`Verifier::is_batchable()`](commonware_cryptography::certificate::Verifier::is_batchable) returns `true`
213//! (such as [scheme::ed25519], [scheme::bls12381_multisig] and [scheme::bls12381_threshold]), `simplex` lazily
214//! verifies messages (only when a quorum is met), enabling efficient batch verification. For schemes where
215//! `is_batchable()` returns `false` (such as [scheme::secp256r1]), signatures are verified eagerly as they
216//! arrive since there is no batching benefit.
217//!
218//! If an invalid signature is detected, the `Batcher` will perform repeated bisections over collected
219//! messages to find the offending message (and block the peer(s) that sent it via [commonware_p2p::Blocker]).
220//!
221//! _If using a p2p implementation that is not authenticated, it is not safe to employ this optimization
222//! as any attacking peer could simply reconnect from a different address. We recommend [commonware_p2p::authenticated]._
223//!
224//! ### Fetching Missing Certificates
225//!
226//! Instead of trying to fetch all possible certificates above the floor, we only attempt to fetch
227//! nullifications for all views from the floor (last certified notarization or finalization) to the current view.
228//! This technique, however, is not sufficient to guarantee progress.
229//!
230//! Consider the case where `f` honest participants have seen a finalization for a given view `v` (and nullifications only
231//! from `v` to the current view `c`) but the remaining `f+1` honest participants have not (they have exclusively seen
232//! nullifications from some view `o < v` to `c`). Neither partition of participants will vote for the other's proposals.
233//!
234//! To ensure progress is eventually made, leaders with nullified proposals directly broadcast the best finalization
235//! certificate they are aware of to ensure all honest participants eventually consider the same proposal ancestry valid.
236//!
237//! _While a more aggressive recovery mechanism could be employed, like requiring all participants to broadcast their highest
238//! finalization certificate after nullification, it would impose significant overhead under normal network
239//! conditions (whereas the approach described incurs no overhead under normal network conditions). Recall, honest participants
240//! already broadcast observed certificates to all other participants in each view (and misaligned participants should only ever
241//! be observed following severe network degradation)._
242//!
243//! ## Pluggable Hashing and Cryptography
244//!
245//! Hashing is abstracted via the [commonware_cryptography::Hasher] trait and cryptography is abstracted via
246//! the [commonware_cryptography::certificate::Scheme] trait, allowing deployments to employ approaches that best match their
247//! requirements (or to provide their own without modifying any consensus logic). The following schemes
248//! are supported out-of-the-box:
249//!
250//! ### [scheme::ed25519]
251//!
252//! [commonware_cryptography::ed25519] signatures are ["High-speed high-security signatures"](https://eprint.iacr.org/2011/368)
253//! with 32 byte public keys and 64 byte signatures. While they are well-supported by commercial HSMs and offer efficient batch
254//! verification, the signatures are not aggregatable (and certificates grow linearly with the quorum size).
255//!
256//! ### [scheme::bls12381_multisig]
257//!
258//! [commonware_cryptography::bls12381] is a ["digital signature scheme with aggregation properties"](https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.txt).
259//! Unlike [commonware_cryptography::ed25519], signatures from multiple participants (say the signers in a certificate) can be aggregated
260//! into a single signature (reducing bandwidth usage per broadcast). That being said, [commonware_cryptography::bls12381] is much slower
261//! to verify than [commonware_cryptography::ed25519] and isn't supported by most HSMs (a standardization effort expired in 2022).
262//!
263//! ### [scheme::secp256r1]
264//!
265//! [commonware_cryptography::secp256r1] signatures use the NIST P-256 elliptic curve (also known as prime256v1), which is widely
266//! supported by commercial HSMs and hardware security modules. Unlike [commonware_cryptography::ed25519], Secp256r1 does not
267//! benefit from batch verification, so signatures are verified individually. Certificates grow linearly with quorum size
268//! (similar to ed25519).
269//!
270//! ### [scheme::bls12381_threshold]
271//!
272//! [scheme::bls12381_threshold] employs threshold cryptography (BLS12-381 threshold signatures with a `2f+1` of `3f+1` quorum)
273//! to generate succinct consensus certificates (verifiable with just the static public key). This scheme requires instantiating
274//! the shared secret via [commonware_cryptography::bls12381::dkg] and resharing whenever participants change.
275//!
276//! Two (non-attributable) variants are provided:
277//!
278//! - [scheme::bls12381_threshold::standard]: Certificates contain only a vote signature.
279//!
280//! - [scheme::bls12381_threshold::vrf]: Certificates contain a vote signature and a view signature (i.e. a seed that can be used
281//!   as a VRF). This variant can be configured for random leader election (via [elector::Random]) and/or incorporate this randomness
282//!   into execution.
283//!
284//! #### Embedded VRF ([scheme::bls12381_threshold::vrf])
285//!
286//! Every `notarize(c,v)` or `nullify(v)` message includes an `attestation(v)` (a partial signature over the view `v`). After `2f+1`
287//! `notarize(c,v)` or `nullify(v)` messages are collected from unique participants, `seed(v)` can be recovered. Because `attestation(v)` is
288//! only over the view `v`, the seed derived for a given view `v` is the same regardless of whether or not a block was notarized in said
289//! view `v`.
290//!
291//! Because the value of `seed(v)` cannot be known prior to message broadcast by any participant (including the leader) in view `v`
292//! and cannot be manipulated by any participant (deterministic for any `2f+1` signers at a given view `v`), it can be used both as a beacon
293//! for leader election (where `seed(v)` determines the leader for `v+1`) and a source of randomness in execution (where `seed(v)`
294//! is used as a seed in `v`).
295//!
296//! #### Succinct Certificates
297//!
298//! All broadcast consensus messages (`notarize(c,v)`, `nullify(v)`, `finalize(c,v)`) contain attestations (partial signatures) for a static
299//! public key (derived from a group polynomial that can be recomputed during reconfiguration using [dkg](commonware_cryptography::bls12381::dkg)).
300//! As soon as `2f+1` messages are collected, a threshold signature over `notarization(c,v)`, `nullification(v)`, and `finalization(c,v)`
301//! can be recovered, respectively. Because the public key is static, any of these certificates can be verified by an external
302//! process without following the consensus instance and/or tracking the current set of participants (as is typically required
303//! to operate a lite client).
304//!
305//! These threshold signatures over `notarization(c,v)`, `nullification(v)`, and `finalization(c,v)` (i.e. the consensus certificates)
306//! can be used to secure interoperability between different consensus instances and user interactions with an infrastructure provider
307//! (where any data served can be proven to derive from some finalized block of some consensus instance with a known static public key).
308//!
309//! ## Persistence
310//!
311//! The `Voter` caches all data required to participate in consensus to avoid any disk reads on
312//! on the critical path. To enable recovery, the `Voter` writes valid messages it receives from
313//! consensus and messages it generates to a write-ahead log (WAL) implemented by [commonware_storage::journal::segmented::variable::Journal].
314//! Before sending a message, any pending `Journal` appends are synced to prevent inadvertent Byzantine
315//! behavior on restart (especially in the case of unclean shutdown). All appends made in the same event
316//! loop iteration are coalesced into a single sync that runs after messages are constructed and before
317//! any are broadcast (even if there is nothing to broadcast). The proposal payload relay is not a
318//! consensus message and is not gated on this sync: to lower view latency, it is requested as soon
319//! as the automaton returns a payload, which is safe because extra payload bytes (unlike votes)
320//! cannot form a conflicting certificate (see [`Plan::Propose`]).
321//!
322//! ## Automaton Failure Semantics
323//!
324//! If a validator is the leader for a view but cannot build a valid payload yet (for example because
325//! it is still syncing), it should decline the [`Automaton::propose`](crate::Automaton::propose)
326//! request by dropping the response channel. Simplex treats this as a missing proposal, broadcasts
327//! `nullify(v)`, and other validators can use the leader-nullify fast path to skip the view.
328//!
329//! Once `propose` returns a payload, the local proposer is committed to that payload for verification
330//! and certification. [`Automaton::verify`](crate::Automaton::verify) and
331//! [`CertifiableAutomaton::certify`](crate::CertifiableAutomaton::certify) are stable verdict APIs,
332//! not backpressure or syncing signals. While missing data may still arrive (and/or a validator cannot
333//! immediately determine if a payload is valid), implementations should keep these requests pending rather
334//! than returning `false` or closing the channel.
335//!
336//! Returning `false` from `verify` means the proposal is permanently invalid and causes a local
337//! nullify. Returning `false` from `certify` means the notarized payload is permanently
338//! uncertifiable for that round and also causes a local nullify. Closing `certify` does not provide
339//! a fast-skip signal and can halt progress because certification requests are not retried during
340//! the same run. The safe way to stop working on certification is to keep the request pending until
341//! Simplex drops it after finalizing the block or a descendant.
342
343pub mod elector;
344pub mod scheme;
345pub mod types;
346
347cfg_if::cfg_if! {
348    if #[cfg(not(target_arch = "wasm32"))] {
349        use crate::types::{Round, View, ViewDelta};
350        use commonware_cryptography::PublicKey;
351        use commonware_p2p::Recipients;
352
353        mod actors;
354        pub mod config;
355        pub use config::{Config, Floor, ForwardingPolicy};
356        mod engine;
357        pub use engine::Engine;
358        mod metrics;
359
360        /// The minimum view we are tracking both in-memory and on-disk.
361        pub(crate) const fn min_active(activity_timeout: ViewDelta, last_finalized: View) -> View {
362            last_finalized.saturating_sub(activity_timeout)
363        }
364
365        /// Whether or not a view is interesting to us. This is a function
366        /// of both `min_active` and whether or not the view is too far
367        /// in the future (based on the view we are currently in).
368        pub(crate) fn interesting(
369            activity_timeout: ViewDelta,
370            last_finalized: View,
371            current: View,
372            pending: View,
373            allow_future: bool,
374        ) -> bool {
375            // If the view is genesis, skip it, genesis doesn't have votes
376            if pending.is_zero() {
377                return false;
378            }
379            if pending < min_active(activity_timeout, last_finalized) {
380                return false;
381            }
382            if !allow_future && pending > current.next() {
383                return false;
384            }
385            true
386        }
387
388        /// Describes how a payload should be broadcast to the network.
389        pub enum Plan<P: PublicKey> {
390            /// Initial broadcast of a newly proposed block to all participants.
391            ///
392            /// Requested before the proposer's notarize vote is durable: a
393            /// proposer that crashes and restarts may emit this plan again
394            /// with a different payload for the same round. Consumers must
395            /// tolerate multiple candidates per round (at most one is ever
396            /// referenced by the proposer's signed votes).
397            Propose {
398                /// The round in which the block was proposed.
399                round: Round,
400            },
401            /// Forward a block to a specific set of peers.
402            ///
403            /// Requested only for a proposal already backed by a certificate.
404            /// Forwarding is best-effort help for lagging peers and advertises
405            /// nothing about the sender's own state, so it needs no durability
406            /// ordering.
407            Forward {
408                /// The round in which the forwarded block was proposed.
409                round: Round,
410                /// The recipients to forward the block to.
411                recipients: Recipients<P>,
412            },
413        }
414    }
415}
416
417#[cfg(any(test, feature = "mocks"))]
418pub mod mocks;
419
420/// Convenience alias for [`N3f1::quorum`].
421#[cfg(test)]
422pub(crate) fn quorum(n: u32) -> u32 {
423    use commonware_utils::{Faults, N3f1};
424
425    N3f1::quorum(n)
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431    use crate::{
432        simplex::{
433            elector::{Config as Elector, Elector as ElectorTrait, Random, RoundRobin},
434            mocks::{
435                scheme as scheme_mocks,
436                twins::{self, Elector as TwinsElector},
437                wrapped,
438            },
439            scheme::{
440                bls12381_multisig,
441                bls12381_threshold::{
442                    standard as bls12381_threshold_std,
443                    vrf::{self as bls12381_threshold_vrf, Seedable},
444                },
445                ed25519, secp256r1, Scheme,
446            },
447            types::{
448                Certificate, Finalization as TFinalization, Finalize as TFinalize,
449                Notarization as TNotarization, Notarize as TNotarize,
450                Nullification as TNullification, Nullify as TNullify, Proposal, Vote,
451            },
452        },
453        types::{Epoch, Participant, Round},
454        Monitor, Viewable,
455    };
456    use commonware_codec::{Decode, DecodeExt, Encode};
457    use commonware_cryptography::{
458        bls12381::primitives::variant::{MinPk, MinSig, Variant},
459        certificate::mocks::Fixture,
460        ed25519::{PrivateKey, PublicKey},
461        sha256::{Digest as Sha256Digest, Digest as D},
462        Hasher as _, Sha256, Signer as _,
463    };
464    use commonware_macros::{select, test_group, test_traced};
465    use commonware_p2p::{
466        simulated::{Config, Link, Network, Oracle, Receiver, Sender, SplitOrigin},
467        utils::mocks::inert_channel,
468        Manager as _, Recipients, Sender as _, TrackedPeers,
469    };
470    use commonware_parallel::{Sequential, Strategy};
471    use commonware_runtime::{
472        buffer::paged::CacheRef, deterministic, telemetry::metrics::count_running_tasks, Clock,
473        IoBuf, Metrics as _, Quota, Runner, Spawner, Strategizer as _, Supervisor as _,
474    };
475    use commonware_utils::{
476        ordered::Set, sync::Mutex, test_rng, Faults, N3f1, NZUsize, TestRng, NZU16,
477    };
478    use engine::Engine;
479    use futures::future::join_all;
480    use rand::{rngs::StdRng, RngExt as _, SeedableRng};
481    use rand_core::CryptoRng;
482    use std::{
483        collections::{BTreeMap, HashMap, HashSet},
484        num::{NonZeroU16, NonZeroU32, NonZeroUsize},
485        sync::Arc,
486        time::Duration,
487    };
488    use tracing::{debug, info, warn};
489    use types::Activity;
490
491    // Invoke `$cb!($($args)*, $suffix, $elector, $fixture)` once per canonical
492    // (elector, scheme) fixture.
493    macro_rules! for_each_fixture {
494        ($cb:ident!($($args:tt)*)) => {
495            $cb!($($args)*, bls12381_threshold_vrf_min_pk, Random, bls12381_threshold_vrf::fixture::<MinPk, _>);
496            $cb!($($args)*, bls12381_threshold_vrf_min_sig, Random, bls12381_threshold_vrf::fixture::<MinSig, _>);
497            $cb!($($args)*, bls12381_threshold_std_min_pk, RoundRobin, bls12381_threshold_std::fixture::<MinPk, _>);
498            $cb!($($args)*, bls12381_threshold_std_min_sig, RoundRobin, bls12381_threshold_std::fixture::<MinSig, _>);
499            $cb!($($args)*, bls12381_multisig_min_pk, RoundRobin, bls12381_multisig::fixture::<MinPk, _>);
500            $cb!($($args)*, bls12381_multisig_min_sig, RoundRobin, bls12381_multisig::fixture::<MinSig, _>);
501            $cb!($($args)*, ed25519, RoundRobin, ed25519::fixture);
502            $cb!($($args)*, secp256r1, RoundRobin, secp256r1::fixture);
503        };
504    }
505
506    // Generate one `#[test_group("slow")] #[test_traced]` test per canonical
507    // (elector, scheme) fixture, named `test_<callee>_<suffix>`. The helper takes
508    // the elector as its third generic parameter.
509    //
510    // Supported forms:
511    //   test_for_all_fixtures!(callee);                  // callee::<_, _, Elector>(fixture)
512    //   test_for_all_fixtures!(callee, arg);             // callee::<_, _, Elector, _>(fixture, arg)
513    //   test_for_all_fixtures!(callee, arg, level = "INFO"); // arg with a trace-level override
514    //   test_for_all_fixtures!(callee, seeds = N);       // loops callee::<_, _, Elector>(seed, fixture)
515    //   test_for_all_fixtures!(callee, level = "INFO");  // overrides the trace level
516    macro_rules! test_for_all_fixtures {
517        ($callee:ident) => {
518            for_each_fixture!(test_for_all_fixtures!(@emit [test_traced] $callee [] []));
519        };
520        ($callee:ident, level = $level:literal) => {
521            for_each_fixture!(test_for_all_fixtures!(@emit [test_traced($level)] $callee [] []));
522        };
523        ($callee:ident, seeds = $n:expr) => {
524            for_each_fixture!(test_for_all_fixtures!(@seeded $n, $callee));
525        };
526        ($callee:ident, $arg:expr, level = $level:literal) => {
527            for_each_fixture!(test_for_all_fixtures!(@emit [test_traced($level)] $callee [, _] [, $arg]));
528        };
529        ($callee:ident, $arg:expr) => {
530            for_each_fixture!(test_for_all_fixtures!(@emit [test_traced] $callee [, _] [, $arg]));
531        };
532        (@emit [$traced:meta] $callee:ident [$($generics:tt)*] [$($args:tt)*], $suffix:ident, $elector:ty, $fixture:expr) => {
533            paste::paste! {
534                #[test_group("slow")]
535                #[$traced]
536                fn [<test_ $callee _ $suffix>]() {
537                    $callee::<_, _, $elector $($generics)*>($fixture $($args)*);
538                }
539            }
540        };
541        (@seeded $n:expr, $callee:ident, $suffix:ident, $elector:ty, $fixture:expr) => {
542            paste::paste! {
543                #[test_group("slow")]
544                #[test_traced]
545                fn [<test_ $callee _ $suffix>]() {
546                    for seed in 0..$n {
547                        $callee::<_, _, $elector>(seed, $fixture);
548                    }
549                }
550            }
551        };
552    }
553
554    const PAGE_SIZE: NonZeroU16 = NZU16!(1024);
555    const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(10);
556    const TEST_QUOTA: Quota = Quota::per_second(NonZeroU32::MAX);
557
558    #[test]
559    fn test_interesting() {
560        let activity_timeout = ViewDelta::new(10);
561
562        // Genesis view is never interesting
563        assert!(!interesting(
564            activity_timeout,
565            View::zero(),
566            View::zero(),
567            View::zero(),
568            false
569        ));
570        assert!(!interesting(
571            activity_timeout,
572            View::zero(),
573            View::new(1),
574            View::zero(),
575            true
576        ));
577
578        // View below min_active is not interesting
579        assert!(!interesting(
580            activity_timeout,
581            View::new(20),
582            View::new(25),
583            View::new(5), // below min_active (10)
584            false
585        ));
586
587        // View at min_active boundary is interesting
588        assert!(interesting(
589            activity_timeout,
590            View::new(20),
591            View::new(25),
592            View::new(10), // exactly min_active
593            false
594        ));
595
596        // Future view beyond current.next() is not interesting when allow_future is false
597        assert!(!interesting(
598            activity_timeout,
599            View::new(20),
600            View::new(25),
601            View::new(27),
602            false
603        ));
604
605        // Future view beyond current.next() is interesting when allow_future is true
606        assert!(interesting(
607            activity_timeout,
608            View::new(20),
609            View::new(25),
610            View::new(27),
611            true
612        ));
613
614        // View at current.next() is interesting
615        assert!(interesting(
616            activity_timeout,
617            View::new(20),
618            View::new(25),
619            View::new(26),
620            false
621        ));
622
623        // View within valid range is interesting
624        assert!(interesting(
625            activity_timeout,
626            View::new(20),
627            View::new(25),
628            View::new(22),
629            false
630        ));
631
632        // When last_finalized is 0 and activity_timeout would underflow
633        // min_active saturates at 0, so view 1 should still be interesting
634        assert!(interesting(
635            activity_timeout,
636            View::zero(),
637            View::new(5),
638            View::new(1),
639            false
640        ));
641    }
642
643    /// Register a validator with the oracle.
644    async fn register_validator(
645        oracle: &mut Oracle<PublicKey, deterministic::Context>,
646        validator: PublicKey,
647    ) -> (
648        (
649            Sender<PublicKey, deterministic::Context>,
650            Receiver<PublicKey>,
651        ),
652        (
653            Sender<PublicKey, deterministic::Context>,
654            Receiver<PublicKey>,
655        ),
656        (
657            Sender<PublicKey, deterministic::Context>,
658            Receiver<PublicKey>,
659        ),
660    ) {
661        let control = oracle.control(validator.clone());
662        let (vote_sender, vote_receiver) = control.register(0, TEST_QUOTA).await.unwrap();
663        let (certificate_sender, certificate_receiver) =
664            control.register(1, TEST_QUOTA).await.unwrap();
665        let (resolver_sender, resolver_receiver) = control.register(2, TEST_QUOTA).await.unwrap();
666        (
667            (vote_sender, vote_receiver),
668            (certificate_sender, certificate_receiver),
669            (resolver_sender, resolver_receiver),
670        )
671    }
672
673    /// Registers all validators using the oracle.
674    async fn register_validators(
675        oracle: &mut Oracle<PublicKey, deterministic::Context>,
676        validators: &[PublicKey],
677    ) -> HashMap<
678        PublicKey,
679        (
680            (
681                Sender<PublicKey, deterministic::Context>,
682                Receiver<PublicKey>,
683            ),
684            (
685                Sender<PublicKey, deterministic::Context>,
686                Receiver<PublicKey>,
687            ),
688            (
689                Sender<PublicKey, deterministic::Context>,
690                Receiver<PublicKey>,
691            ),
692        ),
693    > {
694        let mut registrations = HashMap::new();
695        for validator in validators.iter() {
696            let registration = register_validator(oracle, validator.clone()).await;
697            registrations.insert(validator.clone(), registration);
698        }
699        registrations
700    }
701
702    async fn start_test_network_with_peers<I>(
703        context: deterministic::Context,
704        peers: I,
705        disconnect_on_block: bool,
706    ) -> Oracle<PublicKey, deterministic::Context>
707    where
708        I: IntoIterator<Item = PublicKey>,
709    {
710        let (network, oracle) = Network::new_with_peers(
711            context.child("network"),
712            Config {
713                max_size: 1024 * 1024,
714                disconnect_on_block,
715                tracked_peer_sets: NZUsize!(1),
716            },
717            peers,
718        )
719        .await;
720        network.start();
721        oracle
722    }
723
724    async fn start_test_network_with_split_peers<I, J>(
725        context: deterministic::Context,
726        primary: I,
727        secondary: J,
728        disconnect_on_block: bool,
729    ) -> Oracle<PublicKey, deterministic::Context>
730    where
731        I: IntoIterator<Item = PublicKey>,
732        J: IntoIterator<Item = PublicKey>,
733    {
734        let (network, oracle) = Network::new_with_split_peers(
735            context.child("network"),
736            Config {
737                max_size: 1024 * 1024,
738                disconnect_on_block,
739                tracked_peer_sets: NZUsize!(1),
740            },
741            primary,
742            secondary,
743        )
744        .await;
745        network.start();
746        oracle
747    }
748
749    /// Enum to describe the action to take when linking validators.
750    enum Action {
751        Link(Link),
752        Update(Link), // Unlink and then link
753        Unlink,
754    }
755
756    /// Links (or unlinks) validators using the oracle.
757    ///
758    /// The `action` parameter determines the action (e.g. link, unlink) to take.
759    /// The `restrict_to` function can be used to restrict the linking to certain connections,
760    /// otherwise all validators will be linked to all other validators.
761    async fn link_validators(
762        oracle: &mut Oracle<PublicKey, deterministic::Context>,
763        validators: &[PublicKey],
764        action: Action,
765        restrict_to: Option<fn(usize, usize, usize) -> bool>,
766    ) {
767        for (i1, v1) in validators.iter().enumerate() {
768            for (i2, v2) in validators.iter().enumerate() {
769                // Ignore self
770                if v2 == v1 {
771                    continue;
772                }
773
774                // Restrict to certain connections
775                if let Some(f) = restrict_to {
776                    if !f(validators.len(), i1, i2) {
777                        continue;
778                    }
779                }
780
781                // Do any unlinking first
782                match action {
783                    Action::Update(_) | Action::Unlink => {
784                        oracle.remove_link(v1.clone(), v2.clone()).await.unwrap();
785                    }
786                    _ => {}
787                }
788
789                // Do any linking after
790                match action {
791                    Action::Link(ref link) | Action::Update(ref link) => {
792                        oracle
793                            .add_link(v1.clone(), v2.clone(), link.clone())
794                            .await
795                            .unwrap();
796                    }
797                    _ => {}
798                }
799            }
800        }
801    }
802
803    /// Counts lines where all patterns match and the trailing value is non-zero.
804    fn count_nonzero_metric_lines(encoded: &str, patterns: &[&str]) -> u32 {
805        encoded
806            .lines()
807            .filter(|line| patterns.iter().all(|p| line.contains(p)))
808            .filter(|line| {
809                line.split_whitespace()
810                    .last()
811                    .and_then(|s| s.parse::<u64>().ok())
812                    .is_some_and(|n| n > 0)
813            })
814            .count() as u32
815    }
816
817    fn all_online<S, F, L, T>(
818        mut fixture: F,
819        strategy: impl FnOnce(&mut deterministic::Context) -> T + Send + 'static,
820    ) where
821        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
822        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
823        L: Elector<S>,
824        T: Strategy,
825    {
826        // Create context
827        let n = 5;
828        let quorum = quorum(n) as usize;
829        let required_containers = View::new(100);
830        let activity_timeout = ViewDelta::new(10);
831        let skip_timeout = ViewDelta::new(5);
832        let namespace = b"consensus".to_vec();
833        let executor = deterministic::Runner::timed(Duration::from_secs(300));
834        executor.start(|mut context| async move {
835            // Register participants
836            let Fixture {
837                participants,
838                schemes,
839                ..
840            } = fixture(&mut context, &namespace, n);
841            let strategy = strategy(&mut context);
842            let mut oracle =
843                start_test_network_with_peers(context.child("network"), participants.clone(), true)
844                    .await;
845            let mut registrations = register_validators(&mut oracle, &participants).await;
846
847            // Link all validators
848            let link = Link {
849                latency: Duration::from_millis(10),
850                jitter: Duration::from_millis(1),
851                success_rate: 1.0,
852            };
853            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
854
855            // Create engines
856            let elector = L::default();
857            let relay = Arc::new(mocks::relay::Relay::new());
858            let mut reporters = Vec::new();
859            let mut engine_handlers = Vec::new();
860            for (idx, validator) in participants.iter().enumerate() {
861                // Create scheme context
862                let context = context
863                    .child("validator")
864                    .with_attribute("public_key", validator);
865
866                // Configure engine
867                let reporter_config = mocks::reporter::Config {
868                    participants: participants.clone().try_into().unwrap(),
869                    scheme: schemes[idx].clone(),
870                    elector: elector.clone(),
871                };
872                let reporter =
873                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
874                reporters.push(reporter.clone());
875                let application_cfg = mocks::application::Config {
876                    hasher: Sha256::default(),
877                    relay: relay.clone(),
878                    me: validator.clone(),
879                    propose_latency: (10.0, 5.0),
880                    verify_latency: (10.0, 5.0),
881                    certify_latency: (10.0, 5.0),
882                    should_certify: mocks::application::Certifier::Always,
883                };
884                let (actor, application) = mocks::application::Application::new(
885                    context.child("application"),
886                    application_cfg,
887                );
888                actor.start();
889                let blocker = oracle.control(validator.clone());
890                let cfg = config::Config {
891                    scheme: schemes[idx].clone(),
892                    elector: elector.clone(),
893                    blocker,
894                    automaton: application.clone(),
895                    relay: application.clone(),
896                    reporter: reporter.clone(),
897                    strategy: strategy.clone(),
898                    partition: validator.to_string(),
899                    mailbox_size: NZUsize!(1024),
900                    epoch: Epoch::new(333),
901                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
902                        Epoch::new(333),
903                    )),
904                    leader_timeout: Duration::from_secs(1),
905                    certification_timeout: Duration::from_secs(2),
906                    timeout_retry: Duration::from_secs(10),
907                    fetch_timeout: Duration::from_secs(1),
908                    activity_timeout,
909                    skip_timeout,
910                    fetch_concurrent: NZUsize!(4),
911                    replay_buffer: NZUsize!(1024 * 1024),
912                    write_buffer: NZUsize!(1024 * 1024),
913                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
914                    forwarding: ForwardingPolicy::Disabled,
915                };
916                let engine = Engine::new(context.child("engine"), cfg);
917
918                // Start engine
919                let (pending, recovered, resolver) = registrations
920                    .remove(validator)
921                    .expect("validator should be registered");
922                engine_handlers.push(engine.start(pending, recovered, resolver));
923            }
924
925            // Wait for all engines to finish
926            let mut finalizers = Vec::new();
927            for reporter in reporters.iter_mut() {
928                let (mut latest, mut monitor) = reporter.subscribe().await;
929                finalizers.push(context.child("finalizer").spawn(move |_| async move {
930                    while latest < required_containers {
931                        latest = monitor.recv().await.expect("event missing");
932                    }
933                }));
934            }
935            join_all(finalizers).await;
936
937            // Check reporters for correct activity
938            let latest_complete = required_containers.saturating_sub(activity_timeout);
939            for reporter in reporters.iter() {
940                // Ensure no faults
941                reporter.assert_no_faults();
942
943                // Ensure no invalid signatures
944                reporter.assert_no_invalid();
945
946                // Ensure certificates for all views
947                {
948                    let certified = reporter.certified.lock();
949                    for view in View::range(View::new(1), latest_complete) {
950                        // Ensure certificate for every view
951                        if !certified.contains(&view) {
952                            panic!("view: {view}");
953                        }
954                    }
955                }
956
957                // Ensure no forks
958                let mut notarized = HashMap::new();
959                let mut finalized = HashMap::new();
960                {
961                    let notarizes = reporter.notarizes.lock();
962                    for view in View::range(View::new(1), latest_complete) {
963                        // Ensure only one payload proposed per view
964                        let Some(payloads) = notarizes.get(&view) else {
965                            continue;
966                        };
967                        if payloads.len() > 1 {
968                            panic!("view: {view}");
969                        }
970                        let (digest, notarizers) = payloads.iter().next().unwrap();
971                        notarized.insert(view, *digest);
972
973                        if notarizers.len() < quorum {
974                            // We can't verify that everyone participated at every view because some nodes may
975                            // have started later.
976                            panic!("view: {view}");
977                        }
978                    }
979                }
980                {
981                    let notarizations = reporter.notarizations.lock();
982                    for view in View::range(View::new(1), latest_complete) {
983                        // Ensure notarization matches digest from notarizes
984                        let Some(notarization) = notarizations.get(&view) else {
985                            continue;
986                        };
987                        let Some(digest) = notarized.get(&view) else {
988                            continue;
989                        };
990                        assert_eq!(&notarization.proposal.payload, digest);
991                    }
992                }
993                {
994                    let finalizes = reporter.finalizes.lock();
995                    for view in View::range(View::new(1), latest_complete) {
996                        // Ensure only one payload proposed per view
997                        let Some(payloads) = finalizes.get(&view) else {
998                            continue;
999                        };
1000                        if payloads.len() > 1 {
1001                            panic!("view: {view}");
1002                        }
1003                        let (digest, finalizers) = payloads.iter().next().unwrap();
1004                        finalized.insert(view, *digest);
1005
1006                        // Only check at views below timeout
1007                        if view > latest_complete {
1008                            continue;
1009                        }
1010
1011                        // Ensure everyone participating
1012                        if finalizers.len() < quorum {
1013                            // We can't verify that everyone participated at every view because some nodes may
1014                            // have started later.
1015                            panic!("view: {view}");
1016                        }
1017
1018                        // Ensure no nullifies for any finalizers
1019                        let nullifies = reporter.nullifies.lock();
1020                        let Some(nullifies) = nullifies.get(&view) else {
1021                            continue;
1022                        };
1023                        for finalizers in payloads.values() {
1024                            for finalizer in finalizers.iter() {
1025                                if nullifies.contains(finalizer) {
1026                                    panic!("should not nullify and finalize at same view");
1027                                }
1028                            }
1029                        }
1030                    }
1031                }
1032                {
1033                    let finalizations = reporter.finalizations.lock();
1034                    for view in View::range(View::new(1), latest_complete) {
1035                        // Ensure finalization matches digest from finalizes
1036                        let Some(finalization) = finalizations.get(&view) else {
1037                            continue;
1038                        };
1039                        let Some(digest) = finalized.get(&view) else {
1040                            continue;
1041                        };
1042                        assert_eq!(&finalization.proposal.payload, digest);
1043                    }
1044                }
1045            }
1046
1047            // Ensure no blocked connections
1048            let blocked = oracle.blocked().await.unwrap();
1049            assert!(blocked.is_empty());
1050        });
1051    }
1052
1053    test_for_all_fixtures!(all_online, |_| Sequential);
1054
1055    #[test_group("slow")]
1056    #[test_traced]
1057    fn test_all_online_rayon_bls12381_threshold_vrf_min_pk() {
1058        all_online::<_, _, Random, _>(bls12381_threshold_vrf::fixture::<MinPk, _>, |context| {
1059            context.strategy(NZUsize!(2))
1060        });
1061    }
1062
1063    fn non_genesis_floor_joiner_catches_tip<S, F, L>(mut fixture: F)
1064    where
1065        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
1066        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
1067        L: Elector<S>,
1068    {
1069        // First let a quorum finalize beyond genesis so the joiner has a real
1070        // floor certificate and existing tip to catch.
1071        let n = 5;
1072        let active_count = quorum(n) as usize;
1073        let initial_tip_target = View::new(15);
1074        let activity_timeout = ViewDelta::new(10);
1075        let skip_timeout = ViewDelta::new(5);
1076        let namespace = b"consensus".to_vec();
1077        let executor = deterministic::Runner::timed(Duration::from_secs(300));
1078        executor.start(|mut context| async move {
1079            let Fixture {
1080                participants,
1081                schemes,
1082                ..
1083            } = fixture(&mut context, &namespace, n);
1084            let mut oracle =
1085                start_test_network_with_peers(context.child("network"), participants.clone(), true)
1086                    .await;
1087
1088            let active = &participants[..active_count];
1089            let joiner_idx = active_count;
1090            let joiner = participants[joiner_idx].clone();
1091
1092            let link = Link {
1093                latency: Duration::from_millis(10),
1094                jitter: Duration::from_millis(1),
1095                success_rate: 1.0,
1096            };
1097            link_validators(&mut oracle, active, Action::Link(link.clone()), None).await;
1098
1099            let elector = L::default();
1100            let relay = Arc::new(mocks::relay::Relay::new());
1101            let mut reporters = Vec::new();
1102            let mut engine_handlers = Vec::new();
1103
1104            for (idx, validator) in active.iter().enumerate() {
1105                let validator_context = context
1106                    .child("validator")
1107                    .with_attribute("public_key", validator);
1108
1109                let reporter_config = mocks::reporter::Config {
1110                    participants: participants.clone().try_into().unwrap(),
1111                    scheme: schemes[idx].clone(),
1112                    elector: elector.clone(),
1113                };
1114                let reporter = mocks::reporter::Reporter::new(
1115                    validator_context.child("reporter"),
1116                    reporter_config,
1117                );
1118                reporters.push(reporter.clone());
1119
1120                let application_cfg = mocks::application::Config {
1121                    hasher: Sha256::default(),
1122                    relay: relay.clone(),
1123                    me: validator.clone(),
1124                    propose_latency: (10.0, 5.0),
1125                    verify_latency: (10.0, 5.0),
1126                    certify_latency: (10.0, 5.0),
1127                    should_certify: mocks::application::Certifier::Always,
1128                };
1129                let (actor, application) = mocks::application::Application::new(
1130                    validator_context.child("application"),
1131                    application_cfg,
1132                );
1133                actor.start();
1134
1135                let cfg = config::Config {
1136                    scheme: schemes[idx].clone(),
1137                    elector: elector.clone(),
1138                    blocker: oracle.control(validator.clone()),
1139                    automaton: application.clone(),
1140                    relay: application.clone(),
1141                    reporter: reporter.clone(),
1142                    strategy: Sequential,
1143                    partition: format!("joiner_catches_tip_{validator}"),
1144                    mailbox_size: NZUsize!(1024),
1145                    epoch: Epoch::new(333),
1146                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
1147                        Epoch::new(333),
1148                    )),
1149                    leader_timeout: Duration::from_secs(1),
1150                    certification_timeout: Duration::from_secs(2),
1151                    timeout_retry: Duration::from_secs(10),
1152                    fetch_timeout: Duration::from_secs(1),
1153                    activity_timeout,
1154                    skip_timeout,
1155                    fetch_concurrent: NZUsize!(4),
1156                    replay_buffer: NZUsize!(1024 * 1024),
1157                    write_buffer: NZUsize!(1024 * 1024),
1158                    page_cache: CacheRef::from_pooler(
1159                        &validator_context,
1160                        PAGE_SIZE,
1161                        PAGE_CACHE_SIZE,
1162                    ),
1163                    forwarding: ForwardingPolicy::Disabled,
1164                };
1165                let engine = Engine::new(validator_context.child("engine"), cfg);
1166                let (pending, recovered, resolver) =
1167                    register_validator(&mut oracle, validator.clone()).await;
1168                engine_handlers.push(engine.start(pending, recovered, resolver));
1169            }
1170
1171            let mut finalizers = Vec::new();
1172            for reporter in reporters.iter_mut() {
1173                let (mut latest, mut monitor) = reporter.subscribe().await;
1174                finalizers.push(
1175                    context
1176                        .child("initial_finalizer")
1177                        .spawn(move |_| async move {
1178                            while latest < initial_tip_target {
1179                                latest = monitor.recv().await.expect("event missing");
1180                            }
1181                            latest
1182                        }),
1183                );
1184            }
1185            let tip_at_join = join_all(finalizers)
1186                .await
1187                .into_iter()
1188                .map(|result| result.expect("initial finalizer failed"))
1189                .min()
1190                .expect("initial validators missing");
1191
1192            let (floor_view, floor_finalization) = {
1193                let finalizations = reporters[0].finalizations.lock();
1194                finalizations
1195                    .iter()
1196                    .filter(|(view, _)| **view > View::zero() && **view < tip_at_join)
1197                    .min_by_key(|(view, _)| view.get())
1198                    .map(|(view, finalization)| (*view, finalization.clone()))
1199                    .expect("non-genesis floor finalization missing")
1200            };
1201            assert!(floor_view > View::zero());
1202            assert!(floor_view < tip_at_join);
1203
1204            // Start the extra validator from the non-genesis floor and require
1205            // it to catch both the existing tip and later cluster progress.
1206            for validator in active.iter() {
1207                oracle
1208                    .add_link(joiner.clone(), validator.clone(), link.clone())
1209                    .await
1210                    .unwrap();
1211                oracle
1212                    .add_link(validator.clone(), joiner.clone(), link.clone())
1213                    .await
1214                    .unwrap();
1215            }
1216
1217            let joiner_context = context
1218                .child("validator")
1219                .with_attribute("public_key", &joiner);
1220            let reporter_config = mocks::reporter::Config {
1221                participants: participants.clone().try_into().unwrap(),
1222                scheme: schemes[joiner_idx].clone(),
1223                elector: elector.clone(),
1224            };
1225            let mut joiner_reporter =
1226                mocks::reporter::Reporter::new(joiner_context.child("reporter"), reporter_config);
1227            reporters.push(joiner_reporter.clone());
1228
1229            let application_cfg = mocks::application::Config {
1230                hasher: Sha256::default(),
1231                relay: relay.clone(),
1232                me: joiner.clone(),
1233                propose_latency: (10.0, 5.0),
1234                verify_latency: (10.0, 5.0),
1235                certify_latency: (10.0, 5.0),
1236                should_certify: mocks::application::Certifier::Always,
1237            };
1238            let (actor, application) = mocks::application::Application::new(
1239                joiner_context.child("application"),
1240                application_cfg,
1241            );
1242            actor.start();
1243
1244            let cfg = config::Config {
1245                scheme: schemes[joiner_idx].clone(),
1246                elector,
1247                blocker: oracle.control(joiner.clone()),
1248                automaton: application.clone(),
1249                relay: application.clone(),
1250                reporter: joiner_reporter.clone(),
1251                strategy: Sequential,
1252                partition: format!("joiner_catches_tip_{joiner}"),
1253                mailbox_size: NZUsize!(1024),
1254                epoch: Epoch::new(333),
1255                floor: config::Floor::Finalized(floor_finalization),
1256                leader_timeout: Duration::from_secs(1),
1257                certification_timeout: Duration::from_secs(2),
1258                timeout_retry: Duration::from_secs(10),
1259                fetch_timeout: Duration::from_secs(1),
1260                activity_timeout,
1261                skip_timeout,
1262                fetch_concurrent: NZUsize!(4),
1263                replay_buffer: NZUsize!(1024 * 1024),
1264                write_buffer: NZUsize!(1024 * 1024),
1265                page_cache: CacheRef::from_pooler(&joiner_context, PAGE_SIZE, PAGE_CACHE_SIZE),
1266                forwarding: ForwardingPolicy::Disabled,
1267            };
1268            let engine = Engine::new(joiner_context.child("engine"), cfg);
1269            let (pending, recovered, resolver) = register_validator(&mut oracle, joiner).await;
1270            engine_handlers.push(engine.start(pending, recovered, resolver));
1271
1272            let (mut joiner_latest, mut joiner_monitor) = joiner_reporter.subscribe().await;
1273            while joiner_latest < tip_at_join {
1274                joiner_latest = joiner_monitor.recv().await.expect("event missing");
1275            }
1276
1277            let post_join_target = tip_at_join.saturating_add(ViewDelta::new(5));
1278            while joiner_latest < post_join_target {
1279                joiner_latest = joiner_monitor.recv().await.expect("event missing");
1280            }
1281
1282            for reporter in reporters.iter() {
1283                reporter.assert_no_faults();
1284                reporter.assert_no_invalid();
1285            }
1286
1287            let blocked = oracle.blocked().await.unwrap();
1288            assert!(blocked.is_empty());
1289        });
1290    }
1291
1292    test_for_all_fixtures!(non_genesis_floor_joiner_catches_tip);
1293
1294    /// A dishonest leader (validator 0) proposes payloads that all honest peers
1295    /// refuse to certify.
1296    ///
1297    /// All n validators use the honest Application, but every peer's certifier
1298    /// rejects proposals from views where validator 0 is the elected leader.
1299    /// When validator 0 IS the leader, it short-circuits certification locally
1300    /// (it built the proposal) and votes finalize, but every other peer
1301    /// rejects via the Custom predicate and nullifies. The lone finalize vote
1302    /// cannot form a certificate (quorum=4). The nullification cert (4 honest
1303    /// peers) advances everyone.
1304    ///
1305    /// When an honest validator leads, all peers (including validator 0)
1306    /// certify normally and finalize. The cluster makes progress on honest
1307    /// leader views and nullifies dishonest leader views.
1308    fn dishonest_leader_certification_rejected<S, F>(mut fixture: F)
1309    where
1310        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
1311        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
1312        RoundRobin: Elector<S>,
1313    {
1314        let n = 5;
1315        let required_containers = View::new(50);
1316        let activity_timeout = ViewDelta::new(10);
1317        let skip_timeout = ViewDelta::new(5);
1318        let namespace = b"consensus".to_vec();
1319        let executor = deterministic::Runner::timed(Duration::from_secs(300));
1320        executor.start(|mut context| async move {
1321            let Fixture {
1322                participants,
1323                schemes,
1324                ..
1325            } = fixture(&mut context, &namespace, n);
1326            let mut oracle =
1327                start_test_network_with_peers(context.child("network"), participants.clone(), true)
1328                    .await;
1329            let mut registrations = register_validators(&mut oracle, &participants).await;
1330
1331            let link = Link {
1332                latency: Duration::from_millis(10),
1333                jitter: Duration::from_millis(1),
1334                success_rate: 1.0,
1335            };
1336            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
1337
1338            let elector = RoundRobin::default();
1339            let participants_set: Set<S::PublicKey> = participants.clone().try_into().unwrap();
1340            let built_elector = elector.clone().build(&participants_set);
1341            let relay = Arc::new(mocks::relay::Relay::new());
1342            let mut reporters = Vec::new();
1343            let mut engine_handlers = Vec::new();
1344            let dishonest = Participant::new(0);
1345            for (idx, validator) in participants.iter().enumerate() {
1346                let context = context
1347                    .child("validator")
1348                    .with_attribute("public_key", validator);
1349                let reporter_config = mocks::reporter::Config {
1350                    participants: participants.clone().try_into().unwrap(),
1351                    scheme: schemes[idx].clone(),
1352                    elector: elector.clone(),
1353                };
1354                let reporter =
1355                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
1356                reporters.push(reporter.clone());
1357
1358                let application_cfg = mocks::application::Config {
1359                    hasher: Sha256::default(),
1360                    relay: relay.clone(),
1361                    me: validator.clone(),
1362                    propose_latency: (10.0, 5.0),
1363                    verify_latency: (10.0, 5.0),
1364                    certify_latency: (10.0, 5.0),
1365                    should_certify: mocks::application::Certifier::Custom(Box::new({
1366                        let built_elector_clone = built_elector.clone();
1367                        move |round, _| built_elector_clone.elect(round, None) != dishonest
1368                    })),
1369                };
1370                let (actor, application) = mocks::application::Application::new(
1371                    context.child("application"),
1372                    application_cfg,
1373                );
1374                actor.start();
1375
1376                let blocker = oracle.control(validator.clone());
1377                let cfg = config::Config {
1378                    scheme: schemes[idx].clone(),
1379                    elector: elector.clone(),
1380                    blocker,
1381                    automaton: application.clone(),
1382                    relay: application.clone(),
1383                    reporter: reporter.clone(),
1384                    strategy: Sequential,
1385                    partition: validator.to_string(),
1386                    mailbox_size: NZUsize!(1024),
1387                    epoch: Epoch::new(333),
1388                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
1389                        Epoch::new(333),
1390                    )),
1391                    leader_timeout: Duration::from_secs(1),
1392                    certification_timeout: Duration::from_secs(2),
1393                    timeout_retry: Duration::from_secs(10),
1394                    fetch_timeout: Duration::from_secs(1),
1395                    activity_timeout,
1396                    skip_timeout,
1397                    fetch_concurrent: NZUsize!(4),
1398                    replay_buffer: NZUsize!(1024 * 1024),
1399                    write_buffer: NZUsize!(1024 * 1024),
1400                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1401                    forwarding: ForwardingPolicy::Disabled,
1402                };
1403                let engine = Engine::new(context.child("engine"), cfg);
1404                let (pending, recovered, resolver) = registrations
1405                    .remove(validator)
1406                    .expect("validator should be registered");
1407                engine_handlers.push(engine.start(pending, recovered, resolver));
1408            }
1409
1410            let mut finalizers = Vec::new();
1411            for reporter in reporters.iter_mut() {
1412                let (mut latest, mut monitor) = reporter.subscribe().await;
1413                finalizers.push(context.child("finalizer").spawn(move |_| async move {
1414                    while latest < required_containers {
1415                        latest = monitor.recv().await.expect("event missing");
1416                    }
1417                }));
1418            }
1419            join_all(finalizers).await;
1420
1421            for reporter in reporters.iter() {
1422                reporter.assert_no_faults();
1423                reporter.assert_no_invalid();
1424            }
1425        });
1426    }
1427
1428    #[test_group("slow")]
1429    #[test_traced]
1430    fn test_dishonest_leader_certification_rejected() {
1431        dishonest_leader_certification_rejected::<_, _>(
1432            bls12381_threshold_vrf::fixture::<MinPk, _>,
1433        );
1434        dishonest_leader_certification_rejected::<_, _>(
1435            bls12381_threshold_vrf::fixture::<MinSig, _>,
1436        );
1437        dishonest_leader_certification_rejected::<_, _>(
1438            bls12381_threshold_std::fixture::<MinPk, _>,
1439        );
1440        dishonest_leader_certification_rejected::<_, _>(
1441            bls12381_threshold_std::fixture::<MinSig, _>,
1442        );
1443        dishonest_leader_certification_rejected::<_, _>(bls12381_multisig::fixture::<MinPk, _>);
1444        dishonest_leader_certification_rejected::<_, _>(bls12381_multisig::fixture::<MinSig, _>);
1445        dishonest_leader_certification_rejected::<_, _>(ed25519::fixture);
1446        dishonest_leader_certification_rejected::<_, _>(secp256r1::fixture);
1447    }
1448
1449    fn observer<S, F, L>(mut fixture: F)
1450    where
1451        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
1452        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
1453        L: Elector<S>,
1454    {
1455        // Create context
1456        let n_active = 5;
1457        let required_containers = View::new(100);
1458        let activity_timeout = ViewDelta::new(10);
1459        let skip_timeout = ViewDelta::new(5);
1460        let namespace = b"consensus".to_vec();
1461        let executor = deterministic::Runner::timed(Duration::from_secs(300));
1462        executor.start(|mut context| async move {
1463            // Register participants (active)
1464            let Fixture {
1465                participants,
1466                schemes,
1467                verifier,
1468                ..
1469            } = fixture(&mut context, &namespace, n_active);
1470
1471            // Add observer (no share)
1472            let private_key_observer = PrivateKey::from_seed(n_active as u64);
1473            let public_key_observer = private_key_observer.public_key();
1474
1475            let mut oracle = start_test_network_with_split_peers(
1476                context.child("network"),
1477                participants.clone(),
1478                [public_key_observer.clone()],
1479                true,
1480            )
1481            .await;
1482
1483            // Register all (including observer) with the network
1484            let mut all_validators = participants.clone();
1485            all_validators.push(public_key_observer.clone());
1486            all_validators.sort();
1487            let mut registrations = register_validators(&mut oracle, &all_validators).await;
1488
1489            // Link all peers (including observer)
1490            let link = Link {
1491                latency: Duration::from_millis(10),
1492                jitter: Duration::from_millis(1),
1493                success_rate: 1.0,
1494            };
1495            link_validators(&mut oracle, &all_validators, Action::Link(link), None).await;
1496
1497            // Create engines
1498            let elector = L::default();
1499            let relay = Arc::new(mocks::relay::Relay::new());
1500            let mut reporters = Vec::new();
1501
1502            for (idx, validator) in participants.iter().enumerate() {
1503                let is_observer = *validator == public_key_observer;
1504
1505                // Create scheme context
1506                let context = context
1507                    .child("validator")
1508                    .with_attribute("public_key", validator);
1509
1510                // Configure engine
1511                let signing = if is_observer {
1512                    verifier.clone()
1513                } else {
1514                    schemes[idx].clone()
1515                };
1516                let reporter_config = mocks::reporter::Config {
1517                    participants: participants.clone().try_into().unwrap(),
1518                    scheme: signing.clone(),
1519                    elector: elector.clone(),
1520                };
1521                let reporter =
1522                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
1523                reporters.push(reporter.clone());
1524                let application_cfg = mocks::application::Config {
1525                    hasher: Sha256::default(),
1526                    relay: relay.clone(),
1527                    me: validator.clone(),
1528                    propose_latency: (10.0, 5.0),
1529                    verify_latency: (10.0, 5.0),
1530                    certify_latency: (10.0, 5.0),
1531                    should_certify: mocks::application::Certifier::Always,
1532                };
1533                let (actor, application) = mocks::application::Application::new(
1534                    context.child("application"),
1535                    application_cfg,
1536                );
1537                actor.start();
1538                let blocker = oracle.control(validator.clone());
1539                let cfg = config::Config {
1540                    scheme: signing.clone(),
1541                    elector: elector.clone(),
1542                    blocker,
1543                    automaton: application.clone(),
1544                    relay: application.clone(),
1545                    reporter: reporter.clone(),
1546                    strategy: Sequential,
1547                    partition: validator.to_string(),
1548                    mailbox_size: NZUsize!(1024),
1549                    epoch: Epoch::new(333),
1550                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
1551                        Epoch::new(333),
1552                    )),
1553                    leader_timeout: Duration::from_secs(1),
1554                    certification_timeout: Duration::from_secs(2),
1555                    timeout_retry: Duration::from_secs(10),
1556                    fetch_timeout: Duration::from_secs(1),
1557                    activity_timeout,
1558                    skip_timeout,
1559                    fetch_concurrent: NZUsize!(4),
1560                    replay_buffer: NZUsize!(1024 * 1024),
1561                    write_buffer: NZUsize!(1024 * 1024),
1562                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1563                    forwarding: ForwardingPolicy::Disabled,
1564                };
1565                let engine = Engine::new(context.child("engine"), cfg);
1566
1567                // Start engine
1568                let (pending, recovered, resolver) = registrations
1569                    .remove(validator)
1570                    .expect("validator should be registered");
1571                engine.start(pending, recovered, resolver);
1572            }
1573
1574            // Wait for all  engines to finish
1575            let mut finalizers = Vec::new();
1576            for reporter in reporters.iter_mut() {
1577                let (mut latest, mut monitor) = reporter.subscribe().await;
1578                finalizers.push(context.child("finalizer").spawn(move |_| async move {
1579                    while latest < required_containers {
1580                        latest = monitor.recv().await.expect("event missing");
1581                    }
1582                }));
1583            }
1584            join_all(finalizers).await;
1585
1586            // Sanity check. The standalone secondary observer should still
1587            // process the chain to the same progress threshold as validators.
1588            for reporter in reporters.iter() {
1589                // Ensure no faults or invalid signatures
1590                reporter.assert_no_faults();
1591                reporter.assert_no_invalid();
1592
1593                // Ensure no blocked connections
1594                let blocked = oracle.blocked().await.unwrap();
1595                assert!(blocked.is_empty());
1596            }
1597        });
1598    }
1599
1600    test_for_all_fixtures!(observer);
1601
1602    fn unclean_shutdown<S, F, L>(mut fixture: F)
1603    where
1604        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
1605        F: FnMut(&mut TestRng, &[u8], u32) -> Fixture<S>,
1606        L: Elector<S>,
1607    {
1608        // Create context
1609        let n = 5;
1610        let required_containers = View::new(100);
1611        let activity_timeout = ViewDelta::new(10);
1612        let skip_timeout = ViewDelta::new(5);
1613        let namespace = b"consensus".to_vec();
1614
1615        // Random restarts every x seconds
1616        let shutdowns: Arc<Mutex<u64>> = Arc::new(Mutex::new(0));
1617        let supervised = Arc::new(Mutex::new(Vec::new()));
1618        let mut prev_checkpoint = None;
1619
1620        // Create validator keys
1621        let mut rng = test_rng();
1622        let Fixture {
1623            participants,
1624            schemes,
1625            ..
1626        } = fixture(&mut rng, &namespace, n);
1627        let reporter_seed: [u8; 32] = rng.random();
1628
1629        // Create block relay, shared across restarts.
1630        let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, S::PublicKey>::new());
1631
1632        loop {
1633            let participants = participants.clone();
1634            let schemes = schemes.clone();
1635            let shutdowns = shutdowns.clone();
1636            let supervised = supervised.clone();
1637            let relay = relay.clone();
1638            relay.deregister_all(); // Clear all recipients from previous restart.
1639
1640            let f = |mut context: deterministic::Context| async move {
1641                // Register participants
1642                let mut oracle = start_test_network_with_peers(
1643                    context.child("network"),
1644                    participants.clone(),
1645                    true,
1646                )
1647                .await;
1648                let mut registrations = register_validators(&mut oracle, &participants).await;
1649
1650                // Link all validators
1651                let link = Link {
1652                    latency: Duration::from_millis(50),
1653                    jitter: Duration::from_millis(50),
1654                    success_rate: 1.0,
1655                };
1656                link_validators(&mut oracle, &participants, Action::Link(link), None).await;
1657
1658                // Create engines
1659                let elector = L::default();
1660                let relay = Arc::new(mocks::relay::Relay::new());
1661                let mut reporters = HashMap::new();
1662                let mut engine_handlers = Vec::new();
1663                for (idx, validator) in participants.iter().enumerate() {
1664                    // Create scheme context
1665                    let context = context
1666                        .child("validator")
1667                        .with_attribute("public_key", validator);
1668
1669                    // Configure engine
1670                    let reporter_config = mocks::reporter::Config {
1671                        participants: participants.clone().try_into().unwrap(),
1672                        scheme: schemes[idx].clone(),
1673                        elector: elector.clone(),
1674                    };
1675                    let reporter_rng = StdRng::from_seed(reporter_seed);
1676                    let reporter = mocks::reporter::Reporter::new(reporter_rng, reporter_config);
1677                    reporters.insert(validator.clone(), reporter.clone());
1678                    let application_cfg = mocks::application::Config {
1679                        hasher: Sha256::default(),
1680                        relay: relay.clone(),
1681                        me: validator.clone(),
1682                        propose_latency: (10.0, 5.0),
1683                        verify_latency: (10.0, 5.0),
1684                        certify_latency: (10.0, 5.0),
1685                        should_certify: mocks::application::Certifier::Always,
1686                    };
1687                    let (actor, application) = mocks::application::Application::new(
1688                        context.child("application"),
1689                        application_cfg,
1690                    );
1691                    actor.start();
1692                    let blocker = oracle.control(validator.clone());
1693                    let cfg = config::Config {
1694                        scheme: schemes[idx].clone(),
1695                        elector: elector.clone(),
1696                        blocker,
1697                        automaton: application.clone(),
1698                        relay: application.clone(),
1699                        reporter: reporter.clone(),
1700                        strategy: Sequential,
1701                        partition: validator.to_string(),
1702                        mailbox_size: NZUsize!(1024),
1703                        epoch: Epoch::new(333),
1704                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
1705                            Epoch::new(333),
1706                        )),
1707                        leader_timeout: Duration::from_secs(1),
1708                        certification_timeout: Duration::from_secs(2),
1709                        timeout_retry: Duration::from_secs(10),
1710                        fetch_timeout: Duration::from_secs(1),
1711                        activity_timeout,
1712                        skip_timeout,
1713                        fetch_concurrent: NZUsize!(4),
1714                        replay_buffer: NZUsize!(1024 * 1024),
1715                        write_buffer: NZUsize!(1024 * 1024),
1716                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1717                        forwarding: ForwardingPolicy::Disabled,
1718                    };
1719                    let engine = Engine::new(context.child("engine"), cfg);
1720
1721                    // Start engine
1722                    let (pending, recovered, resolver) = registrations
1723                        .remove(validator)
1724                        .expect("validator should be registered");
1725                    engine_handlers.push(engine.start(pending, recovered, resolver));
1726                }
1727
1728                // Store all finalizer handles
1729                let mut finalizers = Vec::new();
1730                for reporter in reporters.values_mut() {
1731                    let (mut latest, mut monitor) = reporter.subscribe().await;
1732                    finalizers.push(context.child("finalizer").spawn(move |_| async move {
1733                        while latest < required_containers {
1734                            latest = monitor.recv().await.expect("event missing");
1735                        }
1736                    }));
1737                }
1738
1739                // Exit at random points for unclean shutdown of entire set
1740                let wait =
1741                    context.random_range(Duration::from_millis(100)..Duration::from_millis(2_000));
1742                let result = select! {
1743                    _ = context.sleep(wait) => {
1744                        // Collect reporters to check faults
1745                        {
1746                            let mut shutdowns = shutdowns.lock();
1747                            debug!(shutdowns = *shutdowns, elapsed = ?wait, "restarting");
1748                            *shutdowns += 1;
1749                        }
1750                        supervised.lock().push(reporters);
1751                        false
1752                    },
1753                    _ = join_all(finalizers) => {
1754                        // Check reporters for faults activity
1755                        let supervised = supervised.lock();
1756                        for reporters in supervised.iter() {
1757                            for reporter in reporters.values() {
1758                                reporter.assert_no_faults();
1759                            }
1760                        }
1761                        true
1762                    },
1763                };
1764
1765                // Ensure no blocked connections
1766                let blocked = oracle.blocked().await.unwrap();
1767                assert!(blocked.is_empty());
1768
1769                result
1770            };
1771
1772            let (complete, checkpoint) = prev_checkpoint
1773                .map_or_else(
1774                    || deterministic::Runner::timed(Duration::from_secs(180)),
1775                    deterministic::Runner::from,
1776                )
1777                .start_and_recover(f);
1778
1779            // Check if we should exit
1780            if complete {
1781                break;
1782            }
1783
1784            prev_checkpoint = Some(checkpoint);
1785        }
1786    }
1787
1788    test_for_all_fixtures!(unclean_shutdown);
1789
1790    fn backfill<S, F, L>(mut fixture: F)
1791    where
1792        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
1793        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
1794        L: Elector<S>,
1795    {
1796        // Create context
1797        let n = 4;
1798        let required_containers = View::new(100);
1799        let activity_timeout = ViewDelta::new(10);
1800        let skip_timeout = ViewDelta::new(5);
1801        let namespace = b"consensus".to_vec();
1802        let executor = deterministic::Runner::timed(Duration::from_secs(240));
1803        executor.start(|mut context| async move {
1804            // Register participants
1805            let Fixture {
1806                participants,
1807                schemes,
1808                ..
1809            } = fixture(&mut context, &namespace, n);
1810            let mut oracle =
1811                start_test_network_with_peers(context.child("network"), participants.clone(), true)
1812                    .await;
1813            let mut registrations = register_validators(&mut oracle, &participants).await;
1814
1815            // Link all validators except first
1816            let link = Link {
1817                latency: Duration::from_millis(10),
1818                jitter: Duration::from_millis(1),
1819                success_rate: 1.0,
1820            };
1821            link_validators(
1822                &mut oracle,
1823                &participants,
1824                Action::Link(link),
1825                Some(|_, i, j| ![i, j].contains(&0usize)),
1826            )
1827            .await;
1828
1829            // Create engines
1830            let elector = L::default();
1831            let relay = Arc::new(mocks::relay::Relay::new());
1832            let mut reporters = Vec::new();
1833            let mut engine_handlers = Vec::new();
1834            for (idx_scheme, validator) in participants.iter().enumerate() {
1835                // Skip first peer
1836                if idx_scheme == 0 {
1837                    continue;
1838                }
1839
1840                // Create scheme context
1841                let context = context
1842                    .child("validator")
1843                    .with_attribute("public_key", validator);
1844
1845                // Configure engine
1846                let reporter_config = mocks::reporter::Config {
1847                    participants: participants.clone().try_into().unwrap(),
1848                    scheme: schemes[idx_scheme].clone(),
1849                    elector: elector.clone(),
1850                };
1851                let reporter =
1852                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
1853                reporters.push(reporter.clone());
1854                let application_cfg = mocks::application::Config {
1855                    hasher: Sha256::default(),
1856                    relay: relay.clone(),
1857                    me: validator.clone(),
1858                    propose_latency: (10.0, 5.0),
1859                    verify_latency: (10.0, 5.0),
1860                    certify_latency: (10.0, 5.0),
1861                    should_certify: mocks::application::Certifier::Always,
1862                };
1863                let (actor, application) = mocks::application::Application::new(
1864                    context.child("application"),
1865                    application_cfg,
1866                );
1867                actor.start();
1868                let blocker = oracle.control(validator.clone());
1869                let cfg = config::Config {
1870                    scheme: schemes[idx_scheme].clone(),
1871                    elector: elector.clone(),
1872                    blocker,
1873                    automaton: application.clone(),
1874                    relay: application.clone(),
1875                    reporter: reporter.clone(),
1876                    strategy: Sequential,
1877                    partition: validator.to_string(),
1878                    mailbox_size: NZUsize!(1024),
1879                    epoch: Epoch::new(333),
1880                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
1881                        Epoch::new(333),
1882                    )),
1883                    leader_timeout: Duration::from_secs(1),
1884                    certification_timeout: Duration::from_secs(2),
1885                    timeout_retry: Duration::from_secs(10),
1886                    fetch_timeout: Duration::from_secs(1),
1887                    activity_timeout,
1888                    skip_timeout,
1889                    fetch_concurrent: NZUsize!(4),
1890                    replay_buffer: NZUsize!(1024 * 1024),
1891                    write_buffer: NZUsize!(1024 * 1024),
1892                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1893                    forwarding: ForwardingPolicy::Disabled,
1894                };
1895                let engine = Engine::new(context.child("engine"), cfg);
1896
1897                // Start engine
1898                let (pending, recovered, resolver) = registrations
1899                    .remove(validator)
1900                    .expect("validator should be registered");
1901                engine_handlers.push(engine.start(pending, recovered, resolver));
1902            }
1903
1904            // Wait for all engines to finish
1905            let mut finalizers = Vec::new();
1906            for reporter in reporters.iter_mut() {
1907                let (mut latest, mut monitor) = reporter.subscribe().await;
1908                finalizers.push(context.child("finalizer").spawn(move |_| async move {
1909                    while latest < required_containers {
1910                        latest = monitor.recv().await.expect("event missing");
1911                    }
1912                }));
1913            }
1914            join_all(finalizers).await;
1915
1916            // Degrade network connections for online peers
1917            let link = Link {
1918                latency: Duration::from_secs(3),
1919                jitter: Duration::from_millis(0),
1920                success_rate: 1.0,
1921            };
1922            link_validators(
1923                &mut oracle,
1924                &participants,
1925                Action::Update(link.clone()),
1926                Some(|_, i, j| ![i, j].contains(&0usize)),
1927            )
1928            .await;
1929
1930            // Wait for nullifications to accrue
1931            context.sleep(Duration::from_secs(60)).await;
1932
1933            // Unlink second peer from all (except first)
1934            link_validators(
1935                &mut oracle,
1936                &participants,
1937                Action::Unlink,
1938                Some(|_, i, j| [i, j].contains(&1usize) && ![i, j].contains(&0usize)),
1939            )
1940            .await;
1941
1942            // Configure engine for first peer
1943            let me = participants[0].clone();
1944            let context = context.child("validator").with_attribute("public_key", &me);
1945
1946            // Link first peer to all (except second)
1947            link_validators(
1948                &mut oracle,
1949                &participants,
1950                Action::Link(link),
1951                Some(|_, i, j| [i, j].contains(&0usize) && ![i, j].contains(&1usize)),
1952            )
1953            .await;
1954
1955            // Restore network connections for all online peers
1956            let link = Link {
1957                latency: Duration::from_millis(10),
1958                jitter: Duration::from_millis(3),
1959                success_rate: 1.0,
1960            };
1961            link_validators(
1962                &mut oracle,
1963                &participants,
1964                Action::Update(link),
1965                Some(|_, i, j| ![i, j].contains(&1usize)),
1966            )
1967            .await;
1968
1969            // Configure engine
1970            let reporter_config = mocks::reporter::Config {
1971                participants: participants.clone().try_into().unwrap(),
1972                scheme: schemes[0].clone(),
1973                elector: elector.clone(),
1974            };
1975            let mut reporter =
1976                mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
1977            reporters.push(reporter.clone());
1978            let application_cfg = mocks::application::Config {
1979                hasher: Sha256::default(),
1980                relay: relay.clone(),
1981                me: me.clone(),
1982                propose_latency: (10.0, 5.0),
1983                verify_latency: (10.0, 5.0),
1984                certify_latency: (10.0, 5.0),
1985                should_certify: mocks::application::Certifier::Always,
1986            };
1987            let (actor, application) =
1988                mocks::application::Application::new(context.child("application"), application_cfg);
1989            actor.start();
1990            let blocker = oracle.control(me.clone());
1991            let cfg = config::Config {
1992                scheme: schemes[0].clone(),
1993                elector: elector.clone(),
1994                blocker,
1995                automaton: application.clone(),
1996                relay: application.clone(),
1997                reporter: reporter.clone(),
1998                strategy: Sequential,
1999                partition: me.to_string(),
2000                mailbox_size: NZUsize!(1024),
2001                epoch: Epoch::new(333),
2002                floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(Epoch::new(
2003                    333,
2004                ))),
2005                leader_timeout: Duration::from_secs(1),
2006                certification_timeout: Duration::from_secs(2),
2007                timeout_retry: Duration::from_secs(10),
2008                fetch_timeout: Duration::from_secs(1),
2009                activity_timeout,
2010                skip_timeout,
2011                fetch_concurrent: NZUsize!(4),
2012                replay_buffer: NZUsize!(1024 * 1024),
2013                write_buffer: NZUsize!(1024 * 1024),
2014                page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2015                forwarding: ForwardingPolicy::Disabled,
2016            };
2017            let engine = Engine::new(context.child("engine"), cfg);
2018
2019            // Start engine
2020            let (pending, recovered, resolver) = registrations
2021                .remove(&me)
2022                .expect("validator should be registered");
2023            engine_handlers.push(engine.start(pending, recovered, resolver));
2024
2025            // Wait for new engine to finalize required
2026            let (mut latest, mut monitor) = reporter.subscribe().await;
2027            while latest < required_containers {
2028                latest = monitor.recv().await.expect("event missing");
2029            }
2030
2031            // Ensure no blocked connections
2032            let blocked = oracle.blocked().await.unwrap();
2033            assert!(blocked.is_empty());
2034        });
2035    }
2036
2037    test_for_all_fixtures!(backfill);
2038
2039    fn one_offline<S, F, L>(mut fixture: F)
2040    where
2041        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2042        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
2043        L: Elector<S>,
2044    {
2045        // Create context
2046        let n = 5;
2047        let quorum = quorum(n) as usize;
2048        let required_containers = View::new(100);
2049        let activity_timeout = ViewDelta::new(10);
2050        let skip_timeout = ViewDelta::new(5);
2051        let max_exceptions = 10;
2052        let namespace = b"consensus".to_vec();
2053        let executor = deterministic::Runner::timed(Duration::from_secs(300));
2054        executor.start(|mut context| async move {
2055            // Register participants
2056            let Fixture {
2057                participants,
2058                schemes,
2059                ..
2060            } = fixture(&mut context, &namespace, n);
2061            let mut oracle =
2062                start_test_network_with_peers(context.child("network"), participants.clone(), true)
2063                    .await;
2064            let mut registrations = register_validators(&mut oracle, &participants).await;
2065
2066            // Link all validators except first
2067            let link = Link {
2068                latency: Duration::from_millis(10),
2069                jitter: Duration::from_millis(1),
2070                success_rate: 1.0,
2071            };
2072            link_validators(
2073                &mut oracle,
2074                &participants,
2075                Action::Link(link),
2076                Some(|_, i, j| ![i, j].contains(&0usize)),
2077            )
2078            .await;
2079
2080            // Create engines
2081            let elector = L::default();
2082            let relay = Arc::new(mocks::relay::Relay::new());
2083            let mut reporters = Vec::new();
2084            let mut engine_handlers = Vec::new();
2085            for (idx_scheme, validator) in participants.iter().enumerate() {
2086                // Skip first peer
2087                if idx_scheme == 0 {
2088                    continue;
2089                }
2090
2091                // Create scheme context
2092                let context = context
2093                    .child("validator")
2094                    .with_attribute("public_key", validator);
2095
2096                // Configure engine
2097                let reporter_config = mocks::reporter::Config {
2098                    participants: participants.clone().try_into().unwrap(),
2099                    scheme: schemes[idx_scheme].clone(),
2100                    elector: elector.clone(),
2101                };
2102                let reporter =
2103                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2104                reporters.push(reporter.clone());
2105                let application_cfg = mocks::application::Config {
2106                    hasher: Sha256::default(),
2107                    relay: relay.clone(),
2108                    me: validator.clone(),
2109                    propose_latency: (10.0, 5.0),
2110                    verify_latency: (10.0, 5.0),
2111                    certify_latency: (10.0, 5.0),
2112                    should_certify: mocks::application::Certifier::Always,
2113                };
2114                let (actor, application) = mocks::application::Application::new(
2115                    context.child("application"),
2116                    application_cfg,
2117                );
2118                actor.start();
2119                let blocker = oracle.control(validator.clone());
2120                let cfg = config::Config {
2121                    scheme: schemes[idx_scheme].clone(),
2122                    elector: elector.clone(),
2123                    blocker,
2124                    automaton: application.clone(),
2125                    relay: application.clone(),
2126                    reporter: reporter.clone(),
2127                    strategy: Sequential,
2128                    partition: validator.to_string(),
2129                    mailbox_size: NZUsize!(1024),
2130                    epoch: Epoch::new(333),
2131                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2132                        Epoch::new(333),
2133                    )),
2134                    leader_timeout: Duration::from_secs(1),
2135                    certification_timeout: Duration::from_secs(2),
2136                    timeout_retry: Duration::from_secs(10),
2137                    fetch_timeout: Duration::from_secs(1),
2138                    activity_timeout,
2139                    skip_timeout,
2140                    fetch_concurrent: NZUsize!(4),
2141                    replay_buffer: NZUsize!(1024 * 1024),
2142                    write_buffer: NZUsize!(1024 * 1024),
2143                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2144                    forwarding: ForwardingPolicy::Disabled,
2145                };
2146                let engine = Engine::new(context.child("engine"), cfg);
2147
2148                // Start engine
2149                let (pending, recovered, resolver) = registrations
2150                    .remove(validator)
2151                    .expect("validator should be registered");
2152                engine_handlers.push(engine.start(pending, recovered, resolver));
2153            }
2154
2155            // Wait for all engines to finish
2156            let mut finalizers = Vec::new();
2157            for reporter in reporters.iter_mut() {
2158                let (mut latest, mut monitor) = reporter.subscribe().await;
2159                finalizers.push(context.child("finalizer").spawn(move |_| async move {
2160                    while latest < required_containers {
2161                        latest = monitor.recv().await.expect("event missing");
2162                    }
2163                }));
2164            }
2165            join_all(finalizers).await;
2166
2167            // Check reporters for correct activity
2168            let exceptions = 0;
2169            let offline = &participants[0];
2170            for reporter in reporters.iter() {
2171                // Ensure no faults
2172                reporter.assert_no_faults();
2173
2174                // Ensure no invalid signatures
2175                reporter.assert_no_invalid();
2176
2177                // Ensure offline node is never active
2178                let mut exceptions = 0;
2179                {
2180                    let notarizes = reporter.notarizes.lock();
2181                    for (view, payloads) in notarizes.iter() {
2182                        for participants in payloads.values() {
2183                            if participants.contains(offline) {
2184                                panic!("view: {view}");
2185                            }
2186                        }
2187                    }
2188                }
2189                {
2190                    let nullifies = reporter.nullifies.lock();
2191                    for (view, participants) in nullifies.iter() {
2192                        if participants.contains(offline) {
2193                            panic!("view: {view}");
2194                        }
2195                    }
2196                }
2197                {
2198                    let finalizes = reporter.finalizes.lock();
2199                    for (view, payloads) in finalizes.iter() {
2200                        for finalizers in payloads.values() {
2201                            if finalizers.contains(offline) {
2202                                panic!("view: {view}");
2203                            }
2204                        }
2205                    }
2206                }
2207
2208                // Identify offline views
2209                let mut offline_views = Vec::new();
2210                {
2211                    let leaders = reporter.leaders.lock();
2212                    for (view, leader) in leaders.iter() {
2213                        if leader == offline {
2214                            offline_views.push(*view);
2215                        }
2216                    }
2217                }
2218                assert!(!offline_views.is_empty());
2219
2220                // Ensure nullifies/nullification collected for offline node
2221                {
2222                    let nullifies = reporter.nullifies.lock();
2223                    for view in offline_views.iter() {
2224                        let nullifies = nullifies.get(view).map_or(0, |n| n.len());
2225                        if nullifies < quorum {
2226                            warn!("missing expected view nullifies: {}", view);
2227                            exceptions += 1;
2228                        }
2229                    }
2230                }
2231                {
2232                    let nullifications = reporter.nullifications.lock();
2233                    for view in offline_views.iter() {
2234                        if !nullifications.contains_key(view) {
2235                            warn!("missing expected view nullifies: {}", view);
2236                            exceptions += 1;
2237                        }
2238                    }
2239                }
2240
2241                // Ensure exceptions within allowed
2242                assert!(exceptions <= max_exceptions);
2243            }
2244            assert!(exceptions <= max_exceptions);
2245
2246            // Ensure no blocked connections
2247            let blocked = oracle.blocked().await.unwrap();
2248            assert!(blocked.is_empty());
2249
2250            // Ensure online nodes are recording timeouts/nullifications for the offline leader
2251            let encoded = context.encode();
2252            let leader_label = format!("leader=\"{}\"", offline);
2253            assert!(
2254                count_nonzero_metric_lines(&encoded, &["_timeouts", &leader_label]) >= n - 1,
2255                "expected timeout metrics for offline leader"
2256            );
2257            assert_eq!(
2258                count_nonzero_metric_lines(&encoded, &["_nullifications", &leader_label]),
2259                n - 1,
2260                "expected all online nodes to record _nullifications for offline leader"
2261            );
2262        });
2263    }
2264
2265    test_for_all_fixtures!(one_offline);
2266
2267    fn slow_validator<S, F, L>(mut fixture: F)
2268    where
2269        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2270        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
2271        L: Elector<S>,
2272    {
2273        // Create context
2274        let n = 5;
2275        let required_containers = View::new(50);
2276        let activity_timeout = ViewDelta::new(10);
2277        let skip_timeout = ViewDelta::new(5);
2278        let namespace = b"consensus".to_vec();
2279        let executor = deterministic::Runner::timed(Duration::from_secs(300));
2280        executor.start(|mut context| async move {
2281            // Register participants
2282            let Fixture {
2283                participants,
2284                schemes,
2285                ..
2286            } = fixture(&mut context, &namespace, n);
2287            let mut oracle =
2288                start_test_network_with_peers(context.child("network"), participants.clone(), true)
2289                    .await;
2290            let mut registrations = register_validators(&mut oracle, &participants).await;
2291
2292            // Link all validators
2293            let link = Link {
2294                latency: Duration::from_millis(10),
2295                jitter: Duration::from_millis(1),
2296                success_rate: 1.0,
2297            };
2298            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
2299
2300            // Create engines
2301            let elector = L::default();
2302            let relay = Arc::new(mocks::relay::Relay::new());
2303            let mut reporters = Vec::new();
2304            let mut engine_handlers = Vec::new();
2305            for (idx_scheme, validator) in participants.iter().enumerate() {
2306                // Create scheme context
2307                let context = context
2308                    .child("validator")
2309                    .with_attribute("public_key", validator);
2310
2311                // Configure engine
2312                let reporter_config = mocks::reporter::Config {
2313                    participants: participants.clone().try_into().unwrap(),
2314                    scheme: schemes[idx_scheme].clone(),
2315                    elector: elector.clone(),
2316                };
2317                let reporter =
2318                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2319                reporters.push(reporter.clone());
2320                let application_cfg = if idx_scheme == 0 {
2321                    mocks::application::Config {
2322                        hasher: Sha256::default(),
2323                        relay: relay.clone(),
2324                        me: validator.clone(),
2325                        propose_latency: (10_000.0, 0.0),
2326                        verify_latency: (10_000.0, 5.0),
2327                        certify_latency: (10_000.0, 5.0),
2328                        should_certify: mocks::application::Certifier::Always,
2329                    }
2330                } else {
2331                    mocks::application::Config {
2332                        hasher: Sha256::default(),
2333                        relay: relay.clone(),
2334                        me: validator.clone(),
2335                        propose_latency: (10.0, 5.0),
2336                        verify_latency: (10.0, 5.0),
2337                        certify_latency: (10.0, 5.0),
2338                        should_certify: mocks::application::Certifier::Always,
2339                    }
2340                };
2341                let (actor, application) = mocks::application::Application::new(
2342                    context.child("application"),
2343                    application_cfg,
2344                );
2345                actor.start();
2346                let blocker = oracle.control(validator.clone());
2347                let cfg = config::Config {
2348                    scheme: schemes[idx_scheme].clone(),
2349                    elector: elector.clone(),
2350                    blocker,
2351                    automaton: application.clone(),
2352                    relay: application.clone(),
2353                    reporter: reporter.clone(),
2354                    strategy: Sequential,
2355                    partition: validator.to_string(),
2356                    mailbox_size: NZUsize!(1024),
2357                    epoch: Epoch::new(333),
2358                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2359                        Epoch::new(333),
2360                    )),
2361                    leader_timeout: Duration::from_secs(1),
2362                    certification_timeout: Duration::from_secs(2),
2363                    timeout_retry: Duration::from_secs(10),
2364                    fetch_timeout: Duration::from_secs(1),
2365                    activity_timeout,
2366                    skip_timeout,
2367                    fetch_concurrent: NZUsize!(4),
2368                    replay_buffer: NZUsize!(1024 * 1024),
2369                    write_buffer: NZUsize!(1024 * 1024),
2370                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2371                    forwarding: ForwardingPolicy::Disabled,
2372                };
2373                let engine = Engine::new(context.child("engine"), cfg);
2374
2375                // Start engine
2376                let (pending, recovered, resolver) = registrations
2377                    .remove(validator)
2378                    .expect("validator should be registered");
2379                engine_handlers.push(engine.start(pending, recovered, resolver));
2380            }
2381
2382            // Wait for all engines to finish
2383            let mut finalizers = Vec::new();
2384            for reporter in reporters.iter_mut() {
2385                let (mut latest, mut monitor) = reporter.subscribe().await;
2386                finalizers.push(context.child("finalizer").spawn(move |_| async move {
2387                    while latest < required_containers {
2388                        latest = monitor.recv().await.expect("event missing");
2389                    }
2390                }));
2391            }
2392            join_all(finalizers).await;
2393
2394            // Check reporters for correct activity
2395            let slow = &participants[0];
2396            for reporter in reporters.iter() {
2397                // Ensure no faults
2398                reporter.assert_no_faults();
2399
2400                // Ensure no invalid signatures
2401                reporter.assert_no_invalid();
2402
2403                // Ensure the slow validator never emits notarize or finalize
2404                // votes. It may still emit nullifies after timing out.
2405                {
2406                    let notarizes = reporter.notarizes.lock();
2407                    assert!(notarizes.values().all(|payloads| {
2408                        payloads
2409                            .values()
2410                            .all(|participants| !participants.contains(slow))
2411                    }));
2412                }
2413                {
2414                    let finalizes = reporter.finalizes.lock();
2415                    assert!(finalizes.values().all(|payloads| {
2416                        payloads
2417                            .values()
2418                            .all(|participants| !participants.contains(slow))
2419                    }));
2420                }
2421
2422                // Ensure every reporter observes finalization progress to at least the target view.
2423                {
2424                    let finalizations = reporter.finalizations.lock();
2425                    assert!(finalizations
2426                        .keys()
2427                        .any(|view| *view >= required_containers));
2428                }
2429            }
2430
2431            // Ensure no blocked connections
2432            let blocked = oracle.blocked().await.unwrap();
2433            assert!(blocked.is_empty());
2434        });
2435    }
2436
2437    test_for_all_fixtures!(slow_validator);
2438
2439    fn all_recovery<S, F, L>(mut fixture: F)
2440    where
2441        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2442        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
2443        L: Elector<S>,
2444    {
2445        // Create context
2446        let n = 5;
2447        let required_containers = View::new(100);
2448        let activity_timeout = ViewDelta::new(10);
2449        let skip_timeout = ViewDelta::new(2);
2450        let namespace = b"consensus".to_vec();
2451        let executor = deterministic::Runner::timed(Duration::from_secs(1800));
2452        executor.start(|mut context| async move {
2453            // Register participants
2454            let Fixture {
2455                participants,
2456                schemes,
2457                ..
2458            } = fixture(&mut context, &namespace, n);
2459            let mut oracle =
2460                start_test_network_with_peers(context.child("network"), participants.clone(), true)
2461                    .await;
2462            let mut registrations = register_validators(&mut oracle, &participants).await;
2463
2464            // Link all validators
2465            let link = Link {
2466                latency: Duration::from_secs(3),
2467                jitter: Duration::from_millis(0),
2468                success_rate: 1.0,
2469            };
2470            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
2471
2472            // Create engines
2473            let elector = L::default();
2474            let relay = Arc::new(mocks::relay::Relay::new());
2475            let mut reporters = Vec::new();
2476            let mut engine_handlers = Vec::new();
2477            for (idx, validator) in participants.iter().enumerate() {
2478                // Create scheme context
2479                let context = context
2480                    .child("validator")
2481                    .with_attribute("public_key", validator);
2482
2483                // Configure engine
2484                let reporter_config = mocks::reporter::Config {
2485                    participants: participants.clone().try_into().unwrap(),
2486                    scheme: schemes[idx].clone(),
2487                    elector: elector.clone(),
2488                };
2489                let reporter =
2490                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2491                reporters.push(reporter.clone());
2492                let application_cfg = mocks::application::Config {
2493                    hasher: Sha256::default(),
2494                    relay: relay.clone(),
2495                    me: validator.clone(),
2496                    propose_latency: (10.0, 5.0),
2497                    verify_latency: (10.0, 5.0),
2498                    certify_latency: (10.0, 5.0),
2499                    should_certify: mocks::application::Certifier::Always,
2500                };
2501                let (actor, application) = mocks::application::Application::new(
2502                    context.child("application"),
2503                    application_cfg,
2504                );
2505                actor.start();
2506                let blocker = oracle.control(validator.clone());
2507                let cfg = config::Config {
2508                    scheme: schemes[idx].clone(),
2509                    elector: elector.clone(),
2510                    blocker,
2511                    automaton: application.clone(),
2512                    relay: application.clone(),
2513                    reporter: reporter.clone(),
2514                    strategy: Sequential,
2515                    partition: validator.to_string(),
2516                    mailbox_size: NZUsize!(1024),
2517                    epoch: Epoch::new(333),
2518                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2519                        Epoch::new(333),
2520                    )),
2521                    leader_timeout: Duration::from_secs(1),
2522                    certification_timeout: Duration::from_secs(2),
2523                    timeout_retry: Duration::from_secs(10),
2524                    fetch_timeout: Duration::from_secs(1),
2525                    activity_timeout,
2526                    skip_timeout,
2527                    fetch_concurrent: NZUsize!(4),
2528                    replay_buffer: NZUsize!(1024 * 1024),
2529                    write_buffer: NZUsize!(1024 * 1024),
2530                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2531                    forwarding: ForwardingPolicy::Disabled,
2532                };
2533                let engine = Engine::new(context.child("engine"), cfg);
2534
2535                // Start engine
2536                let (pending, recovered, resolver) = registrations
2537                    .remove(validator)
2538                    .expect("validator should be registered");
2539                engine_handlers.push(engine.start(pending, recovered, resolver));
2540            }
2541
2542            // Wait for a few virtual minutes (shouldn't finalize anything)
2543            let mut finalizers = Vec::new();
2544            for reporter in reporters.iter_mut() {
2545                let (_, mut monitor) = reporter.subscribe().await;
2546                finalizers.push(context.child("finalizer").spawn(move |context| async move {
2547                    select! {
2548                        _timeout = context.sleep(Duration::from_secs(60)) => {},
2549                        _done = monitor.recv() => {
2550                            panic!("engine should not notarize or finalize anything");
2551                        },
2552                    }
2553                }));
2554            }
2555            join_all(finalizers).await;
2556
2557            // Unlink all validators to get latest view
2558            link_validators(&mut oracle, &participants, Action::Unlink, None).await;
2559
2560            // Wait for a virtual minute (nothing should happen)
2561            context.sleep(Duration::from_secs(60)).await;
2562
2563            // Get latest view
2564            let mut latest = View::zero();
2565            for reporter in reporters.iter() {
2566                let nullifies = reporter.nullifies.lock();
2567                let max = nullifies.keys().max().unwrap();
2568                if *max > latest {
2569                    latest = *max;
2570                }
2571            }
2572
2573            // Update links
2574            let link = Link {
2575                latency: Duration::from_millis(10),
2576                jitter: Duration::from_millis(1),
2577                success_rate: 1.0,
2578            };
2579            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
2580
2581            // Wait for all engines to finish
2582            let mut finalizers = Vec::new();
2583            for reporter in reporters.iter_mut() {
2584                let (mut latest, mut monitor) = reporter.subscribe().await;
2585                finalizers.push(context.child("finalizer").spawn(move |_| async move {
2586                    while latest < required_containers {
2587                        latest = monitor.recv().await.expect("event missing");
2588                    }
2589                }));
2590            }
2591            join_all(finalizers).await;
2592
2593            // Check reporters for correct activity
2594            for reporter in reporters.iter() {
2595                // Ensure no faults
2596                reporter.assert_no_faults();
2597
2598                // Ensure no invalid signatures
2599                reporter.assert_no_invalid();
2600
2601                // Ensure quick recovery.
2602                //
2603                // If the skip timeout isn't implemented correctly, we may go many views before participants
2604                // start to notarize a validator's proposal.
2605                {
2606                    // Ensure nearly all views around latest are notarized.
2607                    // We don't check for finalization since some of the blocks may fail to be
2608                    // certified for the purposes of testing.
2609                    let mut found = 0;
2610                    let notarizations = reporter.notarizations.lock();
2611                    for view in View::range(latest, latest.saturating_add(activity_timeout)) {
2612                        if notarizations.contains_key(&view) {
2613                            found += 1;
2614                        }
2615                    }
2616                    let tolerated_missing = skip_timeout.get().saturating_add(1);
2617                    assert!(
2618                        found >= activity_timeout.get().saturating_sub(tolerated_missing),
2619                        "found: {found}"
2620                    );
2621                }
2622            }
2623
2624            // Ensure no blocked connections
2625            let blocked = oracle.blocked().await.unwrap();
2626            assert!(blocked.is_empty());
2627        });
2628    }
2629
2630    test_for_all_fixtures!(all_recovery);
2631
2632    fn all_crash_after_nullify<S, F, L>(mut fixture: F)
2633    where
2634        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2635        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
2636        L: Elector<S>,
2637    {
2638        // Create context
2639        let n = 4;
2640        let required_containers = View::new(10);
2641        let activity_timeout = ViewDelta::new(10);
2642        let skip_timeout = ViewDelta::new(5);
2643        let namespace = b"consensus".to_vec();
2644        let executor = deterministic::Runner::timed(Duration::from_secs(3600));
2645        executor.start(|mut context| async move {
2646            // Register participants
2647            let Fixture {
2648                participants,
2649                schemes,
2650                ..
2651            } = fixture(&mut context, &namespace, n);
2652            let mut oracle =
2653                start_test_network_with_peers(context.child("network"), participants.clone(), true)
2654                    .await;
2655            let mut registrations = register_validators(&mut oracle, &participants).await;
2656
2657            // Participant 0 never starts an engine and no links exist yet, so no
2658            // view can produce a certificate before the crash below.
2659            let elector = L::default();
2660            let relay = Arc::new(mocks::relay::Relay::new());
2661            let mut reporters = Vec::new();
2662            let mut engine_handlers = Vec::new();
2663            for (idx_scheme, validator) in participants.iter().enumerate() {
2664                // Skip first peer
2665                if idx_scheme == 0 {
2666                    continue;
2667                }
2668
2669                // Create scheme context
2670                let context = context
2671                    .child("validator")
2672                    .with_attribute("public_key", validator);
2673
2674                // Configure engine
2675                let reporter_config = mocks::reporter::Config {
2676                    participants: participants.clone().try_into().unwrap(),
2677                    scheme: schemes[idx_scheme].clone(),
2678                    elector: elector.clone(),
2679                };
2680                let reporter =
2681                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2682                reporters.push(reporter.clone());
2683                let application_cfg = mocks::application::Config {
2684                    hasher: Sha256::default(),
2685                    relay: relay.clone(),
2686                    me: validator.clone(),
2687                    propose_latency: (10.0, 5.0),
2688                    verify_latency: (10.0, 5.0),
2689                    certify_latency: (10.0, 5.0),
2690                    should_certify: mocks::application::Certifier::Always,
2691                };
2692                let (actor, application) = mocks::application::Application::new(
2693                    context.child("application"),
2694                    application_cfg,
2695                );
2696                actor.start();
2697                let blocker = oracle.control(validator.clone());
2698                let cfg = config::Config {
2699                    scheme: schemes[idx_scheme].clone(),
2700                    elector: elector.clone(),
2701                    blocker,
2702                    automaton: application.clone(),
2703                    relay: application.clone(),
2704                    reporter: reporter.clone(),
2705                    strategy: Sequential,
2706                    partition: validator.to_string(),
2707                    mailbox_size: NZUsize!(1024),
2708                    epoch: Epoch::new(333),
2709                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2710                        Epoch::new(333),
2711                    )),
2712                    leader_timeout: Duration::from_secs(1),
2713                    certification_timeout: Duration::from_secs(2),
2714                    timeout_retry: Duration::from_secs(10),
2715                    fetch_timeout: Duration::from_secs(1),
2716                    activity_timeout,
2717                    skip_timeout,
2718                    fetch_concurrent: NZUsize!(4),
2719                    replay_buffer: NZUsize!(1024 * 1024),
2720                    write_buffer: NZUsize!(1024 * 1024),
2721                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2722                    forwarding: ForwardingPolicy::Disabled,
2723                };
2724                let engine = Engine::new(context.child("engine"), cfg);
2725
2726                // Start engine
2727                let (pending, recovered, resolver) = registrations
2728                    .remove(validator)
2729                    .expect("validator should be registered");
2730                engine_handlers.push(engine.start(pending, recovered, resolver));
2731            }
2732
2733            // Wait for every online validator to construct its nullify vote for
2734            // view 1.
2735            let stalled = View::new(1);
2736            loop {
2737                let nullified = reporters.iter().zip(participants.iter().skip(1)).all(
2738                    |(reporter, validator)| {
2739                        reporter
2740                            .nullifies
2741                            .lock()
2742                            .get(&stalled)
2743                            .is_some_and(|nullifiers| nullifiers.contains(validator))
2744                    },
2745                );
2746                if nullified {
2747                    break;
2748                }
2749                context.sleep(Duration::from_millis(100)).await;
2750            }
2751
2752            // The reporter observes our vote via the batcher, which can run ahead
2753            // of the voter's journal sync in the same instant. Wait one more tick
2754            // so every vote is durable before crashing.
2755            context.sleep(Duration::from_secs(1)).await;
2756
2757            // Crash every online validator before any nullification certificate
2758            // can circulate.
2759            for handle in engine_handlers.drain(..) {
2760                handle.abort();
2761                let _ = handle.await;
2762            }
2763            relay.deregister_all();
2764
2765            // Restore connectivity between the online validators.
2766            let link = Link {
2767                latency: Duration::from_millis(10),
2768                jitter: Duration::from_millis(1),
2769                success_rate: 1.0,
2770            };
2771            link_validators(
2772                &mut oracle,
2773                &participants,
2774                Action::Link(link),
2775                Some(|_, i, j| ![i, j].contains(&0usize)),
2776            )
2777            .await;
2778
2779            // Restart every online validator from its journal.
2780            let mut reporters = Vec::new();
2781            for (idx_scheme, validator) in participants.iter().enumerate() {
2782                // Skip first peer
2783                if idx_scheme == 0 {
2784                    continue;
2785                }
2786
2787                // Create scheme context
2788                let context = context
2789                    .child("validator_restarted")
2790                    .with_attribute("public_key", validator);
2791
2792                // Configure engine
2793                let reporter_config = mocks::reporter::Config {
2794                    participants: participants.clone().try_into().unwrap(),
2795                    scheme: schemes[idx_scheme].clone(),
2796                    elector: elector.clone(),
2797                };
2798                let reporter =
2799                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2800                reporters.push(reporter.clone());
2801                let application_cfg = mocks::application::Config {
2802                    hasher: Sha256::default(),
2803                    relay: relay.clone(),
2804                    me: validator.clone(),
2805                    propose_latency: (10.0, 5.0),
2806                    verify_latency: (10.0, 5.0),
2807                    certify_latency: (10.0, 5.0),
2808                    should_certify: mocks::application::Certifier::Always,
2809                };
2810                let (actor, application) = mocks::application::Application::new(
2811                    context.child("application"),
2812                    application_cfg,
2813                );
2814                actor.start();
2815                let blocker = oracle.control(validator.clone());
2816                let cfg = config::Config {
2817                    scheme: schemes[idx_scheme].clone(),
2818                    elector: elector.clone(),
2819                    blocker,
2820                    automaton: application.clone(),
2821                    relay: application.clone(),
2822                    reporter: reporter.clone(),
2823                    strategy: Sequential,
2824                    partition: validator.to_string(),
2825                    mailbox_size: NZUsize!(1024),
2826                    epoch: Epoch::new(333),
2827                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2828                        Epoch::new(333),
2829                    )),
2830                    leader_timeout: Duration::from_secs(1),
2831                    certification_timeout: Duration::from_secs(2),
2832                    timeout_retry: Duration::from_secs(10),
2833                    fetch_timeout: Duration::from_secs(1),
2834                    activity_timeout,
2835                    skip_timeout,
2836                    fetch_concurrent: NZUsize!(4),
2837                    replay_buffer: NZUsize!(1024 * 1024),
2838                    write_buffer: NZUsize!(1024 * 1024),
2839                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2840                    forwarding: ForwardingPolicy::Disabled,
2841                };
2842                let engine = Engine::new(context.child("engine"), cfg);
2843
2844                // Start engine
2845                let (pending, recovered, resolver) =
2846                    register_validator(&mut oracle, validator.clone()).await;
2847                engine.start(pending, recovered, resolver);
2848            }
2849
2850            // The restarted validators must reconstruct a nullification for view 1
2851            // to make progress (participant 0 never votes, so every remaining vote
2852            // is required to reach quorum).
2853            let mut finalizers = Vec::new();
2854            for reporter in reporters.iter_mut() {
2855                let (mut latest, mut monitor) = reporter.subscribe().await;
2856                finalizers.push(context.child("finalizer").spawn(move |_| async move {
2857                    while latest < required_containers {
2858                        latest = monitor.recv().await.expect("event missing");
2859                    }
2860                }));
2861            }
2862            join_all(finalizers).await;
2863        });
2864    }
2865
2866    test_for_all_fixtures!(all_crash_after_nullify);
2867
2868    fn partition<S, F, L>(mut fixture: F)
2869    where
2870        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2871        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
2872        L: Elector<S>,
2873    {
2874        // Create context
2875        let n = 10;
2876        let required_containers = View::new(50);
2877        let activity_timeout = ViewDelta::new(10);
2878        let skip_timeout = ViewDelta::new(5);
2879        let namespace = b"consensus".to_vec();
2880        let executor = deterministic::Runner::timed(Duration::from_secs(900));
2881        executor.start(|mut context| async move {
2882            // Register participants
2883            let Fixture {
2884                participants,
2885                schemes,
2886                ..
2887            } = fixture(&mut context, &namespace, n);
2888            let mut oracle =
2889                start_test_network_with_peers(context.child("network"), participants.clone(), true)
2890                    .await;
2891            let mut registrations = register_validators(&mut oracle, &participants).await;
2892
2893            // Link all validators
2894            let link = Link {
2895                latency: Duration::from_millis(10),
2896                jitter: Duration::from_millis(1),
2897                success_rate: 1.0,
2898            };
2899            link_validators(&mut oracle, &participants, Action::Link(link.clone()), None).await;
2900
2901            // Create engines
2902            let elector = L::default();
2903            let relay = Arc::new(mocks::relay::Relay::new());
2904            let mut reporters = Vec::new();
2905            let mut engine_handlers = Vec::new();
2906            for (idx, validator) in participants.iter().enumerate() {
2907                // Create scheme context
2908                let context = context
2909                    .child("validator")
2910                    .with_attribute("public_key", validator);
2911
2912                // Configure engine
2913                let reporter_config = mocks::reporter::Config {
2914                    participants: participants.clone().try_into().unwrap(),
2915                    scheme: schemes[idx].clone(),
2916                    elector: elector.clone(),
2917                };
2918                let reporter =
2919                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2920                reporters.push(reporter.clone());
2921                let application_cfg = mocks::application::Config {
2922                    hasher: Sha256::default(),
2923                    relay: relay.clone(),
2924                    me: validator.clone(),
2925                    propose_latency: (10.0, 5.0),
2926                    verify_latency: (10.0, 5.0),
2927                    certify_latency: (10.0, 5.0),
2928                    should_certify: mocks::application::Certifier::Always,
2929                };
2930                let (actor, application) = mocks::application::Application::new(
2931                    context.child("application"),
2932                    application_cfg,
2933                );
2934                actor.start();
2935                let blocker = oracle.control(validator.clone());
2936                let cfg = config::Config {
2937                    scheme: schemes[idx].clone(),
2938                    elector: elector.clone(),
2939                    blocker,
2940                    automaton: application.clone(),
2941                    relay: application.clone(),
2942                    reporter: reporter.clone(),
2943                    strategy: Sequential,
2944                    partition: validator.to_string(),
2945                    mailbox_size: NZUsize!(1024),
2946                    epoch: Epoch::new(333),
2947                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2948                        Epoch::new(333),
2949                    )),
2950                    leader_timeout: Duration::from_secs(1),
2951                    certification_timeout: Duration::from_secs(2),
2952                    timeout_retry: Duration::from_secs(10),
2953                    fetch_timeout: Duration::from_secs(1),
2954                    activity_timeout,
2955                    skip_timeout,
2956                    fetch_concurrent: NZUsize!(4),
2957                    replay_buffer: NZUsize!(1024 * 1024),
2958                    write_buffer: NZUsize!(1024 * 1024),
2959                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2960                    forwarding: ForwardingPolicy::Disabled,
2961                };
2962                let engine = Engine::new(context.child("engine"), cfg);
2963
2964                // Start engine
2965                let (pending, recovered, resolver) = registrations
2966                    .remove(validator)
2967                    .expect("validator should be registered");
2968                engine_handlers.push(engine.start(pending, recovered, resolver));
2969            }
2970
2971            // Wait for all engines to finish
2972            let mut finalizers = Vec::new();
2973            for reporter in reporters.iter_mut() {
2974                let (mut latest, mut monitor) = reporter.subscribe().await;
2975                finalizers.push(context.child("finalizer").spawn(move |_| async move {
2976                    while latest < required_containers {
2977                        latest = monitor.recv().await.expect("event missing");
2978                    }
2979                }));
2980            }
2981            join_all(finalizers).await;
2982
2983            // Cut all links between validator halves
2984            fn separated(n: usize, a: usize, b: usize) -> bool {
2985                let m = n / 2;
2986                (a < m && b >= m) || (a >= m && b < m)
2987            }
2988            link_validators(&mut oracle, &participants, Action::Unlink, Some(separated)).await;
2989
2990            // Wait for any in-progress notarizations/finalizations to finish
2991            context.sleep(Duration::from_secs(10)).await;
2992
2993            // Wait for a few virtual minutes (shouldn't finalize anything)
2994            let mut finalizers = Vec::new();
2995            for reporter in reporters.iter_mut() {
2996                let (_, mut monitor) = reporter.subscribe().await;
2997                finalizers.push(context.child("finalizer").spawn(move |context| async move {
2998                    select! {
2999                        _timeout = context.sleep(Duration::from_secs(60)) => {},
3000                        _done = monitor.recv() => {
3001                            panic!("engine should not notarize or finalize anything");
3002                        },
3003                    }
3004                }));
3005            }
3006            join_all(finalizers).await;
3007
3008            // Restore links
3009            link_validators(
3010                &mut oracle,
3011                &participants,
3012                Action::Link(link),
3013                Some(separated),
3014            )
3015            .await;
3016
3017            // Wait for all engines to finish
3018            let mut finalizers = Vec::new();
3019            for reporter in reporters.iter_mut() {
3020                let (mut latest, mut monitor) = reporter.subscribe().await;
3021                let required = latest.saturating_add(ViewDelta::new(required_containers.get()));
3022                finalizers.push(context.child("finalizer").spawn(move |_| async move {
3023                    while latest < required {
3024                        latest = monitor.recv().await.expect("event missing");
3025                    }
3026                }));
3027            }
3028            join_all(finalizers).await;
3029
3030            // Check reporters for correct activity
3031            for reporter in reporters.iter() {
3032                // Ensure no faults
3033                reporter.assert_no_faults();
3034
3035                // Ensure no invalid signatures
3036                reporter.assert_no_invalid();
3037            }
3038
3039            // Ensure no blocked connections
3040            let blocked = oracle.blocked().await.unwrap();
3041            assert!(blocked.is_empty());
3042        });
3043    }
3044
3045    test_for_all_fixtures!(partition);
3046
3047    fn slow_and_lossy_links_seeded<S, F, L>(seed: u64, mut fixture: F) -> String
3048    where
3049        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3050        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3051        L: Elector<S>,
3052    {
3053        // Create context
3054        let n = 5;
3055        let required_containers = View::new(50);
3056        let activity_timeout = ViewDelta::new(10);
3057        let skip_timeout = ViewDelta::new(5);
3058        let namespace = b"consensus".to_vec();
3059        let cfg = deterministic::Config::new()
3060            .with_seed(seed)
3061            .with_timeout(Some(Duration::from_secs(5_000)));
3062        let executor = deterministic::Runner::new(cfg);
3063        executor.start(|mut context| async move {
3064            // Register participants
3065            let Fixture {
3066                participants,
3067                schemes,
3068                ..
3069            } = fixture(&mut context, &namespace, n);
3070            let mut oracle =
3071                start_test_network_with_peers(context.child("network"), participants.clone(), true)
3072                    .await;
3073            let mut registrations = register_validators(&mut oracle, &participants).await;
3074
3075            // Link all validators
3076            let degraded_link = Link {
3077                latency: Duration::from_millis(200),
3078                jitter: Duration::from_millis(150),
3079                success_rate: 0.5,
3080            };
3081            link_validators(
3082                &mut oracle,
3083                &participants,
3084                Action::Link(degraded_link),
3085                None,
3086            )
3087            .await;
3088
3089            // Create engines
3090            let elector = L::default();
3091            let relay = Arc::new(mocks::relay::Relay::new());
3092            let mut reporters = Vec::new();
3093            let mut engine_handlers = Vec::new();
3094            for (idx, validator) in participants.iter().enumerate() {
3095                // Create scheme context
3096                let context = context
3097                    .child("validator")
3098                    .with_attribute("public_key", validator);
3099
3100                // Configure engine
3101                let reporter_config = mocks::reporter::Config {
3102                    participants: participants.clone().try_into().unwrap(),
3103                    scheme: schemes[idx].clone(),
3104                    elector: elector.clone(),
3105                };
3106                let reporter =
3107                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3108                reporters.push(reporter.clone());
3109                let application_cfg = mocks::application::Config {
3110                    hasher: Sha256::default(),
3111                    relay: relay.clone(),
3112                    me: validator.clone(),
3113                    propose_latency: (10.0, 5.0),
3114                    verify_latency: (10.0, 5.0),
3115                    certify_latency: (10.0, 5.0),
3116                    should_certify: mocks::application::Certifier::Always,
3117                };
3118                let (actor, application) = mocks::application::Application::new(
3119                    context.child("application"),
3120                    application_cfg,
3121                );
3122                actor.start();
3123                let blocker = oracle.control(validator.clone());
3124                let cfg = config::Config {
3125                    scheme: schemes[idx].clone(),
3126                    elector: elector.clone(),
3127                    blocker,
3128                    automaton: application.clone(),
3129                    relay: application.clone(),
3130                    reporter: reporter.clone(),
3131                    strategy: Sequential,
3132                    partition: validator.to_string(),
3133                    mailbox_size: NZUsize!(1024),
3134                    epoch: Epoch::new(333),
3135                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3136                        Epoch::new(333),
3137                    )),
3138                    leader_timeout: Duration::from_secs(1),
3139                    certification_timeout: Duration::from_secs(2),
3140                    timeout_retry: Duration::from_secs(10),
3141                    fetch_timeout: Duration::from_secs(1),
3142                    activity_timeout,
3143                    skip_timeout,
3144                    fetch_concurrent: NZUsize!(4),
3145                    replay_buffer: NZUsize!(1024 * 1024),
3146                    write_buffer: NZUsize!(1024 * 1024),
3147                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3148                    forwarding: ForwardingPolicy::Disabled,
3149                };
3150                let engine = Engine::new(context.child("engine"), cfg);
3151
3152                // Start engine
3153                let (pending, recovered, resolver) = registrations
3154                    .remove(validator)
3155                    .expect("validator should be registered");
3156                engine_handlers.push(engine.start(pending, recovered, resolver));
3157            }
3158
3159            // Wait for all engines to finish
3160            let mut finalizers = Vec::new();
3161            for reporter in reporters.iter_mut() {
3162                let (mut latest, mut monitor) = reporter.subscribe().await;
3163                finalizers.push(context.child("finalizer").spawn(move |_| async move {
3164                    while latest < required_containers {
3165                        latest = monitor.recv().await.expect("event missing");
3166                    }
3167                }));
3168            }
3169            join_all(finalizers).await;
3170
3171            // Check reporters for correct activity
3172            for reporter in reporters.iter() {
3173                // Ensure no faults
3174                reporter.assert_no_faults();
3175
3176                // Ensure no invalid signatures
3177                reporter.assert_no_invalid();
3178            }
3179
3180            // Ensure no blocked connections
3181            let blocked = oracle.blocked().await.unwrap();
3182            assert!(blocked.is_empty());
3183
3184            context.auditor().state()
3185        })
3186    }
3187
3188    fn slow_and_lossy_links<S, F, L>(fixture: F) -> String
3189    where
3190        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3191        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3192        L: Elector<S>,
3193    {
3194        slow_and_lossy_links_seeded::<_, _, L>(6, fixture)
3195    }
3196
3197    test_for_all_fixtures!(slow_and_lossy_links);
3198
3199    fn determinism<S, F, L>(seed: u64, fixture: F)
3200    where
3201        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3202        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S> + Copy,
3203        L: Elector<S>,
3204    {
3205        // We use slow and lossy links as the deterministic test
3206        // because it is the most complex test.
3207        assert_eq!(
3208            slow_and_lossy_links_seeded::<_, _, L>(seed, fixture),
3209            slow_and_lossy_links_seeded::<_, _, L>(seed, fixture),
3210        );
3211    }
3212
3213    test_for_all_fixtures!(determinism, seeds = 5);
3214
3215    #[test_group("slow")]
3216    #[test_traced]
3217    fn test_distinct_states() {
3218        // Sanity check that different schemes produce different audit states.
3219        macro_rules! collect {
3220            ($vec:ident, $suffix:ident, $elector:ty, $fixture:expr) => {
3221                $vec.push((
3222                    stringify!($suffix),
3223                    slow_and_lossy_links_seeded::<_, _, $elector>(7, $fixture),
3224                ));
3225            };
3226        }
3227        let mut states = Vec::new();
3228        for_each_fixture!(collect!(states));
3229        for pair in states.windows(2) {
3230            assert_ne!(
3231                pair[0].1, pair[1].1,
3232                "state {} equals state {}",
3233                pair[0].0, pair[1].0
3234            );
3235        }
3236    }
3237
3238    fn conflicter<S, F, L>(seed: u64, mut fixture: F)
3239    where
3240        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3241        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3242        L: Elector<S>,
3243    {
3244        // Create context
3245        let n = 4;
3246        let required_containers = View::new(50);
3247        let activity_timeout = ViewDelta::new(10);
3248        let skip_timeout = ViewDelta::new(5);
3249        let namespace = b"consensus".to_vec();
3250        let cfg = deterministic::Config::new()
3251            .with_seed(seed)
3252            .with_timeout(Some(Duration::from_secs(30)));
3253        let executor = deterministic::Runner::new(cfg);
3254        executor.start(|mut context| async move {
3255            // Register participants
3256            let Fixture {
3257                participants,
3258                schemes,
3259                ..
3260            } = fixture(&mut context, &namespace, n);
3261            let mut oracle =
3262                start_test_network_with_peers(context.child("network"), participants.clone(), true)
3263                    .await;
3264            let mut registrations = register_validators(&mut oracle, &participants).await;
3265
3266            // Link all validators
3267            let link = Link {
3268                latency: Duration::from_millis(10),
3269                jitter: Duration::from_millis(1),
3270                success_rate: 1.0,
3271            };
3272            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
3273
3274            // Create engines
3275            let elector = L::default();
3276            let relay = Arc::new(mocks::relay::Relay::new());
3277            let mut reporters = Vec::new();
3278            for (idx_scheme, validator) in participants.iter().enumerate() {
3279                // Create scheme context
3280                let context = context
3281                    .child("validator")
3282                    .with_attribute("public_key", validator);
3283
3284                // Start engine
3285                let reporter_config = mocks::reporter::Config {
3286                    participants: participants.clone().try_into().unwrap(),
3287                    scheme: schemes[idx_scheme].clone(),
3288                    elector: elector.clone(),
3289                };
3290                let reporter =
3291                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3292                let (pending, recovered, resolver) = registrations
3293                    .remove(validator)
3294                    .expect("validator should be registered");
3295                if idx_scheme == 0 {
3296                    let cfg = mocks::conflicter::Config {
3297                        scheme: schemes[idx_scheme].clone(),
3298                    };
3299
3300                    let engine: mocks::conflicter::Conflicter<_, _, Sha256> =
3301                        mocks::conflicter::Conflicter::new(context.child("byzantine_engine"), cfg);
3302                    engine.start(pending);
3303                } else {
3304                    reporters.push(reporter.clone());
3305                    let application_cfg = mocks::application::Config {
3306                        hasher: Sha256::default(),
3307                        relay: relay.clone(),
3308                        me: validator.clone(),
3309                        propose_latency: (10.0, 5.0),
3310                        verify_latency: (10.0, 5.0),
3311                        certify_latency: (10.0, 5.0),
3312                        should_certify: mocks::application::Certifier::Always,
3313                    };
3314                    let (actor, application) = mocks::application::Application::new(
3315                        context.child("application"),
3316                        application_cfg,
3317                    );
3318                    actor.start();
3319                    let blocker = oracle.control(validator.clone());
3320                    let cfg = config::Config {
3321                        scheme: schemes[idx_scheme].clone(),
3322                        elector: elector.clone(),
3323                        blocker,
3324                        automaton: application.clone(),
3325                        relay: application.clone(),
3326                        reporter: reporter.clone(),
3327                        strategy: Sequential,
3328                        partition: validator.to_string(),
3329                        mailbox_size: NZUsize!(1024),
3330                        epoch: Epoch::new(333),
3331                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3332                            Epoch::new(333),
3333                        )),
3334                        leader_timeout: Duration::from_secs(1),
3335                        certification_timeout: Duration::from_secs(2),
3336                        timeout_retry: Duration::from_secs(10),
3337                        fetch_timeout: Duration::from_secs(1),
3338                        activity_timeout,
3339                        skip_timeout,
3340                        fetch_concurrent: NZUsize!(4),
3341                        replay_buffer: NZUsize!(1024 * 1024),
3342                        write_buffer: NZUsize!(1024 * 1024),
3343                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3344                        forwarding: ForwardingPolicy::Disabled,
3345                    };
3346                    let engine = Engine::new(context.child("engine"), cfg);
3347                    engine.start(pending, recovered, resolver);
3348                }
3349            }
3350
3351            // Wait for all engines to finish
3352            let mut finalizers = Vec::new();
3353            for reporter in reporters.iter_mut() {
3354                let (mut latest, mut monitor) = reporter.subscribe().await;
3355                finalizers.push(context.child("finalizer").spawn(move |_| async move {
3356                    while latest < required_containers {
3357                        latest = monitor.recv().await.expect("event missing");
3358                    }
3359                }));
3360            }
3361            join_all(finalizers).await;
3362
3363            // Check reporters for correct activity
3364            let byz = &participants[0];
3365            let mut count_conflicting = 0;
3366            for reporter in reporters.iter() {
3367                // Ensure only faults for byz
3368                {
3369                    let faults = reporter.faults.lock();
3370                    assert_eq!(faults.len(), 1);
3371                    let faulter = faults.get(byz).expect("byzantine party is not faulter");
3372                    for faults in faulter.values() {
3373                        for fault in faults.iter() {
3374                            match fault {
3375                                Activity::ConflictingNotarize(_) => {
3376                                    count_conflicting += 1;
3377                                }
3378                                Activity::ConflictingFinalize(_) => {
3379                                    count_conflicting += 1;
3380                                }
3381                                _ => panic!("unexpected fault: {fault:?}"),
3382                            }
3383                        }
3384                    }
3385                }
3386
3387                // Ensure no invalid signatures
3388                reporter.assert_no_invalid();
3389            }
3390            assert!(count_conflicting > 0);
3391
3392            // Ensure conflicter is blocked
3393            let blocked = oracle.blocked().await.unwrap();
3394            assert!(!blocked.is_empty());
3395            for (a, b) in blocked {
3396                assert_ne!(&a, byz);
3397                assert_eq!(&b, byz);
3398            }
3399        });
3400    }
3401
3402    test_for_all_fixtures!(conflicter, seeds = 5);
3403
3404    fn invalid<S, F, L>(seed: u64, mut fixture: F)
3405    where
3406        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3407        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3408        L: Elector<S>,
3409    {
3410        // Create context
3411        let n = 4;
3412        let required_containers = View::new(50);
3413        let activity_timeout = ViewDelta::new(10);
3414        let skip_timeout = ViewDelta::new(5);
3415        let namespace = b"consensus".to_vec();
3416        let cfg = deterministic::Config::new()
3417            .with_seed(seed)
3418            .with_timeout(Some(Duration::from_secs(30)));
3419        let executor = deterministic::Runner::new(cfg);
3420        executor.start(|mut context| async move {
3421            // Register participants
3422            let Fixture {
3423                participants,
3424                schemes,
3425                ..
3426            } = fixture(&mut context, &namespace, n);
3427
3428            let schemes: Vec<_> = schemes
3429                .into_iter()
3430                .enumerate()
3431                .map(|(idx, scheme)| {
3432                    let is_byzantine = idx == 0;
3433                    let behavior = if is_byzantine {
3434                        wrapped::Behavior::CorruptSignature
3435                    } else {
3436                        wrapped::Behavior::Honest
3437                    };
3438                    wrapped::Scheme::new(scheme, behavior)
3439                })
3440                .collect();
3441
3442            let mut oracle =
3443                start_test_network_with_peers(context.child("network"), participants.clone(), true)
3444                    .await;
3445            let mut registrations = register_validators(&mut oracle, &participants).await;
3446
3447            // Link all validators
3448            let link = Link {
3449                latency: Duration::from_millis(10),
3450                jitter: Duration::from_millis(1),
3451                success_rate: 1.0,
3452            };
3453            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
3454
3455            // Create engines
3456            let elector = wrapped::Config(L::default());
3457            let relay = Arc::new(mocks::relay::Relay::new());
3458            let mut reporters = Vec::new();
3459            for (idx_scheme, validator) in participants.iter().enumerate() {
3460                // Create scheme context
3461                let context = context
3462                    .child("validator")
3463                    .with_attribute("public_key", validator);
3464
3465                let reporter_config = mocks::reporter::Config {
3466                    participants: participants.clone().try_into().unwrap(),
3467                    scheme: schemes[idx_scheme].clone(),
3468                    elector: elector.clone(),
3469                };
3470                let reporter =
3471                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3472                reporters.push(reporter.clone());
3473
3474                let application_cfg = mocks::application::Config {
3475                    hasher: Sha256::default(),
3476                    relay: relay.clone(),
3477                    me: validator.clone(),
3478                    propose_latency: (10.0, 5.0),
3479                    verify_latency: (10.0, 5.0),
3480                    certify_latency: (10.0, 5.0),
3481                    should_certify: mocks::application::Certifier::Always,
3482                };
3483                let (actor, application) = mocks::application::Application::new(
3484                    context.child("application"),
3485                    application_cfg,
3486                );
3487                actor.start();
3488                let blocker = oracle.control(validator.clone());
3489                let cfg = config::Config {
3490                    scheme: schemes[idx_scheme].clone(),
3491                    elector: elector.clone(),
3492                    blocker,
3493                    automaton: application.clone(),
3494                    relay: application.clone(),
3495                    reporter: reporter.clone(),
3496                    strategy: Sequential,
3497                    partition: validator.clone().to_string(),
3498                    mailbox_size: NZUsize!(1024),
3499                    epoch: Epoch::new(333),
3500                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3501                        Epoch::new(333),
3502                    )),
3503                    leader_timeout: Duration::from_secs(1),
3504                    certification_timeout: Duration::from_secs(2),
3505                    timeout_retry: Duration::from_secs(10),
3506                    fetch_timeout: Duration::from_secs(1),
3507                    activity_timeout,
3508                    skip_timeout,
3509                    fetch_concurrent: NZUsize!(4),
3510                    replay_buffer: NZUsize!(1024 * 1024),
3511                    write_buffer: NZUsize!(1024 * 1024),
3512                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3513                    forwarding: ForwardingPolicy::Disabled,
3514                };
3515                let engine = Engine::new(context.child("engine"), cfg);
3516                let (pending, recovered, resolver) = registrations
3517                    .remove(validator)
3518                    .expect("validator should be registered");
3519                engine.start(pending, recovered, resolver);
3520            }
3521
3522            // Wait for all engines to finish.
3523            // The byzantine node will not finish since it will mark any finalization
3524            // certificates it creates (using its own invalid signature) as invalid.
3525            let mut finalizers = Vec::new();
3526            for reporter in reporters.iter_mut().skip(1) {
3527                let (mut latest, mut monitor) = reporter.subscribe().await;
3528                finalizers.push(context.child("finalizer").spawn(move |_| async move {
3529                    while latest < required_containers {
3530                        latest = monitor.recv().await.expect("event missing");
3531                    }
3532                }));
3533            }
3534            join_all(finalizers).await;
3535
3536            // Check all reporters for activity
3537            for (i, reporter) in reporters.iter().enumerate() {
3538                // Ensure no faults
3539                reporter.assert_no_faults();
3540
3541                // All nodes see invalid signatures since the honest reporters get unfiltered votes
3542                // once they pass the view.
3543                assert!(*reporter.invalid_votes.lock() > 0);
3544
3545                // Only the byzantine node sees invalid certificates since it constructs them from
3546                // its own invalid vote. The honest nodes reject them before reaching the reporter.
3547                let is_byzantine = i == 0;
3548                if is_byzantine {
3549                    assert!(*reporter.invalid_certificates.lock() > 0);
3550                } else {
3551                    assert_eq!(*reporter.invalid_certificates.lock(), 0);
3552                }
3553            }
3554
3555            // Ensure byzantine node is blocked by honest nodes.
3556            // The ">=" is because the Byzantine node may block itself.
3557            let blocked = oracle.blocked().await.unwrap();
3558            assert!(blocked.len() >= participants.len() - 1);
3559            let byz = &participants[0];
3560            for (_, b) in blocked {
3561                // Assert only the byzantine node is blocked.
3562                assert_eq!(&b, byz);
3563            }
3564        });
3565    }
3566
3567    test_for_all_fixtures!(invalid, seeds = 5);
3568
3569    // Test that when a node receives finalizations, it reports them.
3570    fn received_certificates_are_reported<S, F, L>(mut fixture: F)
3571    where
3572        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3573        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3574        L: Elector<S>,
3575    {
3576        let n = 4;
3577        let required_containers = View::new(10);
3578        let activity_timeout = ViewDelta::new(10);
3579        let skip_timeout = ViewDelta::new(5);
3580        let namespace = b"consensus".to_vec();
3581        let cfg = deterministic::Config::new()
3582            .with_seed(0)
3583            .with_timeout(Some(Duration::from_secs(30)));
3584        let executor = deterministic::Runner::new(cfg);
3585        executor.start(|mut context| async move {
3586            let Fixture {
3587                participants,
3588                schemes,
3589                ..
3590            } = fixture(&mut context, &namespace, n);
3591
3592            let mut oracle = start_test_network_with_peers(
3593                context.child("network"),
3594                participants.clone(),
3595                false,
3596            )
3597            .await;
3598            let mut registrations = register_validators(&mut oracle, &participants).await;
3599
3600            // Link all honest nodes. Only link node 0 to node 1.
3601            //
3602            // Node 0 cannot locally form a certificate because it only sees itself plus one honest
3603            // peer, but it should still receive the certificates relayed by that peer.
3604            let link = Link {
3605                latency: Duration::from_millis(100),
3606                jitter: Duration::from_millis(1),
3607                success_rate: 1.0,
3608            };
3609            fn link_graph(_: usize, i: usize, j: usize) -> bool {
3610                if i == 0 || j == 0 {
3611                    return i == 1 || j == 1;
3612                }
3613                true
3614            }
3615            link_validators(
3616                &mut oracle,
3617                &participants,
3618                Action::Link(link),
3619                Some(link_graph),
3620            )
3621            .await;
3622
3623            let elector = L::default();
3624            let relay = Arc::new(mocks::relay::Relay::new());
3625            let mut reporters = Vec::new();
3626            for (idx_scheme, validator) in participants.iter().enumerate() {
3627                let context = context
3628                    .child("validator")
3629                    .with_attribute("public_key", validator);
3630                let reporter_config = mocks::reporter::Config {
3631                    participants: participants.clone().try_into().unwrap(),
3632                    scheme: schemes[idx_scheme].clone(),
3633                    elector: elector.clone(),
3634                };
3635                let reporter =
3636                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3637                reporters.push(reporter.clone());
3638
3639                let application_cfg = mocks::application::Config {
3640                    hasher: Sha256::default(),
3641                    relay: relay.clone(),
3642                    me: validator.clone(),
3643                    propose_latency: (10.0, 5.0),
3644                    verify_latency: (10.0, 5.0),
3645                    certify_latency: (10.0, 5.0),
3646                    should_certify: mocks::application::Certifier::Always,
3647                };
3648                let (actor, application) = mocks::application::Application::new(
3649                    context.child("application"),
3650                    application_cfg,
3651                );
3652                actor.start();
3653                let blocker = oracle.control(validator.clone());
3654                let cfg = config::Config {
3655                    scheme: schemes[idx_scheme].clone(),
3656                    elector: elector.clone(),
3657                    blocker,
3658                    automaton: application.clone(),
3659                    relay: application.clone(),
3660                    reporter: reporter.clone(),
3661                    strategy: Sequential,
3662                    partition: validator.clone().to_string(),
3663                    mailbox_size: NZUsize!(1024),
3664                    epoch: Epoch::new(333),
3665                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3666                        Epoch::new(333),
3667                    )),
3668                    leader_timeout: Duration::from_secs(1),
3669                    certification_timeout: Duration::from_secs(2),
3670                    timeout_retry: Duration::from_secs(10),
3671                    fetch_timeout: Duration::from_secs(1),
3672                    activity_timeout,
3673                    skip_timeout,
3674                    fetch_concurrent: NZUsize!(4),
3675                    replay_buffer: NZUsize!(1024 * 1024),
3676                    write_buffer: NZUsize!(1024 * 1024),
3677                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3678                    forwarding: ForwardingPolicy::Disabled,
3679                };
3680                let engine = Engine::new(context.child("engine"), cfg);
3681                let (pending, recovered, resolver) = registrations
3682                    .remove(validator)
3683                    .expect("validator should be registered");
3684                engine.start(pending, recovered, resolver);
3685            }
3686            // Wait for an honest node to observe the finalizations
3687            let excluded_reporter = reporters[0].clone();
3688            let mut honest_reporter = reporters[1].clone();
3689            let (mut honest_latest, mut honest_monitor) = honest_reporter.subscribe().await;
3690            while honest_latest < required_containers {
3691                honest_latest = honest_monitor.recv().await.expect("event missing");
3692            }
3693
3694            // Wait for all in-flight certificates to arrive at excluded node and be reported.
3695            context.sleep(Duration::from_secs(1)).await;
3696
3697            // It should have received similar certificates to the honest node (with some
3698            // tolerance for initial views in which may not have yet been connected).
3699            let honest_notarized = {
3700                let notarizations = honest_reporter.notarizations.lock();
3701                View::range(View::new(1), required_containers.next())
3702                    .filter(|view| notarizations.contains_key(view))
3703                    .count()
3704            };
3705            let excluded_notarized = {
3706                let notarizations = excluded_reporter.notarizations.lock();
3707                View::range(View::new(1), required_containers.next())
3708                    .filter(|view| notarizations.contains_key(view))
3709                    .count()
3710            };
3711            assert!(
3712                excluded_notarized >= honest_notarized.saturating_sub(2),
3713                "honest_notarized: {honest_notarized}, excluded_notarized: {excluded_notarized}"
3714            );
3715
3716            let honest_finalized = {
3717                let finalizations = honest_reporter.finalizations.lock();
3718                View::range(View::new(1), required_containers.next())
3719                    .filter(|view| finalizations.contains_key(view))
3720                    .count()
3721            };
3722            let excluded_finalized = {
3723                let finalizations = excluded_reporter.finalizations.lock();
3724                View::range(View::new(1), required_containers.next())
3725                    .filter(|view| finalizations.contains_key(view))
3726                    .count()
3727            };
3728            assert!(
3729                excluded_finalized >= honest_finalized.saturating_sub(2),
3730                "honest_finalized: {honest_finalized}, excluded_finalized: {excluded_finalized}"
3731            );
3732        });
3733    }
3734
3735    test_for_all_fixtures!(received_certificates_are_reported);
3736
3737    fn survives_burst<S, F, L>(mut fixture: F)
3738    where
3739        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3740        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3741        L: Elector<S>,
3742    {
3743        let n = 4;
3744        let epoch = Epoch::new(333);
3745        let namespace = b"mailbox_size_one_certificate_burst".to_vec();
3746        let executor = deterministic::Runner::default();
3747        executor.start(|mut context| async move {
3748            let Fixture {
3749                participants,
3750                schemes,
3751                ..
3752            } = fixture(&mut context, &namespace, n);
3753            let me = participants[0].clone();
3754            let mut oracle =
3755                start_test_network_with_peers(context.child("network"), participants.clone(), true)
3756                    .await;
3757            let (pending, recovered, resolver) = register_validator(&mut oracle, me.clone()).await;
3758
3759            let injector_pk = PrivateKey::from_seed(9_000_000).public_key();
3760            let (mut injector_sender, _injector_receiver) = oracle
3761                .control(injector_pk.clone())
3762                .register(1, TEST_QUOTA)
3763                .await
3764                .unwrap();
3765            let link = Link {
3766                latency: Duration::from_millis(0),
3767                jitter: Duration::from_millis(0),
3768                success_rate: 1.0,
3769            };
3770            oracle
3771                .add_link(injector_pk.clone(), me.clone(), link)
3772                .await
3773                .unwrap();
3774            oracle.manager().track(
3775                1,
3776                TrackedPeers::new(
3777                    Set::from_iter_dedup(std::iter::once(me.clone())),
3778                    Set::from_iter_dedup(std::iter::once(injector_pk.clone())),
3779                ),
3780            );
3781            context.sleep(Duration::from_millis(1)).await;
3782
3783            let quorum = quorum(n) as usize;
3784            let notarization = |view: View, parent: View, payload: &[u8]| {
3785                let proposal =
3786                    Proposal::new(Round::new(epoch, view), parent, Sha256::hash(payload));
3787                let votes: Vec<_> = schemes
3788                    .iter()
3789                    .take(quorum)
3790                    .map(|scheme| TNotarize::sign(scheme, proposal.clone()).unwrap())
3791                    .collect();
3792                TNotarization::from_notarizes(&schemes[0], &votes, &Sequential)
3793                    .expect("notarization requires quorum")
3794            };
3795            let finalization = |view: View, parent: View, payload: &[u8]| {
3796                let proposal =
3797                    Proposal::new(Round::new(epoch, view), parent, Sha256::hash(payload));
3798                let votes: Vec<_> = schemes
3799                    .iter()
3800                    .take(quorum)
3801                    .map(|scheme| TFinalize::sign(scheme, proposal.clone()).unwrap())
3802                    .collect();
3803                TFinalization::from_finalizes(&schemes[0], &votes, &Sequential)
3804                    .expect("finalization requires quorum")
3805            };
3806
3807            // Load the network with certificates that the batcher will want to pass to the voter
3808            for certificate in [
3809                Certificate::Notarization(notarization(View::new(1), View::zero(), b"payload-1")),
3810                Certificate::Notarization(notarization(View::new(2), View::new(1), b"payload-2")),
3811                Certificate::Notarization(notarization(View::new(3), View::new(2), b"payload-3")),
3812                Certificate::Finalization(finalization(View::new(3), View::new(2), b"payload-3")),
3813            ] {
3814                injector_sender.send(Recipients::One(me.clone()), certificate.encode(), true);
3815            }
3816
3817            let elector = L::default();
3818            let reporter_config = mocks::reporter::Config {
3819                participants: participants.clone().try_into().unwrap(),
3820                scheme: schemes[0].clone(),
3821                elector: elector.clone(),
3822            };
3823            let reporter =
3824                mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3825            let mut monitor_reporter = reporter.clone();
3826            let (mut latest, mut monitor) = monitor_reporter.subscribe().await;
3827
3828            let relay = Arc::new(mocks::relay::Relay::new());
3829            let application_cfg = mocks::application::Config {
3830                hasher: Sha256::default(),
3831                relay: relay.clone(),
3832                me: me.clone(),
3833                propose_latency: (0.0, 0.0),
3834                verify_latency: (0.0, 0.0),
3835                certify_latency: (0.0, 0.0),
3836                should_certify: mocks::application::Certifier::Always,
3837            };
3838            let (mut application_actor, application) =
3839                mocks::application::Application::new(context.child("application"), application_cfg);
3840            application_actor.set_stall_proposals(true);
3841            application_actor.start();
3842
3843            let cfg = config::Config {
3844                scheme: schemes[0].clone(),
3845                elector,
3846                blocker: oracle.control(me.clone()),
3847                automaton: application.clone(),
3848                relay: application,
3849                reporter,
3850                strategy: Sequential,
3851                partition: me.to_string(),
3852                mailbox_size: NZUsize!(1),
3853                epoch,
3854                floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(epoch)),
3855                leader_timeout: Duration::from_secs(1),
3856                certification_timeout: Duration::from_secs(2),
3857                timeout_retry: Duration::from_secs(10),
3858                fetch_timeout: Duration::from_secs(1),
3859                activity_timeout: ViewDelta::new(10),
3860                skip_timeout: ViewDelta::new(5),
3861                fetch_concurrent: NZUsize!(4),
3862                replay_buffer: NZUsize!(1024 * 1024),
3863                write_buffer: NZUsize!(1024 * 1024),
3864                page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3865                forwarding: ForwardingPolicy::Disabled,
3866            };
3867            let engine = Engine::new(context.child("engine"), cfg);
3868            engine.start(pending, recovered, resolver);
3869
3870            while latest < View::new(3) {
3871                latest = monitor.recv().await.expect("finalization event missing");
3872            }
3873        });
3874    }
3875
3876    test_for_all_fixtures!(survives_burst);
3877
3878    fn impersonator<S, F, L>(seed: u64, mut fixture: F)
3879    where
3880        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3881        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3882        L: Elector<S>,
3883    {
3884        // Create context
3885        let n = 4;
3886        let required_containers = View::new(50);
3887        let activity_timeout = ViewDelta::new(10);
3888        let skip_timeout = ViewDelta::new(5);
3889        let namespace = b"consensus".to_vec();
3890        let cfg = deterministic::Config::new()
3891            .with_seed(seed)
3892            .with_timeout(Some(Duration::from_secs(30)));
3893        let executor = deterministic::Runner::new(cfg);
3894        executor.start(|mut context| async move {
3895            // Register participants
3896            let Fixture {
3897                participants,
3898                schemes,
3899                ..
3900            } = fixture(&mut context, &namespace, n);
3901            let mut oracle =
3902                start_test_network_with_peers(context.child("network"), participants.clone(), true)
3903                    .await;
3904            let mut registrations = register_validators(&mut oracle, &participants).await;
3905
3906            // Link all validators
3907            let link = Link {
3908                latency: Duration::from_millis(10),
3909                jitter: Duration::from_millis(1),
3910                success_rate: 1.0,
3911            };
3912            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
3913
3914            // Create engines
3915            let elector = L::default();
3916            let relay = Arc::new(mocks::relay::Relay::new());
3917            let mut reporters = Vec::new();
3918            for (idx_scheme, validator) in participants.iter().enumerate() {
3919                // Create scheme context
3920                let context = context
3921                    .child("validator")
3922                    .with_attribute("public_key", validator);
3923
3924                // Start engine
3925                let reporter_config = mocks::reporter::Config {
3926                    participants: participants.clone().try_into().unwrap(),
3927                    scheme: schemes[idx_scheme].clone(),
3928                    elector: elector.clone(),
3929                };
3930                let reporter =
3931                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3932                let (pending, recovered, resolver) = registrations
3933                    .remove(validator)
3934                    .expect("validator should be registered");
3935                if idx_scheme == 0 {
3936                    let cfg = mocks::impersonator::Config {
3937                        scheme: schemes[idx_scheme].clone(),
3938                    };
3939
3940                    let engine: mocks::impersonator::Impersonator<_, _, Sha256> =
3941                        mocks::impersonator::Impersonator::new(
3942                            context.child("byzantine_engine"),
3943                            cfg,
3944                        );
3945                    engine.start(pending);
3946                } else {
3947                    reporters.push(reporter.clone());
3948                    let application_cfg = mocks::application::Config {
3949                        hasher: Sha256::default(),
3950                        relay: relay.clone(),
3951                        me: validator.clone(),
3952                        propose_latency: (10.0, 5.0),
3953                        verify_latency: (10.0, 5.0),
3954                        certify_latency: (10.0, 5.0),
3955                        should_certify: mocks::application::Certifier::Always,
3956                    };
3957                    let (actor, application) = mocks::application::Application::new(
3958                        context.child("application"),
3959                        application_cfg,
3960                    );
3961                    actor.start();
3962                    let blocker = oracle.control(validator.clone());
3963                    let cfg = config::Config {
3964                        scheme: schemes[idx_scheme].clone(),
3965                        elector: elector.clone(),
3966                        blocker,
3967                        automaton: application.clone(),
3968                        relay: application.clone(),
3969                        reporter: reporter.clone(),
3970                        strategy: Sequential,
3971                        partition: validator.clone().to_string(),
3972                        mailbox_size: NZUsize!(1024),
3973                        epoch: Epoch::new(333),
3974                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3975                            Epoch::new(333),
3976                        )),
3977                        leader_timeout: Duration::from_secs(1),
3978                        certification_timeout: Duration::from_secs(2),
3979                        timeout_retry: Duration::from_secs(10),
3980                        fetch_timeout: Duration::from_secs(1),
3981                        activity_timeout,
3982                        skip_timeout,
3983                        fetch_concurrent: NZUsize!(4),
3984                        replay_buffer: NZUsize!(1024 * 1024),
3985                        write_buffer: NZUsize!(1024 * 1024),
3986                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3987                        forwarding: ForwardingPolicy::Disabled,
3988                    };
3989                    let engine = Engine::new(context.child("engine"), cfg);
3990                    engine.start(pending, recovered, resolver);
3991                }
3992            }
3993
3994            // Wait for all engines to finish
3995            let mut finalizers = Vec::new();
3996            for reporter in reporters.iter_mut() {
3997                let (mut latest, mut monitor) = reporter.subscribe().await;
3998                finalizers.push(context.child("finalizer").spawn(move |_| async move {
3999                    while latest < required_containers {
4000                        latest = monitor.recv().await.expect("event missing");
4001                    }
4002                }));
4003            }
4004            join_all(finalizers).await;
4005
4006            // Check reporters for correct activity
4007            let byz = &participants[0];
4008            for reporter in reporters.iter() {
4009                // Ensure no faults
4010                reporter.assert_no_faults();
4011
4012                // Ensure no invalid signatures
4013                reporter.assert_no_invalid();
4014            }
4015
4016            // Ensure invalid is blocked
4017            let blocked = oracle.blocked().await.unwrap();
4018            assert!(!blocked.is_empty());
4019            for (a, b) in blocked {
4020                assert_ne!(&a, byz);
4021                assert_eq!(&b, byz);
4022            }
4023        });
4024    }
4025
4026    test_for_all_fixtures!(impersonator, seeds = 5);
4027
4028    fn equivocator_seeded<S, F, L>(seed: u64, mut fixture: F) -> bool
4029    where
4030        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4031        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4032        L: Elector<S>,
4033    {
4034        // Create context
4035        let n = 7;
4036        let required_containers = View::new(50);
4037        let activity_timeout = ViewDelta::new(10);
4038        let skip_timeout = ViewDelta::new(5);
4039        let namespace = b"consensus".to_vec();
4040        let cfg = deterministic::Config::new()
4041            .with_seed(seed)
4042            .with_timeout(Some(Duration::from_secs(60)));
4043        let executor = deterministic::Runner::new(cfg);
4044        executor.start(|mut context| async move {
4045            // Register participants
4046            let Fixture {
4047                participants,
4048                schemes,
4049                ..
4050            } = fixture(&mut context, &namespace, n);
4051            let mut oracle =
4052                start_test_network_with_peers(context.child("network"), participants.clone(), true)
4053                    .await;
4054            let mut registrations = register_validators(&mut oracle, &participants).await;
4055
4056            // Link all validators
4057            let link = Link {
4058                latency: Duration::from_millis(10),
4059                jitter: Duration::from_millis(1),
4060                success_rate: 1.0,
4061            };
4062            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4063
4064            // Create engines
4065            let elector = L::default();
4066            let mut engines = Vec::new();
4067            let relay = Arc::new(mocks::relay::Relay::new());
4068            let mut reporters = Vec::new();
4069            for (idx_scheme, validator) in participants.iter().enumerate() {
4070                // Create scheme context
4071                let context = context
4072                    .child("validator")
4073                    .with_attribute("public_key", validator);
4074
4075                // Start engine
4076                let reporter_config = mocks::reporter::Config {
4077                    participants: participants.clone().try_into().unwrap(),
4078                    scheme: schemes[idx_scheme].clone(),
4079                    elector: elector.clone(),
4080                };
4081                let reporter =
4082                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4083                reporters.push(reporter.clone());
4084                let (pending, recovered, resolver) = registrations
4085                    .remove(validator)
4086                    .expect("validator should be registered");
4087                if idx_scheme == 0 {
4088                    let cfg = mocks::equivocator::Config {
4089                        scheme: schemes[idx_scheme].clone(),
4090                        epoch: Epoch::new(333),
4091                        relay: relay.clone(),
4092                        hasher: Sha256::default(),
4093                        elector: elector.clone(),
4094                    };
4095
4096                    let engine = mocks::equivocator::Equivocator::new(
4097                        context.child("byzantine_engine"),
4098                        cfg,
4099                    );
4100                    engines.push(engine.start(pending, recovered));
4101                } else {
4102                    let application_cfg = mocks::application::Config {
4103                        hasher: Sha256::default(),
4104                        relay: relay.clone(),
4105                        me: validator.clone(),
4106                        propose_latency: (10.0, 5.0),
4107                        verify_latency: (10.0, 5.0),
4108                        certify_latency: (10.0, 5.0),
4109                        should_certify: mocks::application::Certifier::Always,
4110                    };
4111                    let (actor, application) = mocks::application::Application::new(
4112                        context.child("application"),
4113                        application_cfg,
4114                    );
4115                    actor.start();
4116                    let blocker = oracle.control(validator.clone());
4117                    let cfg = config::Config {
4118                        scheme: schemes[idx_scheme].clone(),
4119                        elector: elector.clone(),
4120                        blocker,
4121                        automaton: application.clone(),
4122                        relay: application.clone(),
4123                        reporter: reporter.clone(),
4124                        strategy: Sequential,
4125                        partition: validator.to_string(),
4126                        mailbox_size: NZUsize!(1024),
4127                        epoch: Epoch::new(333),
4128                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4129                            Epoch::new(333),
4130                        )),
4131                        leader_timeout: Duration::from_secs(1),
4132                        certification_timeout: Duration::from_secs(2),
4133                        timeout_retry: Duration::from_secs(10),
4134                        fetch_timeout: Duration::from_secs(1),
4135                        activity_timeout,
4136                        skip_timeout,
4137                        fetch_concurrent: NZUsize!(4),
4138                        replay_buffer: NZUsize!(1024 * 1024),
4139                        write_buffer: NZUsize!(1024 * 1024),
4140                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4141                        forwarding: ForwardingPolicy::Disabled,
4142                    };
4143                    let engine = Engine::new(context.child("engine"), cfg);
4144                    engines.push(engine.start(pending, recovered, resolver));
4145                }
4146            }
4147
4148            // Wait for all engines to hit required containers
4149            let mut finalizers = Vec::new();
4150            for reporter in reporters.iter_mut().skip(1) {
4151                let (mut latest, mut monitor) = reporter.subscribe().await;
4152                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4153                    while latest < required_containers {
4154                        latest = monitor.recv().await.expect("event missing");
4155                    }
4156                }));
4157            }
4158            join_all(finalizers).await;
4159
4160            // Abort a validator
4161            let idx = context.random_range(1..engines.len()); // skip byzantine validator
4162            let validator = &participants[idx];
4163            let handle = engines.remove(idx);
4164            handle.abort();
4165            let _ = handle.await;
4166            reporters.remove(idx);
4167            info!(idx, ?validator, "aborted validator");
4168
4169            // Wait for all engines to hit required containers
4170            let mut finalizers = Vec::new();
4171            for reporter in reporters.iter_mut().skip(1) {
4172                let (mut latest, mut monitor) = reporter.subscribe().await;
4173                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4174                    while latest < View::new(required_containers.get() * 2) {
4175                        latest = monitor.recv().await.expect("event missing");
4176                    }
4177                }));
4178            }
4179            join_all(finalizers).await;
4180
4181            // Recreate engine
4182            info!(idx, ?validator, "restarting validator");
4183            let context = context
4184                .child("validator_restarted")
4185                .with_attribute("public_key", validator);
4186
4187            // Start engine
4188            let reporter_config = mocks::reporter::Config {
4189                participants: participants.clone().try_into().unwrap(),
4190                scheme: schemes[idx].clone(),
4191                elector: elector.clone(),
4192            };
4193            let reporter =
4194                mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4195            let (pending, recovered, resolver) =
4196                register_validator(&mut oracle, validator.clone()).await;
4197            reporters.push(reporter.clone());
4198            let application_cfg = mocks::application::Config {
4199                hasher: Sha256::default(),
4200                relay: relay.clone(),
4201                me: validator.clone(),
4202                propose_latency: (10.0, 5.0),
4203                verify_latency: (10.0, 5.0),
4204                certify_latency: (10.0, 5.0),
4205                should_certify: mocks::application::Certifier::Always,
4206            };
4207            let (actor, application) =
4208                mocks::application::Application::new(context.child("application"), application_cfg);
4209            actor.start();
4210            let blocker = oracle.control(validator.clone());
4211            let cfg = config::Config {
4212                scheme: schemes[idx].clone(),
4213                elector: elector.clone(),
4214                blocker,
4215                automaton: application.clone(),
4216                relay: application.clone(),
4217                reporter: reporter.clone(),
4218                strategy: Sequential,
4219                partition: validator.to_string(),
4220                mailbox_size: NZUsize!(1024),
4221                epoch: Epoch::new(333),
4222                floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(Epoch::new(
4223                    333,
4224                ))),
4225                leader_timeout: Duration::from_secs(1),
4226                certification_timeout: Duration::from_secs(2),
4227                timeout_retry: Duration::from_secs(10),
4228                fetch_timeout: Duration::from_secs(1),
4229                activity_timeout,
4230                skip_timeout,
4231                fetch_concurrent: NZUsize!(4),
4232                replay_buffer: NZUsize!(1024 * 1024),
4233                write_buffer: NZUsize!(1024 * 1024),
4234                page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4235                forwarding: ForwardingPolicy::Disabled,
4236            };
4237            let engine = Engine::new(context.child("engine"), cfg);
4238            engine.start(pending, recovered, resolver);
4239
4240            // Wait for all engines to hit required containers
4241            let mut finalizers = Vec::new();
4242            for reporter in reporters.iter_mut().skip(1) {
4243                let (mut latest, mut monitor) = reporter.subscribe().await;
4244                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4245                    while latest < View::new(required_containers.get() * 3) {
4246                        latest = monitor.recv().await.expect("event missing");
4247                    }
4248                }));
4249            }
4250            join_all(finalizers).await;
4251
4252            // Check equivocator blocking (we aren't guaranteed a fault will be produced
4253            // because it may not be possible to extract a conflicting vote from the certificate
4254            // we receive)
4255            let byz = &participants[0];
4256            let blocked = oracle.blocked().await.unwrap();
4257            for (a, b) in &blocked {
4258                assert_ne!(a, byz);
4259                assert_eq!(b, byz);
4260            }
4261            !blocked.is_empty()
4262        })
4263    }
4264
4265    fn equivocator<S, F, L>(fixture: F)
4266    where
4267        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4268        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S> + Copy,
4269        L: Elector<S>,
4270    {
4271        let detected = (0..5).any(|seed| equivocator_seeded::<_, _, L>(seed, fixture));
4272        assert!(
4273            detected,
4274            "expected at least one seed to detect equivocation"
4275        );
4276    }
4277
4278    test_for_all_fixtures!(equivocator);
4279
4280    fn reconfigurer<S, F, L>(seed: u64, mut fixture: F)
4281    where
4282        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4283        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4284        L: Elector<S>,
4285    {
4286        // Create context
4287        let n = 4;
4288        let required_containers = View::new(50);
4289        let activity_timeout = ViewDelta::new(10);
4290        let skip_timeout = ViewDelta::new(5);
4291        let namespace = b"consensus".to_vec();
4292        let cfg = deterministic::Config::new()
4293            .with_seed(seed)
4294            .with_timeout(Some(Duration::from_secs(30)));
4295        let executor = deterministic::Runner::new(cfg);
4296        executor.start(|mut context| async move {
4297            // Register participants
4298            let Fixture {
4299                participants,
4300                schemes,
4301                ..
4302            } = fixture(&mut context, &namespace, n);
4303            let mut oracle =
4304                start_test_network_with_peers(context.child("network"), participants.clone(), true)
4305                    .await;
4306            let mut registrations = register_validators(&mut oracle, &participants).await;
4307
4308            // Link all validators
4309            let link = Link {
4310                latency: Duration::from_millis(10),
4311                jitter: Duration::from_millis(1),
4312                success_rate: 1.0,
4313            };
4314            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4315
4316            // Create engines
4317            let elector = L::default();
4318            let relay = Arc::new(mocks::relay::Relay::new());
4319            let mut reporters = Vec::new();
4320            for (idx_scheme, validator) in participants.iter().enumerate() {
4321                // Create scheme context
4322                let context = context
4323                    .child("validator")
4324                    .with_attribute("public_key", validator);
4325
4326                // Start engine
4327                let reporter_config = mocks::reporter::Config {
4328                    participants: participants.clone().try_into().unwrap(),
4329                    scheme: schemes[idx_scheme].clone(),
4330                    elector: elector.clone(),
4331                };
4332                let reporter =
4333                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4334                let (pending, recovered, resolver) = registrations
4335                    .remove(validator)
4336                    .expect("validator should be registered");
4337                if idx_scheme == 0 {
4338                    let cfg = mocks::reconfigurer::Config {
4339                        scheme: schemes[idx_scheme].clone(),
4340                    };
4341                    let engine: mocks::reconfigurer::Reconfigurer<_, _, Sha256> =
4342                        mocks::reconfigurer::Reconfigurer::new(
4343                            context.child("byzantine_engine"),
4344                            cfg,
4345                        );
4346                    engine.start(pending);
4347                } else {
4348                    reporters.push(reporter.clone());
4349                    let application_cfg = mocks::application::Config {
4350                        hasher: Sha256::default(),
4351                        relay: relay.clone(),
4352                        me: validator.clone(),
4353                        propose_latency: (10.0, 5.0),
4354                        verify_latency: (10.0, 5.0),
4355                        certify_latency: (10.0, 5.0),
4356                        should_certify: mocks::application::Certifier::Always,
4357                    };
4358                    let (actor, application) = mocks::application::Application::new(
4359                        context.child("application"),
4360                        application_cfg,
4361                    );
4362                    actor.start();
4363                    let blocker = oracle.control(validator.clone());
4364                    let cfg = config::Config {
4365                        scheme: schemes[idx_scheme].clone(),
4366                        elector: elector.clone(),
4367                        blocker,
4368                        automaton: application.clone(),
4369                        relay: application.clone(),
4370                        reporter: reporter.clone(),
4371                        strategy: Sequential,
4372                        partition: validator.to_string(),
4373                        mailbox_size: NZUsize!(1024),
4374                        epoch: Epoch::new(333),
4375                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4376                            Epoch::new(333),
4377                        )),
4378                        leader_timeout: Duration::from_secs(1),
4379                        certification_timeout: Duration::from_secs(2),
4380                        timeout_retry: Duration::from_secs(10),
4381                        fetch_timeout: Duration::from_secs(1),
4382                        activity_timeout,
4383                        skip_timeout,
4384                        fetch_concurrent: NZUsize!(4),
4385                        replay_buffer: NZUsize!(1024 * 1024),
4386                        write_buffer: NZUsize!(1024 * 1024),
4387                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4388                        forwarding: ForwardingPolicy::Disabled,
4389                    };
4390                    let engine = Engine::new(context.child("engine"), cfg);
4391                    engine.start(pending, recovered, resolver);
4392                }
4393            }
4394
4395            // Wait for all engines to finish
4396            let mut finalizers = Vec::new();
4397            for reporter in reporters.iter_mut() {
4398                let (mut latest, mut monitor) = reporter.subscribe().await;
4399                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4400                    while latest < required_containers {
4401                        latest = monitor.recv().await.expect("event missing");
4402                    }
4403                }));
4404            }
4405            join_all(finalizers).await;
4406
4407            // Check reporters for correct activity
4408            let byz = &participants[0];
4409            for reporter in reporters.iter() {
4410                // Ensure no faults
4411                reporter.assert_no_faults();
4412
4413                // Ensure no invalid signatures
4414                reporter.assert_no_invalid();
4415            }
4416
4417            // Ensure reconfigurer is blocked (epoch mismatch)
4418            let blocked = oracle.blocked().await.unwrap();
4419            assert!(!blocked.is_empty());
4420            for (a, b) in blocked {
4421                assert_ne!(&a, byz);
4422                assert_eq!(&b, byz);
4423            }
4424        });
4425    }
4426
4427    test_for_all_fixtures!(reconfigurer, seeds = 5);
4428
4429    fn nuller<S, F, L>(seed: u64, mut fixture: F)
4430    where
4431        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4432        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4433        L: Elector<S>,
4434    {
4435        // Create context
4436        let n = 4;
4437        let required_containers = View::new(50);
4438        let activity_timeout = ViewDelta::new(10);
4439        let skip_timeout = ViewDelta::new(5);
4440        let namespace = b"consensus".to_vec();
4441        let cfg = deterministic::Config::new()
4442            .with_seed(seed)
4443            .with_timeout(Some(Duration::from_secs(30)));
4444        let executor = deterministic::Runner::new(cfg);
4445        executor.start(|mut context| async move {
4446            // Register participants
4447            let Fixture {
4448                participants,
4449                schemes,
4450                ..
4451            } = fixture(&mut context, &namespace, n);
4452            let mut oracle =
4453                start_test_network_with_peers(context.child("network"), participants.clone(), true)
4454                    .await;
4455            let mut registrations = register_validators(&mut oracle, &participants).await;
4456
4457            // Link all validators
4458            let link = Link {
4459                latency: Duration::from_millis(10),
4460                jitter: Duration::from_millis(1),
4461                success_rate: 1.0,
4462            };
4463            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4464
4465            // Create engines
4466            let elector = L::default();
4467            let relay = Arc::new(mocks::relay::Relay::new());
4468            let mut reporters = Vec::new();
4469            for (idx_scheme, validator) in participants.iter().enumerate() {
4470                // Create scheme context
4471                let context = context
4472                    .child("validator")
4473                    .with_attribute("public_key", validator);
4474
4475                // Start engine
4476                let reporter_config = mocks::reporter::Config {
4477                    participants: participants.clone().try_into().unwrap(),
4478                    scheme: schemes[idx_scheme].clone(),
4479                    elector: elector.clone(),
4480                };
4481                let reporter =
4482                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4483                let (pending, recovered, resolver) = registrations
4484                    .remove(validator)
4485                    .expect("validator should be registered");
4486                if idx_scheme == 0 {
4487                    let cfg = mocks::nuller::Config {
4488                        scheme: schemes[idx_scheme].clone(),
4489                    };
4490                    let engine: mocks::nuller::Nuller<_, _, Sha256> =
4491                        mocks::nuller::Nuller::new(context.child("byzantine_engine"), cfg);
4492                    engine.start(pending);
4493                } else {
4494                    reporters.push(reporter.clone());
4495                    let application_cfg = mocks::application::Config {
4496                        hasher: Sha256::default(),
4497                        relay: relay.clone(),
4498                        me: validator.clone(),
4499                        propose_latency: (10.0, 5.0),
4500                        verify_latency: (10.0, 5.0),
4501                        certify_latency: (10.0, 5.0),
4502                        should_certify: mocks::application::Certifier::Always,
4503                    };
4504                    let (actor, application) = mocks::application::Application::new(
4505                        context.child("application"),
4506                        application_cfg,
4507                    );
4508                    actor.start();
4509                    let blocker = oracle.control(validator.clone());
4510                    let cfg = config::Config {
4511                        scheme: schemes[idx_scheme].clone(),
4512                        elector: elector.clone(),
4513                        blocker,
4514                        automaton: application.clone(),
4515                        relay: application.clone(),
4516                        reporter: reporter.clone(),
4517                        strategy: Sequential,
4518                        partition: validator.clone().to_string(),
4519                        mailbox_size: NZUsize!(1024),
4520                        epoch: Epoch::new(333),
4521                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4522                            Epoch::new(333),
4523                        )),
4524                        leader_timeout: Duration::from_secs(1),
4525                        certification_timeout: Duration::from_secs(2),
4526                        timeout_retry: Duration::from_secs(10),
4527                        fetch_timeout: Duration::from_secs(1),
4528                        activity_timeout,
4529                        skip_timeout,
4530                        fetch_concurrent: NZUsize!(4),
4531                        replay_buffer: NZUsize!(1024 * 1024),
4532                        write_buffer: NZUsize!(1024 * 1024),
4533                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4534                        forwarding: ForwardingPolicy::Disabled,
4535                    };
4536                    let engine = Engine::new(context.child("engine"), cfg);
4537                    engine.start(pending, recovered, resolver);
4538                }
4539            }
4540
4541            // Wait for all engines to finish
4542            let mut finalizers = Vec::new();
4543            for reporter in reporters.iter_mut() {
4544                let (mut latest, mut monitor) = reporter.subscribe().await;
4545                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4546                    while latest < required_containers {
4547                        latest = monitor.recv().await.expect("event missing");
4548                    }
4549                }));
4550            }
4551            join_all(finalizers).await;
4552
4553            // Check reporters for correct activity
4554            let byz = &participants[0];
4555            let mut count_nullify_and_finalize = 0;
4556            for reporter in reporters.iter() {
4557                // Ensure only faults for byz
4558                {
4559                    let faults = reporter.faults.lock();
4560                    assert_eq!(faults.len(), 1);
4561                    let faulter = faults.get(byz).expect("byzantine party is not faulter");
4562                    for faults in faulter.values() {
4563                        for fault in faults.iter() {
4564                            match fault {
4565                                Activity::NullifyFinalize(_) => {
4566                                    count_nullify_and_finalize += 1;
4567                                }
4568                                _ => panic!("unexpected fault: {fault:?}"),
4569                            }
4570                        }
4571                    }
4572                }
4573
4574                // Ensure no invalid signatures
4575                reporter.assert_no_invalid();
4576            }
4577            assert!(count_nullify_and_finalize > 0);
4578
4579            // Ensure nullifier is blocked
4580            let blocked = oracle.blocked().await.unwrap();
4581            assert!(!blocked.is_empty());
4582            for (a, b) in blocked {
4583                assert_ne!(&a, byz);
4584                assert_eq!(&b, byz);
4585            }
4586        });
4587    }
4588
4589    test_for_all_fixtures!(nuller, seeds = 5);
4590
4591    fn outdated<S, F, L>(seed: u64, mut fixture: F)
4592    where
4593        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4594        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4595        L: Elector<S>,
4596    {
4597        // Create context
4598        let n = 4;
4599        let required_containers = View::new(100);
4600        let activity_timeout = ViewDelta::new(10);
4601        let skip_timeout = ViewDelta::new(5);
4602        let namespace = b"consensus".to_vec();
4603        let cfg = deterministic::Config::new()
4604            .with_seed(seed)
4605            .with_timeout(Some(Duration::from_secs(30)));
4606        let executor = deterministic::Runner::new(cfg);
4607        executor.start(|mut context| async move {
4608            // Register participants
4609            let Fixture {
4610                participants,
4611                schemes,
4612                ..
4613            } = fixture(&mut context, &namespace, n);
4614            let mut oracle =
4615                start_test_network_with_peers(context.child("network"), participants.clone(), true)
4616                    .await;
4617            let mut registrations = register_validators(&mut oracle, &participants).await;
4618
4619            // Link all validators
4620            let link = Link {
4621                latency: Duration::from_millis(10),
4622                jitter: Duration::from_millis(1),
4623                success_rate: 1.0,
4624            };
4625            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4626
4627            // Create engines
4628            let elector = L::default();
4629            let relay = Arc::new(mocks::relay::Relay::new());
4630            let mut reporters = Vec::new();
4631            for (idx_scheme, validator) in participants.iter().enumerate() {
4632                // Create scheme context
4633                let context = context
4634                    .child("validator")
4635                    .with_attribute("public_key", validator);
4636
4637                // Start engine
4638                let reporter_config = mocks::reporter::Config {
4639                    participants: participants.clone().try_into().unwrap(),
4640                    scheme: schemes[idx_scheme].clone(),
4641                    elector: elector.clone(),
4642                };
4643                let reporter =
4644                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4645                let (pending, recovered, resolver) = registrations
4646                    .remove(validator)
4647                    .expect("validator should be registered");
4648                if idx_scheme == 0 {
4649                    let cfg = mocks::outdated::Config {
4650                        scheme: schemes[idx_scheme].clone(),
4651                        view_delta: ViewDelta::new(activity_timeout.get().saturating_mul(4)),
4652                    };
4653                    let engine: mocks::outdated::Outdated<_, _, Sha256> =
4654                        mocks::outdated::Outdated::new(context.child("byzantine_engine"), cfg);
4655                    engine.start(pending);
4656                } else {
4657                    reporters.push(reporter.clone());
4658                    let application_cfg = mocks::application::Config {
4659                        hasher: Sha256::default(),
4660                        relay: relay.clone(),
4661                        me: validator.clone(),
4662                        propose_latency: (10.0, 5.0),
4663                        verify_latency: (10.0, 5.0),
4664                        certify_latency: (10.0, 5.0),
4665                        should_certify: mocks::application::Certifier::Always,
4666                    };
4667                    let (actor, application) = mocks::application::Application::new(
4668                        context.child("application"),
4669                        application_cfg,
4670                    );
4671                    actor.start();
4672                    let blocker = oracle.control(validator.clone());
4673                    let cfg = config::Config {
4674                        scheme: schemes[idx_scheme].clone(),
4675                        elector: elector.clone(),
4676                        blocker,
4677                        automaton: application.clone(),
4678                        relay: application.clone(),
4679                        reporter: reporter.clone(),
4680                        strategy: Sequential,
4681                        partition: validator.clone().to_string(),
4682                        mailbox_size: NZUsize!(1024),
4683                        epoch: Epoch::new(333),
4684                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4685                            Epoch::new(333),
4686                        )),
4687                        leader_timeout: Duration::from_secs(1),
4688                        certification_timeout: Duration::from_secs(2),
4689                        timeout_retry: Duration::from_secs(10),
4690                        fetch_timeout: Duration::from_secs(1),
4691                        activity_timeout,
4692                        skip_timeout,
4693                        fetch_concurrent: NZUsize!(4),
4694                        replay_buffer: NZUsize!(1024 * 1024),
4695                        write_buffer: NZUsize!(1024 * 1024),
4696                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4697                        forwarding: ForwardingPolicy::Disabled,
4698                    };
4699                    let engine = Engine::new(context.child("engine"), cfg);
4700                    engine.start(pending, recovered, resolver);
4701                }
4702            }
4703
4704            // Wait for all engines to finish
4705            let mut finalizers = Vec::new();
4706            for reporter in reporters.iter_mut() {
4707                let (mut latest, mut monitor) = reporter.subscribe().await;
4708                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4709                    while latest < required_containers {
4710                        latest = monitor.recv().await.expect("event missing");
4711                    }
4712                }));
4713            }
4714            join_all(finalizers).await;
4715
4716            // Check reporters for correct activity
4717            for reporter in reporters.iter() {
4718                // Ensure no faults
4719                reporter.assert_no_faults();
4720
4721                // Ensure no invalid signatures
4722                reporter.assert_no_invalid();
4723            }
4724
4725            // Ensure no blocked connections
4726            let blocked = oracle.blocked().await.unwrap();
4727            assert!(blocked.is_empty());
4728        });
4729    }
4730
4731    test_for_all_fixtures!(outdated, seeds = 5);
4732
4733    fn run_1k<S, F, L>(mut fixture: F)
4734    where
4735        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4736        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4737        L: Elector<S>,
4738    {
4739        // Create context
4740        let n = 10;
4741        let required_containers = View::new(1_000);
4742        let activity_timeout = ViewDelta::new(10);
4743        let skip_timeout = ViewDelta::new(5);
4744        let namespace = b"consensus".to_vec();
4745        let cfg = deterministic::Config::new();
4746        let executor = deterministic::Runner::new(cfg);
4747        executor.start(|mut context| async move {
4748            // Register participants
4749            let Fixture {
4750                participants,
4751                schemes,
4752                ..
4753            } = fixture(&mut context, &namespace, n);
4754            let mut oracle =
4755                start_test_network_with_peers(context.child("network"), participants.clone(), true)
4756                    .await;
4757            let mut registrations = register_validators(&mut oracle, &participants).await;
4758
4759            // Link all validators
4760            let link = Link {
4761                latency: Duration::from_millis(80),
4762                jitter: Duration::from_millis(10),
4763                success_rate: 0.98,
4764            };
4765            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4766
4767            // Create engines
4768            let elector = L::default();
4769            let relay = Arc::new(mocks::relay::Relay::new());
4770            let mut reporters = Vec::new();
4771            let mut engine_handlers = Vec::new();
4772            for (idx, validator) in participants.iter().enumerate() {
4773                // Create scheme context
4774                let context = context
4775                    .child("validator")
4776                    .with_attribute("public_key", validator);
4777
4778                // Configure engine
4779                let reporter_config = mocks::reporter::Config {
4780                    participants: participants.clone().try_into().unwrap(),
4781                    scheme: schemes[idx].clone(),
4782                    elector: elector.clone(),
4783                };
4784                let reporter =
4785                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4786                reporters.push(reporter.clone());
4787                let application_cfg = mocks::application::Config {
4788                    hasher: Sha256::default(),
4789                    relay: relay.clone(),
4790                    me: validator.clone(),
4791                    propose_latency: (100.0, 50.0),
4792                    verify_latency: (50.0, 40.0),
4793                    certify_latency: (50.0, 40.0),
4794                    should_certify: mocks::application::Certifier::Always,
4795                };
4796                let (actor, application) = mocks::application::Application::new(
4797                    context.child("application"),
4798                    application_cfg,
4799                );
4800                actor.start();
4801                let blocker = oracle.control(validator.clone());
4802                let cfg = config::Config {
4803                    scheme: schemes[idx].clone(),
4804                    elector: elector.clone(),
4805                    blocker,
4806                    automaton: application.clone(),
4807                    relay: application.clone(),
4808                    reporter: reporter.clone(),
4809                    strategy: Sequential,
4810                    partition: validator.to_string(),
4811                    mailbox_size: NZUsize!(1024),
4812                    epoch: Epoch::new(333),
4813                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4814                        Epoch::new(333),
4815                    )),
4816                    leader_timeout: Duration::from_secs(1),
4817                    certification_timeout: Duration::from_secs(2),
4818                    timeout_retry: Duration::from_secs(10),
4819                    fetch_timeout: Duration::from_secs(1),
4820                    activity_timeout,
4821                    skip_timeout,
4822                    fetch_concurrent: NZUsize!(4),
4823                    replay_buffer: NZUsize!(1024 * 1024),
4824                    write_buffer: NZUsize!(1024 * 1024),
4825                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4826                    forwarding: ForwardingPolicy::Disabled,
4827                };
4828                let engine = Engine::new(context.child("engine"), cfg);
4829
4830                // Start engine
4831                let (pending, recovered, resolver) = registrations
4832                    .remove(validator)
4833                    .expect("validator should be registered");
4834                engine_handlers.push(engine.start(pending, recovered, resolver));
4835            }
4836
4837            // Wait for all engines to finish
4838            let mut finalizers = Vec::new();
4839            for reporter in reporters.iter_mut() {
4840                let (mut latest, mut monitor) = reporter.subscribe().await;
4841                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4842                    while latest < required_containers {
4843                        latest = monitor.recv().await.expect("event missing");
4844                    }
4845                }));
4846            }
4847            join_all(finalizers).await;
4848
4849            // Check reporters for correct activity
4850            for reporter in reporters.iter() {
4851                // Ensure no faults
4852                reporter.assert_no_faults();
4853
4854                // Ensure no invalid signatures
4855                reporter.assert_no_invalid();
4856            }
4857
4858            // Ensure no blocked connections
4859            let blocked = oracle.blocked().await.unwrap();
4860            assert!(blocked.is_empty());
4861        })
4862    }
4863
4864    #[test_group("slow")]
4865    #[test_traced]
4866    fn test_1k() {
4867        run_1k::<_, _, RoundRobin>(scheme_mocks::fixture);
4868    }
4869
4870    fn engine_shutdown<S, F, L>(seed: u64, mut fixture: F, graceful: bool)
4871    where
4872        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4873        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4874        L: Elector<S>,
4875    {
4876        let n = 1;
4877        let namespace = b"consensus".to_vec();
4878        let cfg = deterministic::Config::default()
4879            .with_seed(seed)
4880            .with_timeout(Some(Duration::from_secs(10)));
4881        let executor = deterministic::Runner::new(cfg);
4882        executor.start(|mut context| async move {
4883            // Register a single participant
4884            let Fixture {
4885                participants,
4886                schemes,
4887                ..
4888            } = fixture(&mut context, &namespace, n);
4889            let mut oracle =
4890                start_test_network_with_peers(context.child("network"), participants.clone(), true)
4891                    .await;
4892            let mut registrations = register_validators(&mut oracle, &participants).await;
4893
4894            // Link the single validator to itself (no-ops for completeness)
4895            let link = Link {
4896                latency: Duration::from_millis(1),
4897                jitter: Duration::from_millis(0),
4898                success_rate: 1.0,
4899            };
4900            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4901
4902            // Create engine
4903            let elector = L::default();
4904            let reporter_config = mocks::reporter::Config {
4905                participants: participants.clone().try_into().unwrap(),
4906                scheme: schemes[0].clone(),
4907                elector: elector.clone(),
4908            };
4909            let reporter =
4910                mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4911            let relay = Arc::new(mocks::relay::Relay::new());
4912            let application_cfg = mocks::application::Config {
4913                hasher: Sha256::default(),
4914                relay: relay.clone(),
4915                me: participants[0].clone(),
4916                propose_latency: (1.0, 0.0),
4917                verify_latency: (1.0, 0.0),
4918                certify_latency: (1.0, 0.0),
4919                should_certify: mocks::application::Certifier::Always,
4920            };
4921            let (actor, application) =
4922                mocks::application::Application::new(context.child("application"), application_cfg);
4923            actor.start();
4924            let blocker = oracle.control(participants[0].clone());
4925            let cfg = config::Config {
4926                scheme: schemes[0].clone(),
4927                elector: elector.clone(),
4928                blocker,
4929                automaton: application.clone(),
4930                relay: application.clone(),
4931                reporter: reporter.clone(),
4932                strategy: Sequential,
4933                partition: participants[0].clone().to_string(),
4934                mailbox_size: NZUsize!(64),
4935                epoch: Epoch::new(333),
4936                floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(Epoch::new(
4937                    333,
4938                ))),
4939                leader_timeout: Duration::from_millis(50),
4940                certification_timeout: Duration::from_millis(100),
4941                timeout_retry: Duration::from_millis(250),
4942                fetch_timeout: Duration::from_millis(50),
4943                activity_timeout: ViewDelta::new(4),
4944                skip_timeout: ViewDelta::new(2),
4945                fetch_concurrent: NZUsize!(4),
4946                replay_buffer: NZUsize!(1024 * 16),
4947                write_buffer: NZUsize!(1024 * 16),
4948                page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4949                forwarding: ForwardingPolicy::Disabled,
4950            };
4951            let engine = Engine::new(context.child("engine"), cfg);
4952
4953            // Start engine
4954            let (pending, recovered, resolver) = registrations
4955                .remove(&participants[0])
4956                .expect("validator should be registered");
4957            let handle = engine.start(pending, recovered, resolver);
4958
4959            // Allow tasks to start
4960            context.sleep(Duration::from_millis(1000)).await;
4961
4962            // Count running tasks under the engine prefix
4963            let running_before = count_running_tasks(&context, "engine");
4964            assert!(
4965                running_before > 0,
4966                "at least one engine task should be running"
4967            );
4968
4969            // Make sure the engine is still running after some time
4970            context.sleep(Duration::from_millis(1500)).await;
4971            assert!(
4972                count_running_tasks(&context, "engine") > 0,
4973                "engine tasks should still be running"
4974            );
4975
4976            // Shutdown engine and ensure children stop
4977            let running_after = if graceful {
4978                let result = context
4979                    .child("stop")
4980                    .stop(0, Some(Duration::from_secs(5)))
4981                    .await;
4982                assert!(
4983                    result.is_ok(),
4984                    "graceful shutdown should complete: {result:?}"
4985                );
4986                count_running_tasks(&context, "engine")
4987            } else {
4988                handle.abort();
4989                let _ = handle.await; // ensure parent tear-down runs
4990
4991                // Give the runtime a tick to process aborts
4992                context.sleep(Duration::from_millis(1000)).await;
4993                count_running_tasks(&context, "engine")
4994            };
4995            assert_eq!(
4996                running_after, 0,
4997                "all engine tasks should be stopped, but {running_after} still running"
4998            );
4999        });
5000    }
5001
5002    fn children_shutdown_on_engine_abort<S, F, L>(seed: u64, fixture: F)
5003    where
5004        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5005        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5006        L: Elector<S>,
5007    {
5008        engine_shutdown::<S, F, L>(seed, fixture, false);
5009    }
5010
5011    test_for_all_fixtures!(children_shutdown_on_engine_abort, seeds = 10);
5012
5013    fn graceful_shutdown<S, F, L>(seed: u64, fixture: F)
5014    where
5015        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5016        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5017        L: Elector<S>,
5018    {
5019        engine_shutdown::<S, F, L>(seed, fixture, true);
5020    }
5021
5022    test_for_all_fixtures!(graceful_shutdown, seeds = 10);
5023
5024    fn attributable_reporter_filtering<S, F, L>(mut fixture: F)
5025    where
5026        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5027        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5028        L: Elector<S>,
5029    {
5030        let n = 3;
5031        let required_containers = View::new(10);
5032        let activity_timeout = ViewDelta::new(10);
5033        let skip_timeout = ViewDelta::new(5);
5034        let namespace = b"consensus".to_vec();
5035        let executor = deterministic::Runner::timed(Duration::from_secs(30));
5036        executor.start(|mut context| async move {
5037            // Register participants
5038            let Fixture {
5039                participants,
5040                schemes,
5041                ..
5042            } = fixture(&mut context, &namespace, n);
5043            let mut oracle = start_test_network_with_peers(
5044                context.child("network"),
5045                participants.clone(),
5046                false,
5047            )
5048            .await;
5049            let mut registrations = register_validators(&mut oracle, &participants).await;
5050
5051            // Link all validators
5052            let link = Link {
5053                latency: Duration::from_millis(10),
5054                jitter: Duration::from_millis(1),
5055                success_rate: 1.0,
5056            };
5057            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5058
5059            // Create engines with `AttributableReporter` wrapper
5060            let elector = L::default();
5061            let relay = Arc::new(mocks::relay::Relay::new());
5062            let mut reporters = Vec::new();
5063            for (idx, validator) in participants.iter().enumerate() {
5064                let context = context
5065                    .child("validator")
5066                    .with_attribute("public_key", validator);
5067
5068                let reporter_config = mocks::reporter::Config {
5069                    participants: participants.clone().try_into().unwrap(),
5070                    scheme: schemes[idx].clone(),
5071                    elector: elector.clone(),
5072                };
5073                let mock_reporter =
5074                    mocks::reporter::Reporter::new(context.child("mock_reporter"), reporter_config);
5075
5076                // Wrap with `AttributableReporter`
5077                let attributable_reporter = scheme::reporter::AttributableReporter::new(
5078                    context.child("rng"),
5079                    schemes[idx].clone(),
5080                    mock_reporter.clone(),
5081                    Sequential,
5082                    true, // Enable verification
5083                );
5084                reporters.push(mock_reporter.clone());
5085
5086                let application_cfg = mocks::application::Config {
5087                    hasher: Sha256::default(),
5088                    relay: relay.clone(),
5089                    me: validator.clone(),
5090                    propose_latency: (10.0, 5.0),
5091                    verify_latency: (10.0, 5.0),
5092                    certify_latency: (10.0, 5.0),
5093                    should_certify: mocks::application::Certifier::Always,
5094                };
5095                let (actor, application) = mocks::application::Application::new(
5096                    context.child("application"),
5097                    application_cfg,
5098                );
5099                actor.start();
5100                let blocker = oracle.control(validator.clone());
5101                let cfg = config::Config {
5102                    scheme: schemes[idx].clone(),
5103                    elector: elector.clone(),
5104                    blocker,
5105                    automaton: application.clone(),
5106                    relay: application.clone(),
5107                    reporter: attributable_reporter,
5108                    strategy: Sequential,
5109                    partition: validator.to_string(),
5110                    mailbox_size: NZUsize!(1024),
5111                    epoch: Epoch::new(333),
5112                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5113                        Epoch::new(333),
5114                    )),
5115                    leader_timeout: Duration::from_secs(1),
5116                    certification_timeout: Duration::from_secs(2),
5117                    timeout_retry: Duration::from_secs(10),
5118                    fetch_timeout: Duration::from_secs(1),
5119                    activity_timeout,
5120                    skip_timeout,
5121                    fetch_concurrent: NZUsize!(4),
5122                    replay_buffer: NZUsize!(1024 * 1024),
5123                    write_buffer: NZUsize!(1024 * 1024),
5124                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5125                    forwarding: ForwardingPolicy::Disabled,
5126                };
5127                let engine = Engine::new(context.child("engine"), cfg);
5128
5129                // Start engine
5130                let (pending, recovered, resolver) = registrations
5131                    .remove(validator)
5132                    .expect("validator should be registered");
5133                engine.start(pending, recovered, resolver);
5134            }
5135
5136            // Wait for all engines to finish
5137            let mut finalizers = Vec::new();
5138            for reporter in reporters.iter_mut() {
5139                let (mut latest, mut monitor) = reporter.subscribe().await;
5140                finalizers.push(context.child("finalizer").spawn(move |_| async move {
5141                    while latest < required_containers {
5142                        latest = monitor.recv().await.expect("event missing");
5143                    }
5144                }));
5145            }
5146            join_all(finalizers).await;
5147
5148            // Verify filtering behavior based on scheme attributability
5149            for reporter in reporters.iter() {
5150                // Ensure no faults (normal operation)
5151                reporter.assert_no_faults();
5152
5153                // Ensure no invalid signatures
5154                reporter.assert_no_invalid();
5155
5156                // Check that we have certificates reported
5157                {
5158                    let notarizations = reporter.notarizations.lock();
5159                    let finalizations = reporter.finalizations.lock();
5160                    assert!(
5161                        !notarizations.is_empty() || !finalizations.is_empty(),
5162                        "Certificates should be reported"
5163                    );
5164                }
5165
5166                // Check notarizes
5167                let notarizes = reporter.notarizes.lock();
5168                let last_view = notarizes.keys().max().cloned().unwrap_or_default();
5169                for (view, payloads) in notarizes.iter() {
5170                    if *view == last_view {
5171                        continue; // Skip last view
5172                    }
5173
5174                    let signers: usize = payloads.values().map(|signers| signers.len()).sum();
5175
5176                    // For attributable schemes, we should see peer activities
5177                    if S::is_attributable() {
5178                        assert!(signers > 1, "view {view}: {signers}");
5179                    } else {
5180                        // For non-attributable, we shouldn't see any peer activities
5181                        assert_eq!(signers, 0);
5182                    }
5183                }
5184
5185                // Check finalizes
5186                let finalizes = reporter.finalizes.lock();
5187                for payloads in finalizes.values() {
5188                    let signers: usize = payloads.values().map(|signers| signers.len()).sum();
5189
5190                    // For attributable schemes, we should see peer activities
5191                    if S::is_attributable() {
5192                        assert!(signers > 1);
5193                    } else {
5194                        // For non-attributable, we shouldn't see any peer activities
5195                        assert_eq!(signers, 0);
5196                    }
5197                }
5198            }
5199
5200            // Ensure no blocked connections (normal operation)
5201            let blocked = oracle.blocked().await.unwrap();
5202            assert!(blocked.is_empty());
5203        });
5204    }
5205
5206    test_for_all_fixtures!(attributable_reporter_filtering);
5207
5208    fn split_views_no_lockup<S, F, L>(mut fixture: F)
5209    where
5210        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5211        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5212        L: Elector<S>,
5213    {
5214        // Scenario:
5215        // - View F: Finalization of B_1 seen by all participants.
5216        // - View F+1:
5217        //   - Nullification seen by honest (4..=6,7) and all 3 byzantines
5218        //   - Notarization of B_2A seen by honest (1..=3)
5219        // - View F+2:
5220        //   - Nullification seen by honest (1..=3,7) and all 3 byzantines
5221        //   - Notarization of B_2B seen by honest (4..=6)
5222        // - View F+3: Nullification. Seen by all participants.
5223        // - Then ensure progress resumes beyond F+3 after reconnecting
5224
5225        // Define participant types
5226        enum ParticipantType {
5227            Group1,    // receives notarization for f+1, nullification for f+2
5228            Group2,    // receives nullification for f+1, notarization for f+2
5229            Ignorant,  // receives nullification for f+1 and f+2
5230            Byzantine, // nullify-only
5231        }
5232        let get_type = |idx: usize| -> ParticipantType {
5233            match idx {
5234                0..3 => ParticipantType::Group1,
5235                3..6 => ParticipantType::Group2,
5236                6 => ParticipantType::Ignorant,
5237                7..10 => ParticipantType::Byzantine,
5238                _ => unreachable!(),
5239            }
5240        };
5241
5242        // Create context
5243        let n = 10;
5244        let quorum = quorum(n) as usize;
5245        assert_eq!(quorum, 7);
5246        let activity_timeout = ViewDelta::new(10);
5247        let skip_timeout = ViewDelta::new(5);
5248        let namespace = b"consensus".to_vec();
5249        let executor = deterministic::Runner::timed(Duration::from_secs(300));
5250        executor.start(|mut context| async move {
5251            // Register participants
5252            let Fixture {
5253                participants,
5254                schemes,
5255                ..
5256            } = fixture(&mut context, &namespace, n);
5257            let mut oracle = start_test_network_with_peers(
5258                context.child("network"),
5259                participants.clone(),
5260                false,
5261            )
5262            .await;
5263            let mut registrations = register_validators(&mut oracle, &participants).await;
5264
5265            // ========== Build the certificates manually ==========
5266
5267            // Helper: assemble finalization from explicit signer indices
5268            let build_finalization = |proposal: &Proposal<D>| -> TFinalization<_, D> {
5269                let votes: Vec<_> = (0..=quorum)
5270                    .map(|i| TFinalize::sign(&schemes[i], proposal.clone()).unwrap())
5271                    .collect();
5272                TFinalization::from_finalizes(&schemes[0], &votes, &Sequential)
5273                    .expect("finalization quorum")
5274            };
5275            // Helper: assemble notarization from explicit signer indices
5276            let build_notarization = |proposal: &Proposal<D>| -> TNotarization<_, D> {
5277                let votes: Vec<_> = (0..=quorum)
5278                    .map(|i| TNotarize::sign(&schemes[i], proposal.clone()).unwrap())
5279                    .collect();
5280                TNotarization::from_notarizes(&schemes[0], &votes, &Sequential)
5281                    .expect("notarization quorum")
5282            };
5283            let build_nullification = |round: Round| -> TNullification<_> {
5284                let votes: Vec<_> = (0..=quorum)
5285                    .map(|i| TNullify::sign::<D>(&schemes[i], round).unwrap())
5286                    .collect();
5287                TNullification::from_nullifies(&schemes[0], &votes, &Sequential)
5288                    .expect("nullification quorum")
5289            };
5290            // Choose F=1 and construct B_1, B_2A, B_2B
5291            let f_view = 1;
5292            let round_f = Round::new(Epoch::new(333), View::new(f_view));
5293            let payload_b0 = Sha256::hash(b"B_F");
5294            let proposal_b0 = Proposal::new(round_f, View::new(f_view - 1), payload_b0);
5295            let payload_b1a = Sha256::hash(b"B_G1");
5296            let proposal_b1a = Proposal::new(
5297                Round::new(Epoch::new(333), View::new(f_view + 1)),
5298                View::new(f_view),
5299                payload_b1a,
5300            );
5301            let payload_b1b = Sha256::hash(b"B_G2");
5302            let proposal_b1b = Proposal::new(
5303                Round::new(Epoch::new(333), View::new(f_view + 2)),
5304                View::new(f_view),
5305                payload_b1b,
5306            );
5307
5308            // Build notarization and finalization for the first block
5309            let b0_notarization = build_notarization(&proposal_b0);
5310            let b0_finalization = build_finalization(&proposal_b0);
5311            // Build notarizations for F+1 and F+2
5312            let b1a_notarization = build_notarization(&proposal_b1a);
5313            let b1b_notarization = build_notarization(&proposal_b1b);
5314            // Build nullifications for F+1 and F+2
5315            let null_a = build_nullification(Round::new(Epoch::new(333), View::new(f_view + 1)));
5316            let null_b = build_nullification(Round::new(Epoch::new(333), View::new(f_view + 2)));
5317
5318            // Create an 11th non-participant injector and obtain senders
5319            let injector_pk = PrivateKey::from_seed(1_000_000).public_key();
5320            let (mut injector_sender, _inj_certificate_receiver) = oracle
5321                .control(injector_pk.clone())
5322                .register(1, TEST_QUOTA)
5323                .await
5324                .unwrap();
5325
5326            // Create minimal one-way links from injector to all participants (not full mesh)
5327            let link = Link {
5328                latency: Duration::from_millis(10),
5329                jitter: Duration::from_millis(0),
5330                success_rate: 1.0,
5331            };
5332            for p in participants.iter() {
5333                oracle
5334                    .add_link(injector_pk.clone(), p.clone(), link.clone())
5335                    .await
5336                    .unwrap();
5337            }
5338            oracle.manager().track(
5339                1,
5340                TrackedPeers::new(
5341                    Set::from_iter_dedup(participants.iter().cloned()),
5342                    Set::from_iter_dedup(std::slice::from_ref(&injector_pk).iter().cloned()),
5343                ),
5344            );
5345            context.sleep(Duration::from_millis(10)).await;
5346
5347            // ========== Broadcast certificates over recovered network. ==========
5348
5349            // View F:
5350            let msg = Certificate::<_, D>::Notarization(b0_notarization).encode();
5351            injector_sender.send(Recipients::All, msg, true);
5352            let msg = Certificate::<_, D>::Finalization(b0_finalization).encode();
5353            injector_sender.send(Recipients::All, msg, true);
5354            // View F+1:
5355            let notarization_msg = Certificate::<_, D>::Notarization(b1a_notarization);
5356            let nullification_msg = Certificate::<_, D>::Nullification(null_a.clone());
5357            for (i, participant) in participants.iter().enumerate() {
5358                let recipient = Recipients::One(participant.clone());
5359                let msg = match get_type(i) {
5360                    ParticipantType::Group1 => notarization_msg.encode(),
5361                    _ => nullification_msg.encode(),
5362                };
5363                injector_sender.send(recipient, msg, true);
5364            }
5365            // View F+2:
5366            let notarization_msg = Certificate::<_, D>::Notarization(b1b_notarization);
5367            let nullification_msg = Certificate::<_, D>::Nullification(null_b.clone());
5368            for (i, participant) in participants.iter().enumerate() {
5369                let recipient = Recipients::One(participant.clone());
5370                let msg = match get_type(i) {
5371                    ParticipantType::Group2 => notarization_msg.encode(),
5372                    _ => nullification_msg.encode(),
5373                };
5374                injector_sender.send(recipient, msg, true);
5375            }
5376
5377            // ========== Create engines ==========
5378
5379            // Start engines after preloading certificates into each participant's
5380            // recovered channel (ensuring processing before any leader attempts to issue a
5381            // conflicting vote).
5382            let elector = L::default();
5383            let relay = Arc::new(mocks::relay::Relay::new());
5384            let mut honest_reporters = Vec::new();
5385            for (idx, validator) in participants.iter().enumerate() {
5386                let (pending, recovered, resolver) = registrations
5387                    .remove(validator)
5388                    .expect("validator should be registered");
5389                let participant_type = get_type(idx);
5390                if matches!(participant_type, ParticipantType::Byzantine) {
5391                    // Byzantine engines
5392                    let cfg = mocks::nullify_only::Config {
5393                        scheme: schemes[idx].clone(),
5394                    };
5395                    let engine: mocks::nullify_only::NullifyOnly<_, _, Sha256> =
5396                        mocks::nullify_only::NullifyOnly::new(
5397                            context
5398                                .child("byzantine")
5399                                .with_attribute("public_key", validator),
5400                            cfg,
5401                        );
5402                    engine.start(pending);
5403                    // Recovered/resolver channels are unused for byzantine actors.
5404                    drop(recovered);
5405                    drop(resolver);
5406                } else {
5407                    // Honest engines
5408                    let reporter_config = mocks::reporter::Config {
5409                        participants: participants.clone().try_into().unwrap(),
5410                        scheme: schemes[idx].clone(),
5411                        elector: elector.clone(),
5412                    };
5413                    let reporter = mocks::reporter::Reporter::new(
5414                        context
5415                            .child("reporter")
5416                            .with_attribute("public_key", validator),
5417                        reporter_config,
5418                    );
5419                    honest_reporters.push(reporter.clone());
5420
5421                    let application_cfg = mocks::application::Config {
5422                        hasher: Sha256::default(),
5423                        relay: relay.clone(),
5424                        me: validator.clone(),
5425                        propose_latency: (250.0, 50.0), // ensure we process certificates first
5426                        verify_latency: (10.0, 5.0),
5427                        certify_latency: (10.0, 5.0),
5428                        should_certify: mocks::application::Certifier::Always,
5429                    };
5430                    let (actor, application) = mocks::application::Application::new(
5431                        context
5432                            .child("application")
5433                            .with_attribute("public_key", validator),
5434                        application_cfg,
5435                    );
5436                    actor.start();
5437                    let blocker = oracle.control(validator.clone());
5438                    let cfg = config::Config {
5439                        scheme: schemes[idx].clone(),
5440                        elector: elector.clone(),
5441                        blocker,
5442                        automaton: application.clone(),
5443                        relay: application.clone(),
5444                        reporter: reporter.clone(),
5445                        strategy: Sequential,
5446                        partition: validator.to_string(),
5447                        mailbox_size: NZUsize!(1024),
5448                        epoch: Epoch::new(333),
5449                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5450                            Epoch::new(333),
5451                        )),
5452                        leader_timeout: Duration::from_secs(10),
5453                        certification_timeout: Duration::from_secs(10),
5454                        timeout_retry: Duration::from_secs(10),
5455                        fetch_timeout: Duration::from_secs(1),
5456                        activity_timeout,
5457                        skip_timeout,
5458                        fetch_concurrent: NZUsize!(4),
5459                        replay_buffer: NZUsize!(1024 * 1024),
5460                        write_buffer: NZUsize!(1024 * 1024),
5461                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5462                        forwarding: ForwardingPolicy::Disabled,
5463                    };
5464                    let engine = Engine::new(
5465                        context
5466                            .child("engine")
5467                            .with_attribute("public_key", validator),
5468                        cfg,
5469                    );
5470                    engine.start(pending, recovered, resolver);
5471                }
5472            }
5473
5474            // Allow started engines to consume preloaded certificates.
5475            context.sleep(Duration::from_secs(2)).await;
5476
5477            // ========== Assert the exact certificates are seen in each view ==========
5478
5479            // Assert the exact certificates in view F
5480            // All participants should have finalized B_0
5481            let view = View::new(f_view);
5482            for reporter in honest_reporters.iter() {
5483                let finalizations = reporter.finalizations.lock();
5484                assert!(finalizations.contains_key(&view));
5485            }
5486
5487            // Assert the exact certificates in view F+1
5488            // Group 1 should have notarized B_1A only
5489            // All other participants should have nullified F+1
5490            let view = View::new(f_view + 1);
5491            for (i, reporter) in honest_reporters.iter().enumerate() {
5492                let finalizations = reporter.finalizations.lock();
5493                assert!(!finalizations.contains_key(&view));
5494                let nullifications = reporter.nullifications.lock();
5495                let notarizations = reporter.notarizations.lock();
5496                match get_type(i) {
5497                    ParticipantType::Group1 => {
5498                        assert!(notarizations.contains_key(&view));
5499                        assert!(!nullifications.contains_key(&view));
5500                    }
5501                    _ => {
5502                        assert!(nullifications.contains_key(&view));
5503                        assert!(!notarizations.contains_key(&view));
5504                    }
5505                }
5506            }
5507
5508            // Assert the exact certificates in view F+2
5509            // Group 2 should have notarized B_1B only
5510            // All other participants should have nullified F+2
5511            let view = View::new(f_view + 2);
5512            for (i, reporter) in honest_reporters.iter().enumerate() {
5513                let finalizations = reporter.finalizations.lock();
5514                assert!(!finalizations.contains_key(&view));
5515                let nullifications = reporter.nullifications.lock();
5516                let notarizations = reporter.notarizations.lock();
5517                match get_type(i) {
5518                    ParticipantType::Group2 => {
5519                        assert!(notarizations.contains_key(&view));
5520                        assert!(!nullifications.contains_key(&view));
5521                    }
5522                    _ => {
5523                        assert!(nullifications.contains_key(&view));
5524                        assert!(!notarizations.contains_key(&view));
5525                    }
5526                }
5527            }
5528
5529            // Assert no members have yet nullified view F+3
5530            let next_view = View::new(f_view + 3);
5531            for (i, reporter) in honest_reporters.iter().enumerate() {
5532                let nullifies = reporter.nullifies.lock();
5533                assert!(!nullifies.contains_key(&next_view), "reporter {i}");
5534            }
5535
5536            // ========== Reconnect all participants ==========
5537
5538            // Reconnect all participants fully using the helper
5539            link_validators(&mut oracle, &participants, Action::Link(link.clone()), None).await;
5540
5541            // Wait until all honest reporters finalize strictly past F+2 (e.g., at least F+3)
5542            {
5543                let target = View::new(f_view + 3);
5544                let mut finalizers = Vec::new();
5545                for reporter in honest_reporters.iter_mut() {
5546                    let (mut latest, mut monitor) = reporter.subscribe().await;
5547                    finalizers.push(
5548                        context
5549                            .child("resume_finalizer")
5550                            .spawn(move |_| async move {
5551                                while latest < target {
5552                                    latest = monitor.recv().await.expect("event missing");
5553                                }
5554                            }),
5555                    );
5556                }
5557                join_all(finalizers).await;
5558            }
5559
5560            // Sanity checks: no faults/invalid signatures, and no peers blocked
5561            for reporter in honest_reporters.iter() {
5562                reporter.assert_no_faults();
5563                reporter.assert_no_invalid();
5564            }
5565            let blocked = oracle.blocked().await.unwrap();
5566            assert!(blocked.is_empty(), "blocked peers: {blocked:?}");
5567        });
5568    }
5569
5570    test_for_all_fixtures!(split_views_no_lockup);
5571
5572    fn tle<V, L>()
5573    where
5574        V: Variant,
5575        L: Elector<bls12381_threshold_vrf::Scheme<PublicKey, V>>,
5576    {
5577        // Create context
5578        let n = 4;
5579        let namespace = b"consensus".to_vec();
5580        let activity_timeout = ViewDelta::new(100);
5581        let skip_timeout = ViewDelta::new(50);
5582        let executor = deterministic::Runner::timed(Duration::from_secs(30));
5583        executor.start(|mut context| async move {
5584            // Register participants
5585            let Fixture {
5586                participants,
5587                schemes,
5588                ..
5589            } = bls12381_threshold_vrf::fixture::<V, _>(&mut context, &namespace, n);
5590            let mut oracle =
5591                start_test_network_with_peers(context.child("network"), participants.clone(), true)
5592                    .await;
5593            let mut registrations = register_validators(&mut oracle, &participants).await;
5594
5595            // Link all validators
5596            let link = Link {
5597                latency: Duration::from_millis(10),
5598                jitter: Duration::from_millis(5),
5599                success_rate: 1.0,
5600            };
5601            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5602
5603            // Create engines and reporters
5604            let elector = L::default();
5605            let relay = Arc::new(mocks::relay::Relay::new());
5606            let mut reporters = Vec::new();
5607            let mut engine_handlers = Vec::new();
5608            let monitor_reporter = Arc::new(Mutex::new(None));
5609            for (idx, validator) in participants.iter().enumerate() {
5610                // Create scheme context
5611                let context = context
5612                    .child("validator")
5613                    .with_attribute("public_key", validator);
5614
5615                // Store first reporter for monitoring
5616                let reporter_config = mocks::reporter::Config {
5617                    participants: participants.clone().try_into().unwrap(),
5618                    scheme: schemes[idx].clone(),
5619                    elector: elector.clone(),
5620                };
5621                let reporter =
5622                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
5623                reporters.push(reporter.clone());
5624                if idx == 0 {
5625                    *monitor_reporter.lock() = Some(reporter.clone());
5626                }
5627
5628                // Configure application
5629                let application_cfg = mocks::application::Config {
5630                    hasher: Sha256::default(),
5631                    relay: relay.clone(),
5632                    me: validator.clone(),
5633                    propose_latency: (10.0, 5.0),
5634                    verify_latency: (10.0, 5.0),
5635                    certify_latency: (10.0, 5.0),
5636                    should_certify: mocks::application::Certifier::Always,
5637                };
5638                let (actor, application) = mocks::application::Application::new(
5639                    context.child("application"),
5640                    application_cfg,
5641                );
5642                actor.start();
5643                let blocker = oracle.control(validator.clone());
5644                let cfg = config::Config {
5645                    scheme: schemes[idx].clone(),
5646                    elector: elector.clone(),
5647                    blocker,
5648                    automaton: application.clone(),
5649                    relay: application.clone(),
5650                    reporter: reporter.clone(),
5651                    strategy: Sequential,
5652                    partition: validator.to_string(),
5653                    mailbox_size: NZUsize!(1024),
5654                    epoch: Epoch::new(333),
5655                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5656                        Epoch::new(333),
5657                    )),
5658                    leader_timeout: Duration::from_millis(100),
5659                    certification_timeout: Duration::from_millis(200),
5660                    timeout_retry: Duration::from_millis(500),
5661                    fetch_timeout: Duration::from_millis(100),
5662                    activity_timeout,
5663                    skip_timeout,
5664                    fetch_concurrent: NZUsize!(4),
5665                    replay_buffer: NZUsize!(1024 * 1024),
5666                    write_buffer: NZUsize!(1024 * 1024),
5667                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5668                    forwarding: ForwardingPolicy::Disabled,
5669                };
5670                let engine = Engine::new(context.child("engine"), cfg);
5671
5672                // Start engine
5673                let (pending, recovered, resolver) = registrations
5674                    .remove(validator)
5675                    .expect("validator should be registered");
5676                engine_handlers.push(engine.start(pending, recovered, resolver));
5677            }
5678
5679            // Prepare TLE test data
5680            let target = Round::new(Epoch::new(333), View::new(10)); // Encrypt for round (epoch 333, view 10)
5681            let message = b"Secret message for future view10"; // 32 bytes
5682
5683            // Encrypt message
5684            let ciphertext = schemes[0].encrypt(&mut context, target, *message);
5685
5686            // Wait for consensus to reach the target view and then decrypt
5687            let reporter = monitor_reporter.lock().clone().unwrap();
5688            loop {
5689                // Wait for notarization
5690                context.sleep(Duration::from_millis(100)).await;
5691                let notarizations = reporter.notarizations.lock();
5692                let Some(notarization) = notarizations.get(&target.view()) else {
5693                    continue;
5694                };
5695
5696                // Decrypt the message using the seed
5697                let seed = notarization.seed();
5698                let decrypted = seed
5699                    .decrypt(&ciphertext)
5700                    .expect("Decryption should succeed with valid seed signature");
5701                assert_eq!(
5702                    message,
5703                    decrypted.as_ref(),
5704                    "Decrypted message should match original message"
5705                );
5706                break;
5707            }
5708        });
5709    }
5710
5711    #[test_traced]
5712    fn test_tle() {
5713        tle::<MinPk, Random>();
5714        tle::<MinSig, Random>();
5715    }
5716
5717    fn run_hailstorm<S, F, L>(
5718        seed: u64,
5719        shutdowns: usize,
5720        interval: ViewDelta,
5721        mut fixture: F,
5722    ) -> String
5723    where
5724        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5725        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5726        L: Elector<S>,
5727    {
5728        // Create context
5729        let n = 5;
5730        let activity_timeout = ViewDelta::new(10);
5731        let skip_timeout = ViewDelta::new(5);
5732        let namespace = b"consensus".to_vec();
5733        let cfg = deterministic::Config::new().with_seed(seed);
5734        let executor = deterministic::Runner::new(cfg);
5735        executor.start(|mut context| async move {
5736            // Register participants
5737            let Fixture {
5738                participants,
5739                schemes,
5740                ..
5741            } = fixture(&mut context, &namespace, n);
5742            let mut oracle =
5743                start_test_network_with_peers(context.child("network"), participants.clone(), true)
5744                    .await;
5745            let mut registrations = register_validators(&mut oracle, &participants).await;
5746
5747            // Link all validators
5748            let link = Link {
5749                latency: Duration::from_millis(10),
5750                jitter: Duration::from_millis(1),
5751                success_rate: 1.0,
5752            };
5753            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5754
5755            // Create engines
5756            let elector = L::default();
5757            let relay = Arc::new(mocks::relay::Relay::new());
5758            let mut reporters = BTreeMap::new();
5759            let mut engine_handlers = BTreeMap::new();
5760            for (idx, validator) in participants.iter().enumerate() {
5761                // Create scheme context
5762                let context = context
5763                    .child("validator")
5764                    .with_attribute("public_key", validator);
5765
5766                // Configure engine
5767                let reporter_config = mocks::reporter::Config {
5768                    participants: participants.clone().try_into().unwrap(),
5769                    scheme: schemes[idx].clone(),
5770                    elector: elector.clone(),
5771                };
5772                let reporter =
5773                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
5774                reporters.insert(idx, reporter.clone());
5775                let application_cfg = mocks::application::Config {
5776                    hasher: Sha256::default(),
5777                    relay: relay.clone(),
5778                    me: validator.clone(),
5779                    propose_latency: (10.0, 5.0),
5780                    verify_latency: (10.0, 5.0),
5781                    certify_latency: (10.0, 5.0),
5782                    should_certify: mocks::application::Certifier::Always,
5783                };
5784                let (actor, application) = mocks::application::Application::new(
5785                    context.child("application"),
5786                    application_cfg,
5787                );
5788                actor.start();
5789                let blocker = oracle.control(validator.clone());
5790                let cfg = config::Config {
5791                    scheme: schemes[idx].clone(),
5792                    elector: elector.clone(),
5793                    blocker,
5794                    automaton: application.clone(),
5795                    relay: application.clone(),
5796                    reporter: reporter.clone(),
5797                    strategy: Sequential,
5798                    partition: validator.to_string(),
5799                    mailbox_size: NZUsize!(1024),
5800                    epoch: Epoch::new(333),
5801                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5802                        Epoch::new(333),
5803                    )),
5804                    leader_timeout: Duration::from_secs(1),
5805                    certification_timeout: Duration::from_secs(2),
5806                    timeout_retry: Duration::from_secs(10),
5807                    fetch_timeout: Duration::from_secs(1),
5808                    activity_timeout,
5809                    skip_timeout,
5810                    fetch_concurrent: NZUsize!(4),
5811                    replay_buffer: NZUsize!(1024 * 1024),
5812                    write_buffer: NZUsize!(1024 * 1024),
5813                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5814                    forwarding: ForwardingPolicy::Disabled,
5815                };
5816                let engine = Engine::new(context.child("engine"), cfg);
5817
5818                // Start engine
5819                let (pending, recovered, resolver) = registrations
5820                    .remove(validator)
5821                    .expect("validator should be registered");
5822                engine_handlers.insert(idx, engine.start(pending, recovered, resolver));
5823            }
5824
5825            // Run shutdowns
5826            let mut target = View::zero();
5827            for i in 0..shutdowns {
5828                // Update target
5829                target = target.saturating_add(interval);
5830
5831                // Wait for all engines to finish
5832                let mut finalizers = Vec::new();
5833                for reporter in reporters.values_mut() {
5834                    let (mut latest, mut monitor) = reporter.subscribe().await;
5835                    finalizers.push(context.child("finalizer").spawn(move |_| async move {
5836                        while latest < target {
5837                            latest = monitor.recv().await.expect("event missing");
5838                        }
5839                    }));
5840                }
5841                join_all(finalizers).await;
5842                target = target.saturating_add(interval);
5843
5844                // Select a random engine to shutdown
5845                let idx = context.random_range(0..engine_handlers.len());
5846                let validator = &participants[idx];
5847                let handle = engine_handlers.remove(&idx).unwrap();
5848                handle.abort();
5849                let _ = handle.await;
5850                let selected_reporter = reporters.remove(&idx).unwrap();
5851                info!(idx, ?validator, "shutdown validator");
5852
5853                // Wait for all engines to finish
5854                let mut finalizers = Vec::new();
5855                for reporter in reporters.values_mut() {
5856                    let (mut latest, mut monitor) = reporter.subscribe().await;
5857                    finalizers.push(context.child("finalizer").spawn(move |_| async move {
5858                        while latest < target {
5859                            latest = monitor.recv().await.expect("event missing");
5860                        }
5861                    }));
5862                }
5863                join_all(finalizers).await;
5864                target = target.saturating_add(interval);
5865
5866                // Recreate engine
5867                info!(idx, ?validator, "restarting validator");
5868                let context = context
5869                    .child("validator_restarted")
5870                    .with_attribute("public_key", validator)
5871                    .with_attribute("restart", i);
5872
5873                // Start engine
5874                let (pending, recovered, resolver) =
5875                    register_validator(&mut oracle, validator.clone()).await;
5876                let application_cfg = mocks::application::Config {
5877                    hasher: Sha256::default(),
5878                    relay: relay.clone(),
5879                    me: validator.clone(),
5880                    propose_latency: (10.0, 5.0),
5881                    verify_latency: (10.0, 5.0),
5882                    certify_latency: (10.0, 5.0),
5883                    should_certify: mocks::application::Certifier::Always,
5884                };
5885                let (actor, application) = mocks::application::Application::new(
5886                    context.child("application"),
5887                    application_cfg,
5888                );
5889                actor.start();
5890                reporters.insert(idx, selected_reporter.clone());
5891                let blocker = oracle.control(validator.clone());
5892                let cfg = config::Config {
5893                    scheme: schemes[idx].clone(),
5894                    elector: elector.clone(),
5895                    blocker,
5896                    automaton: application.clone(),
5897                    relay: application.clone(),
5898                    reporter: selected_reporter,
5899                    strategy: Sequential,
5900                    partition: validator.to_string(),
5901                    mailbox_size: NZUsize!(1024),
5902                    epoch: Epoch::new(333),
5903                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5904                        Epoch::new(333),
5905                    )),
5906                    leader_timeout: Duration::from_secs(1),
5907                    certification_timeout: Duration::from_secs(2),
5908                    timeout_retry: Duration::from_secs(10),
5909                    fetch_timeout: Duration::from_secs(1),
5910                    activity_timeout,
5911                    skip_timeout,
5912                    fetch_concurrent: NZUsize!(4),
5913                    replay_buffer: NZUsize!(1024 * 1024),
5914                    write_buffer: NZUsize!(1024 * 1024),
5915                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5916                    forwarding: ForwardingPolicy::Disabled,
5917                };
5918                let engine = Engine::new(context.child("engine"), cfg);
5919                engine_handlers.insert(idx, engine.start(pending, recovered, resolver));
5920
5921                // Wait for all engines to hit required containers
5922                let mut finalizers = Vec::new();
5923                for reporter in reporters.values_mut() {
5924                    let (mut latest, mut monitor) = reporter.subscribe().await;
5925                    finalizers.push(context.child("finalizer").spawn(move |_| async move {
5926                        while latest < target {
5927                            latest = monitor.recv().await.expect("event missing");
5928                        }
5929                    }));
5930                }
5931                join_all(finalizers).await;
5932                info!(idx, ?validator, "validator recovered");
5933            }
5934
5935            // Check reporters for correct activity
5936            let latest_complete = target.saturating_sub(activity_timeout);
5937            for reporter in reporters.values() {
5938                // Ensure no faults
5939                reporter.assert_no_faults();
5940
5941                // Ensure no invalid signatures
5942                reporter.assert_no_invalid();
5943
5944                // Ensure no forks
5945                let mut notarized = HashMap::new();
5946                let mut finalized = HashMap::new();
5947                {
5948                    let notarizes = reporter.notarizes.lock();
5949                    for view in View::range(View::new(1), latest_complete) {
5950                        // Ensure only one payload proposed per view
5951                        let Some(payloads) = notarizes.get(&view) else {
5952                            continue;
5953                        };
5954                        if payloads.len() > 1 {
5955                            panic!("view: {view}");
5956                        }
5957                        let (digest, _) = payloads.iter().next().unwrap();
5958                        notarized.insert(view, *digest);
5959                    }
5960                }
5961                {
5962                    let notarizations = reporter.notarizations.lock();
5963                    for view in View::range(View::new(1), latest_complete) {
5964                        // Ensure notarization matches digest from notarizes
5965                        let Some(notarization) = notarizations.get(&view) else {
5966                            continue;
5967                        };
5968                        let Some(digest) = notarized.get(&view) else {
5969                            continue;
5970                        };
5971                        assert_eq!(&notarization.proposal.payload, digest);
5972                    }
5973                }
5974                {
5975                    let finalizes = reporter.finalizes.lock();
5976                    for view in View::range(View::new(1), latest_complete) {
5977                        // Ensure only one payload proposed per view
5978                        let Some(payloads) = finalizes.get(&view) else {
5979                            continue;
5980                        };
5981                        if payloads.len() > 1 {
5982                            panic!("view: {view}");
5983                        }
5984                        let (digest, _) = payloads.iter().next().unwrap();
5985                        finalized.insert(view, *digest);
5986
5987                        // Only check at views below timeout
5988                        if view > latest_complete {
5989                            continue;
5990                        }
5991
5992                        // Ensure no nullifies for any finalizers
5993                        let nullifies = reporter.nullifies.lock();
5994                        let Some(nullifies) = nullifies.get(&view) else {
5995                            continue;
5996                        };
5997                        for finalizers in payloads.values() {
5998                            for finalizer in finalizers.iter() {
5999                                if nullifies.contains(finalizer) {
6000                                    panic!("should not nullify and finalize at same view");
6001                                }
6002                            }
6003                        }
6004                    }
6005                }
6006                {
6007                    let finalizations = reporter.finalizations.lock();
6008                    for view in View::range(View::new(1), latest_complete) {
6009                        // Ensure finalization matches digest from finalizes
6010                        let Some(finalization) = finalizations.get(&view) else {
6011                            continue;
6012                        };
6013                        let Some(digest) = finalized.get(&view) else {
6014                            continue;
6015                        };
6016                        assert_eq!(&finalization.proposal.payload, digest);
6017                    }
6018                }
6019            }
6020
6021            // Ensure no blocked connections
6022            let blocked = oracle.blocked().await.unwrap();
6023            assert!(blocked.is_empty());
6024
6025            // Return state for audit
6026            context.auditor().state()
6027        })
6028    }
6029
6030    // The hailstorm run must be deterministic: two runs with identical inputs
6031    // must produce identical audit state.
6032    fn hailstorm<S, F, L>(fixture: F)
6033    where
6034        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
6035        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S> + Copy,
6036        L: Elector<S>,
6037    {
6038        assert_eq!(
6039            run_hailstorm::<_, _, L>(0, 10, ViewDelta::new(15), fixture),
6040            run_hailstorm::<_, _, L>(0, 10, ViewDelta::new(15), fixture),
6041        );
6042    }
6043
6044    test_for_all_fixtures!(hailstorm);
6045
6046    /// Configuration for a Twins testing campaign.
6047    ///
6048    /// A campaign generates adversarial primary/secondary recipient-set
6049    /// scenarios, splits Byzantine participants into twin halves, and verifies
6050    /// that honest nodes still finalize blocks after the adversarial prefix
6051    /// ends.
6052    ///
6053    /// # Fields
6054    ///
6055    /// - `n`: Total participants. The number of faults is derived as
6056    ///   `N3f1::max_faults(n)`. Larger `n` increases the per-scenario
6057    ///   compromised-set space but also makes each case slower to execute.
6058    ///
6059    /// - `rounds`: Number of adversarial rounds that form the attack prefix.
6060    ///   Each round independently places participants relative to the primary
6061    ///   and secondary recipient sets: outside both, both-halves,
6062    ///   primary-only, or secondary-only. The two recipient sets may overlap;
6063    ///   a participant in `both-halves` is visible to both twins in that view.
6064    ///   After these rounds, the network becomes fully synchronous. More
6065    ///   rounds exponentially increase the canonical scenario space.
6066    ///
6067    /// - `mode`: How multi-round scenarios are constructed. `Sampled` picks
6068    ///   independent recipient sets per round; `Sustained` repeats a single
6069    ///   recipient-set pattern across all rounds (modeling a persistent
6070    ///   adversarial split).
6071    ///
6072    /// - `max_cases`: Upper bound on the total emitted cases. Each case is a
6073    ///   (scenario, compromised-assignment) pair. Also caps scenario
6074    ///   enumeration (sampling uniformly when the space is larger). Cases
6075    ///   are shuffled and truncated to this budget.
6076    ///
6077    /// - `trailing_finalizations`: Number of finalizations each honest node
6078    ///   must produce *after* the adversarial prefix before the case is
6079    ///   considered successful. This is the liveness assertion -- it ensures
6080    ///   the protocol actually commits blocks under synchrony, not just
6081    ///   reaches a high view via nullifications.
6082    #[derive(Clone, Copy, Debug)]
6083    struct TwinsCampaign {
6084        n: u32,
6085        rounds: usize,
6086        mode: twins::Mode,
6087        max_cases: usize,
6088        trailing_finalizations: usize,
6089    }
6090
6091    fn twins_campaign<S, F, L>(
6092        rng: &mut impl CryptoRng,
6093        campaign: TwinsCampaign,
6094        link: Link,
6095        mut fixture: F,
6096    ) where
6097        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
6098        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
6099        L: Elector<S>,
6100    {
6101        let n = campaign.n;
6102        let faults = N3f1::max_faults(n) as usize;
6103        let cases = twins::cases(
6104            rng,
6105            twins::Framework {
6106                participants: n as usize,
6107                faults,
6108                rounds: campaign.rounds,
6109                mode: campaign.mode,
6110                max_cases: campaign.max_cases,
6111            },
6112        );
6113        assert!(
6114            !cases.is_empty(),
6115            "twins campaign should generate at least one case"
6116        );
6117        for case in cases {
6118            let scenario = case.scenario.clone();
6119            let twin_indices = case.compromised.clone();
6120            assert_eq!(
6121                twin_indices.len(),
6122                faults,
6123                "unexpected twins count for n={n} (expected f={faults})",
6124            );
6125
6126            let activity_timeout = ViewDelta::new(10);
6127            let skip_timeout = ViewDelta::new(5);
6128            let namespace = b"consensus".to_vec();
6129            let link = link.clone();
6130            let trailing_finalizations = campaign.trailing_finalizations;
6131            let mut case_fixture =
6132                |ctx: &mut deterministic::Context, ns: &[u8], n: u32| fixture(ctx, ns, n);
6133            let cfg = deterministic::Config::new().with_rng(Box::new(StdRng::from_rng(&mut *rng)));
6134            let executor = deterministic::Runner::new(cfg);
6135            executor.start(|mut context| async move {
6136                let Fixture {
6137                    participants,
6138                    schemes,
6139                    ..
6140                } = case_fixture(&mut context, &namespace, n);
6141                let participants: Arc<[_]> = participants.into();
6142                let mut oracle = start_test_network_with_peers(
6143                    context.child("network"),
6144                    participants.iter().cloned(),
6145                    false,
6146                )
6147                .await;
6148                let mut registrations = register_validators(&mut oracle, &participants).await;
6149                link_validators(&mut oracle, &participants, Action::Link(link), None).await;
6150
6151                let elector = TwinsElector::new(L::default(), &scenario, n as usize);
6152                let relay = Arc::new(mocks::relay::Relay::new());
6153                let mut reporters = Vec::new();
6154                let mut engine_handlers = Vec::new();
6155                let twin_index_set: HashSet<usize> = twin_indices.iter().copied().collect();
6156
6157                // Create twin engines (f Byzantine twins).
6158                for idx in twin_indices.iter().copied() {
6159                    let validator = &participants[idx];
6160                    let (
6161                        (vote_sender, vote_receiver),
6162                        (certificate_sender, certificate_receiver),
6163                        (_resolver_sender, _resolver_receiver),
6164                    ) = registrations
6165                        .remove(validator)
6166                        .expect("validator should be registered");
6167
6168                    let make_vote_forwarder = || {
6169                        let participants = participants.clone();
6170                        let scenario = scenario.clone();
6171                        move |origin: SplitOrigin, _: &Recipients<_>, message: &IoBuf| {
6172                            let msg: Vote<S, D> = Vote::decode(message.clone()).unwrap();
6173                            let (primary, secondary) =
6174                                scenario.partitions(msg.view(), participants.as_ref());
6175                            match origin {
6176                                SplitOrigin::Primary => Some(Recipients::Some(primary)),
6177                                SplitOrigin::Secondary => Some(Recipients::Some(secondary)),
6178                            }
6179                        }
6180                    };
6181                    let make_certificate_forwarder = || {
6182                        let codec = schemes[idx].certificate_codec_config();
6183                        let participants = participants.clone();
6184                        let scenario = scenario.clone();
6185                        move |origin: SplitOrigin, _: &Recipients<_>, message: &IoBuf| {
6186                            let msg: Certificate<S, D> =
6187                                Certificate::decode_cfg(&mut message.as_ref(), &codec).unwrap();
6188                            let (primary, secondary) =
6189                                scenario.partitions(msg.view(), participants.as_ref());
6190                            match origin {
6191                                SplitOrigin::Primary => Some(Recipients::Some(primary)),
6192                                SplitOrigin::Secondary => Some(Recipients::Some(secondary)),
6193                            }
6194                        }
6195                    };
6196                    let make_vote_router = || {
6197                        let participants = participants.clone();
6198                        let scenario = scenario.clone();
6199                        move |(sender, message): &(_, IoBuf)| {
6200                            let msg: Vote<S, D> = Vote::decode(message.clone()).unwrap();
6201                            scenario.route(msg.view(), sender, participants.as_ref())
6202                        }
6203                    };
6204                    let make_certificate_router = || {
6205                        let codec = schemes[idx].certificate_codec_config();
6206                        let participants = participants.clone();
6207                        let scenario = scenario.clone();
6208                        move |(sender, message): &(_, IoBuf)| {
6209                            let msg: Certificate<S, D> =
6210                                Certificate::decode_cfg(&mut message.as_ref(), &codec).unwrap();
6211                            scenario.route(msg.view(), sender, participants.as_ref())
6212                        }
6213                    };
6214                    let (vote_sender_primary, vote_sender_secondary) =
6215                        vote_sender.split_with(make_vote_forwarder());
6216                    let (vote_receiver_primary, vote_receiver_secondary) = vote_receiver
6217                        .split_with(
6218                            context.child("pending_split").with_attribute("index", idx),
6219                            make_vote_router(),
6220                        );
6221                    let (certificate_sender_primary, certificate_sender_secondary) =
6222                        certificate_sender.split_with(make_certificate_forwarder());
6223                    let (certificate_receiver_primary, certificate_receiver_secondary) =
6224                        certificate_receiver.split_with(
6225                            context
6226                                .child("recovered_split")
6227                                .with_attribute("index", idx),
6228                            make_certificate_router(),
6229                        );
6230
6231                    for (twin_label, pending, recovered) in [
6232                        (
6233                            "primary",
6234                            (vote_sender_primary, vote_receiver_primary),
6235                            (certificate_sender_primary, certificate_receiver_primary),
6236                        ),
6237                        (
6238                            "secondary",
6239                            (vote_sender_secondary, vote_receiver_secondary),
6240                            (certificate_sender_secondary, certificate_receiver_secondary),
6241                        ),
6242                    ] {
6243                        let partition = format!("twin_{idx}_{twin_label}");
6244                        let context = context
6245                            .child("twin")
6246                            .with_attribute("index", idx)
6247                            .with_attribute("side", twin_label);
6248
6249                        let reporter_config = mocks::reporter::Config {
6250                            participants: participants.as_ref().try_into().unwrap(),
6251                            scheme: schemes[idx].clone(),
6252                            elector: elector.clone(),
6253                        };
6254                        let reporter = mocks::reporter::Reporter::new(
6255                            context.child("reporter"),
6256                            reporter_config,
6257                        );
6258                        reporters.push(reporter.clone());
6259
6260                        let application_cfg = mocks::application::Config {
6261                            hasher: Sha256::default(),
6262                            relay: relay.clone(),
6263                            me: validator.clone(),
6264                            propose_latency: (10.0, 5.0),
6265                            verify_latency: (10.0, 5.0),
6266                            certify_latency: (10.0, 5.0),
6267                            should_certify: mocks::application::Certifier::Always,
6268                        };
6269                        let (actor, application) = mocks::application::Application::new(
6270                            context.child("application"),
6271                            application_cfg,
6272                        );
6273                        actor.start();
6274
6275                        let blocker = oracle.control(validator.clone());
6276                        let cfg = config::Config {
6277                            scheme: schemes[idx].clone(),
6278                            elector: elector.clone(),
6279                            blocker,
6280                            automaton: application.clone(),
6281                            relay: application.clone(),
6282                            reporter: reporter.clone(),
6283                            strategy: Sequential,
6284                            partition,
6285                            mailbox_size: NZUsize!(1024),
6286                            epoch: Epoch::new(333),
6287                            floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
6288                                Epoch::new(333),
6289                            )),
6290                            leader_timeout: Duration::from_secs(1),
6291                            certification_timeout: Duration::from_millis(1_500),
6292                            timeout_retry: Duration::from_secs(10),
6293                            fetch_timeout: Duration::from_secs(1),
6294                            activity_timeout,
6295                            skip_timeout,
6296                            fetch_concurrent: NZUsize!(4),
6297                            replay_buffer: NZUsize!(1024 * 1024),
6298                            write_buffer: NZUsize!(1024 * 1024),
6299                            page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
6300                            forwarding: ForwardingPolicy::Disabled,
6301                        };
6302                        let engine = Engine::new(context.child("engine"), cfg);
6303                        engine_handlers.push(engine.start(
6304                            pending,
6305                            recovered,
6306                            inert_channel(participants.as_ref()),
6307                        ));
6308                    }
6309                }
6310
6311                // Create honest engines.
6312                let honest_start = reporters.len();
6313                for (idx, validator) in participants.iter().enumerate() {
6314                    if twin_index_set.contains(&idx) {
6315                        continue;
6316                    }
6317
6318                    let partition = format!("honest_{idx}");
6319                    let context = context.child("honest").with_attribute("index", idx);
6320
6321                    let reporter_config = mocks::reporter::Config {
6322                        participants: participants.as_ref().try_into().unwrap(),
6323                        scheme: schemes[idx].clone(),
6324                        elector: elector.clone(),
6325                    };
6326                    let reporter =
6327                        mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
6328                    reporters.push(reporter.clone());
6329
6330                    let application_cfg = mocks::application::Config {
6331                        hasher: Sha256::default(),
6332                        relay: relay.clone(),
6333                        me: validator.clone(),
6334                        propose_latency: (10.0, 5.0),
6335                        verify_latency: (10.0, 5.0),
6336                        certify_latency: (10.0, 5.0),
6337                        should_certify: mocks::application::Certifier::Always,
6338                    };
6339                    let (actor, application) = mocks::application::Application::new(
6340                        context.child("application"),
6341                        application_cfg,
6342                    );
6343                    actor.start();
6344
6345                    let blocker = oracle.control(validator.clone());
6346                    let cfg = config::Config {
6347                        scheme: schemes[idx].clone(),
6348                        elector: elector.clone(),
6349                        blocker,
6350                        automaton: application.clone(),
6351                        relay: application.clone(),
6352                        reporter: reporter.clone(),
6353                        strategy: Sequential,
6354                        partition,
6355                        mailbox_size: NZUsize!(1024),
6356                        epoch: Epoch::new(333),
6357                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
6358                            Epoch::new(333),
6359                        )),
6360                        leader_timeout: Duration::from_secs(1),
6361                        certification_timeout: Duration::from_millis(1_500),
6362                        timeout_retry: Duration::from_secs(10),
6363                        fetch_timeout: Duration::from_secs(1),
6364                        activity_timeout,
6365                        skip_timeout,
6366                        fetch_concurrent: NZUsize!(4),
6367                        replay_buffer: NZUsize!(1024 * 1024),
6368                        write_buffer: NZUsize!(1024 * 1024),
6369                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
6370                        forwarding: ForwardingPolicy::Disabled,
6371                    };
6372                    let engine = Engine::new(context.child("engine"), cfg);
6373
6374                    let (
6375                        (pending_sender, pending_receiver),
6376                        (recovered_sender, recovered_receiver),
6377                        _,
6378                    ) = registrations
6379                        .remove(validator)
6380                        .expect("validator should be registered");
6381                    engine_handlers.push(engine.start(
6382                        (pending_sender, pending_receiver),
6383                        (recovered_sender, recovered_receiver),
6384                        inert_channel(participants.as_ref()),
6385                    ));
6386                }
6387
6388                // Wait for progress (liveness check) across honest replicas only.
6389                //
6390                // Only count finalizations after the adversarial prefix so we
6391                // verify the protocol actually recovers and makes progress under
6392                // synchrony. Finalizations during the prefix may be artifacts of
6393                // the attack setup and do not demonstrate liveness.
6394                //
6395                // Twin halves are Byzantine test machinery and are not required to
6396                // make progress for the campaign to establish honest-node liveness.
6397                let prefix_end = View::new(scenario.rounds().len() as u64);
6398                let mut finalizers = Vec::new();
6399                for (i, reporter) in reporters.iter_mut().skip(honest_start).enumerate() {
6400                    let (_latest, mut monitor) = reporter.subscribe().await;
6401                    let required = trailing_finalizations;
6402                    finalizers.push(context.child("finalizer").with_attribute("index", i).spawn(
6403                        move |_| async move {
6404                            let mut count = 0usize;
6405                            while count < required {
6406                                let view = monitor.recv().await.expect("event missing");
6407                                if view > prefix_end {
6408                                    count += 1;
6409                                }
6410                            }
6411                        },
6412                    ));
6413                }
6414                join_all(finalizers).await;
6415
6416                // Verify safety: no conflicting finalizations across honest reporters.
6417                let mut finalized_at_view: BTreeMap<View, D> = BTreeMap::new();
6418                for reporter in reporters.iter().skip(honest_start) {
6419                    let finalizations = reporter.finalizations.lock();
6420                    for (view, finalization) in finalizations.iter() {
6421                        let digest = finalization.proposal.payload;
6422                        if let Some(existing) = finalized_at_view.get(view) {
6423                            assert_eq!(
6424                                existing, &digest,
6425                                "safety violation: conflicting finalizations at view {view}"
6426                            );
6427                        } else {
6428                            finalized_at_view.insert(*view, digest);
6429                        }
6430                    }
6431                }
6432
6433                // Verify no invalid signatures were observed by honest replicas.
6434                for reporter in reporters.iter().skip(honest_start) {
6435                    reporter.assert_no_invalid();
6436                }
6437
6438                // Ensure no honest signer appears under multiple payloads for the same view.
6439                let twin_identities: HashSet<_> = twin_indices
6440                    .iter()
6441                    .map(|idx| participants[*idx].clone())
6442                    .collect();
6443                let mut notarized_by_honest_signer: BTreeMap<View, HashMap<PublicKey, D>> =
6444                    BTreeMap::new();
6445                let mut finalized_by_honest_signer: BTreeMap<View, HashMap<PublicKey, D>> =
6446                    BTreeMap::new();
6447                for reporter in reporters.iter().skip(honest_start) {
6448                    let notarizes = reporter.notarizes.lock();
6449                    for (view, payloads) in notarizes.iter() {
6450                        let signers = notarized_by_honest_signer.entry(*view).or_default();
6451                        for (digest, payload_signers) in payloads.iter() {
6452                            for signer in payload_signers.iter() {
6453                                if twin_identities.contains(signer) {
6454                                    continue;
6455                                }
6456                                if let Some(existing) = signers.insert(signer.clone(), *digest) {
6457                                    assert_eq!(
6458                                    existing, *digest,
6459                                    "honest signer produced conflicting notarizes at view {view}"
6460                                );
6461                                }
6462                            }
6463                        }
6464                    }
6465
6466                    let finalizes = reporter.finalizes.lock();
6467                    for (view, payloads) in finalizes.iter() {
6468                        let signers = finalized_by_honest_signer.entry(*view).or_default();
6469                        for (digest, payload_signers) in payloads.iter() {
6470                            for signer in payload_signers.iter() {
6471                                if twin_identities.contains(signer) {
6472                                    continue;
6473                                }
6474                                if let Some(existing) = signers.insert(signer.clone(), *digest) {
6475                                    assert_eq!(
6476                                    existing, *digest,
6477                                    "honest signer produced conflicting finalizes at view {view}"
6478                                );
6479                                }
6480                            }
6481                        }
6482                    }
6483                }
6484
6485                // Ensure faults are attributable to twins.
6486                for reporter in reporters.iter().skip(honest_start) {
6487                    let faults = reporter.faults.lock();
6488                    for faulter in faults.keys() {
6489                        assert!(
6490                            twin_identities.contains(faulter),
6491                            "fault from non-twin participant"
6492                        );
6493                    }
6494                }
6495
6496                let blocked = oracle.blocked().await.unwrap();
6497                for (_, faulter) in blocked {
6498                    assert!(
6499                        twin_identities.contains(&faulter),
6500                        "blocked peer attributed to non-twin participant"
6501                    );
6502                }
6503            });
6504        }
6505    }
6506
6507    const TWINS_CAMPAIGN: TwinsCampaign = TwinsCampaign {
6508        n: 5,
6509        rounds: 3,
6510        mode: twins::Mode::Sampled,
6511        max_cases: 20,
6512        trailing_finalizations: 10,
6513    };
6514
6515    const TWINS_LINK: Link = Link {
6516        latency: Duration::from_millis(500),
6517        jitter: Duration::from_millis(500),
6518        success_rate: 1.0,
6519    };
6520
6521    #[test_group("slow")]
6522    #[test_traced("INFO")]
6523    fn test_twins_sampled() {
6524        for link in [
6525            Link {
6526                latency: Duration::from_millis(10),
6527                jitter: Duration::from_millis(10),
6528                success_rate: 1.0,
6529            },
6530            TWINS_LINK,
6531        ] {
6532            twins_campaign::<_, _, RoundRobin>(
6533                &mut test_rng(),
6534                TWINS_CAMPAIGN,
6535                link,
6536                scheme_mocks::fixture,
6537            );
6538        }
6539    }
6540
6541    #[test_group("slow")]
6542    #[test_traced("INFO")]
6543    fn test_twins_sustained() {
6544        let campaign = TwinsCampaign {
6545            mode: twins::Mode::Sustained,
6546            ..TWINS_CAMPAIGN
6547        };
6548        for link in [
6549            Link {
6550                latency: Duration::from_millis(10),
6551                jitter: Duration::from_millis(10),
6552                success_rate: 1.0,
6553            },
6554            TWINS_LINK,
6555        ] {
6556            twins_campaign::<_, _, RoundRobin>(
6557                &mut test_rng(),
6558                campaign,
6559                link,
6560                scheme_mocks::fixture,
6561            );
6562        }
6563    }
6564
6565    #[test_group("slow")]
6566    #[test_traced("INFO")]
6567    fn test_twins_large_sampled() {
6568        let campaign = TwinsCampaign {
6569            n: 10,
6570            rounds: 5,
6571            ..TWINS_CAMPAIGN
6572        };
6573        twins_campaign::<_, _, RoundRobin>(
6574            &mut test_rng(),
6575            campaign,
6576            TWINS_LINK,
6577            scheme_mocks::fixture,
6578        );
6579    }
6580
6581    #[test_group("slow")]
6582    #[test_traced("INFO")]
6583    fn test_twins_large_sustained() {
6584        let campaign = TwinsCampaign {
6585            n: 10,
6586            rounds: 5,
6587            mode: twins::Mode::Sustained,
6588            ..TWINS_CAMPAIGN
6589        };
6590        twins_campaign::<_, _, RoundRobin>(
6591            &mut test_rng(),
6592            campaign,
6593            TWINS_LINK,
6594            scheme_mocks::fixture,
6595        );
6596    }
6597
6598    fn twins<S, F, L>(fixture: F)
6599    where
6600        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
6601        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
6602        L: Elector<S>,
6603    {
6604        twins_campaign::<_, _, L>(&mut test_rng(), TWINS_CAMPAIGN, TWINS_LINK, fixture);
6605    }
6606
6607    test_for_all_fixtures!(twins, level = "INFO");
6608}