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 partition<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 = 10;
2640        let required_containers = View::new(50);
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(900));
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            // Link all validators
2658            let link = Link {
2659                latency: Duration::from_millis(10),
2660                jitter: Duration::from_millis(1),
2661                success_rate: 1.0,
2662            };
2663            link_validators(&mut oracle, &participants, Action::Link(link.clone()), None).await;
2664
2665            // Create engines
2666            let elector = L::default();
2667            let relay = Arc::new(mocks::relay::Relay::new());
2668            let mut reporters = Vec::new();
2669            let mut engine_handlers = Vec::new();
2670            for (idx, validator) in participants.iter().enumerate() {
2671                // Create scheme context
2672                let context = context
2673                    .child("validator")
2674                    .with_attribute("public_key", validator);
2675
2676                // Configure engine
2677                let reporter_config = mocks::reporter::Config {
2678                    participants: participants.clone().try_into().unwrap(),
2679                    scheme: schemes[idx].clone(),
2680                    elector: elector.clone(),
2681                };
2682                let reporter =
2683                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2684                reporters.push(reporter.clone());
2685                let application_cfg = mocks::application::Config {
2686                    hasher: Sha256::default(),
2687                    relay: relay.clone(),
2688                    me: validator.clone(),
2689                    propose_latency: (10.0, 5.0),
2690                    verify_latency: (10.0, 5.0),
2691                    certify_latency: (10.0, 5.0),
2692                    should_certify: mocks::application::Certifier::Always,
2693                };
2694                let (actor, application) = mocks::application::Application::new(
2695                    context.child("application"),
2696                    application_cfg,
2697                );
2698                actor.start();
2699                let blocker = oracle.control(validator.clone());
2700                let cfg = config::Config {
2701                    scheme: schemes[idx].clone(),
2702                    elector: elector.clone(),
2703                    blocker,
2704                    automaton: application.clone(),
2705                    relay: application.clone(),
2706                    reporter: reporter.clone(),
2707                    strategy: Sequential,
2708                    partition: validator.to_string(),
2709                    mailbox_size: NZUsize!(1024),
2710                    epoch: Epoch::new(333),
2711                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2712                        Epoch::new(333),
2713                    )),
2714                    leader_timeout: Duration::from_secs(1),
2715                    certification_timeout: Duration::from_secs(2),
2716                    timeout_retry: Duration::from_secs(10),
2717                    fetch_timeout: Duration::from_secs(1),
2718                    activity_timeout,
2719                    skip_timeout,
2720                    fetch_concurrent: NZUsize!(4),
2721                    replay_buffer: NZUsize!(1024 * 1024),
2722                    write_buffer: NZUsize!(1024 * 1024),
2723                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2724                    forwarding: ForwardingPolicy::Disabled,
2725                };
2726                let engine = Engine::new(context.child("engine"), cfg);
2727
2728                // Start engine
2729                let (pending, recovered, resolver) = registrations
2730                    .remove(validator)
2731                    .expect("validator should be registered");
2732                engine_handlers.push(engine.start(pending, recovered, resolver));
2733            }
2734
2735            // Wait for all engines to finish
2736            let mut finalizers = Vec::new();
2737            for reporter in reporters.iter_mut() {
2738                let (mut latest, mut monitor) = reporter.subscribe().await;
2739                finalizers.push(context.child("finalizer").spawn(move |_| async move {
2740                    while latest < required_containers {
2741                        latest = monitor.recv().await.expect("event missing");
2742                    }
2743                }));
2744            }
2745            join_all(finalizers).await;
2746
2747            // Cut all links between validator halves
2748            fn separated(n: usize, a: usize, b: usize) -> bool {
2749                let m = n / 2;
2750                (a < m && b >= m) || (a >= m && b < m)
2751            }
2752            link_validators(&mut oracle, &participants, Action::Unlink, Some(separated)).await;
2753
2754            // Wait for any in-progress notarizations/finalizations to finish
2755            context.sleep(Duration::from_secs(10)).await;
2756
2757            // Wait for a few virtual minutes (shouldn't finalize anything)
2758            let mut finalizers = Vec::new();
2759            for reporter in reporters.iter_mut() {
2760                let (_, mut monitor) = reporter.subscribe().await;
2761                finalizers.push(context.child("finalizer").spawn(move |context| async move {
2762                    select! {
2763                        _timeout = context.sleep(Duration::from_secs(60)) => {},
2764                        _done = monitor.recv() => {
2765                            panic!("engine should not notarize or finalize anything");
2766                        },
2767                    }
2768                }));
2769            }
2770            join_all(finalizers).await;
2771
2772            // Restore links
2773            link_validators(
2774                &mut oracle,
2775                &participants,
2776                Action::Link(link),
2777                Some(separated),
2778            )
2779            .await;
2780
2781            // Wait for all engines to finish
2782            let mut finalizers = Vec::new();
2783            for reporter in reporters.iter_mut() {
2784                let (mut latest, mut monitor) = reporter.subscribe().await;
2785                let required = latest.saturating_add(ViewDelta::new(required_containers.get()));
2786                finalizers.push(context.child("finalizer").spawn(move |_| async move {
2787                    while latest < required {
2788                        latest = monitor.recv().await.expect("event missing");
2789                    }
2790                }));
2791            }
2792            join_all(finalizers).await;
2793
2794            // Check reporters for correct activity
2795            for reporter in reporters.iter() {
2796                // Ensure no faults
2797                reporter.assert_no_faults();
2798
2799                // Ensure no invalid signatures
2800                reporter.assert_no_invalid();
2801            }
2802
2803            // Ensure no blocked connections
2804            let blocked = oracle.blocked().await.unwrap();
2805            assert!(blocked.is_empty());
2806        });
2807    }
2808
2809    test_for_all_fixtures!(partition);
2810
2811    fn slow_and_lossy_links_seeded<S, F, L>(seed: u64, mut fixture: F) -> String
2812    where
2813        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2814        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
2815        L: Elector<S>,
2816    {
2817        // Create context
2818        let n = 5;
2819        let required_containers = View::new(50);
2820        let activity_timeout = ViewDelta::new(10);
2821        let skip_timeout = ViewDelta::new(5);
2822        let namespace = b"consensus".to_vec();
2823        let cfg = deterministic::Config::new()
2824            .with_seed(seed)
2825            .with_timeout(Some(Duration::from_secs(5_000)));
2826        let executor = deterministic::Runner::new(cfg);
2827        executor.start(|mut context| async move {
2828            // Register participants
2829            let Fixture {
2830                participants,
2831                schemes,
2832                ..
2833            } = fixture(&mut context, &namespace, n);
2834            let mut oracle =
2835                start_test_network_with_peers(context.child("network"), participants.clone(), true)
2836                    .await;
2837            let mut registrations = register_validators(&mut oracle, &participants).await;
2838
2839            // Link all validators
2840            let degraded_link = Link {
2841                latency: Duration::from_millis(200),
2842                jitter: Duration::from_millis(150),
2843                success_rate: 0.5,
2844            };
2845            link_validators(
2846                &mut oracle,
2847                &participants,
2848                Action::Link(degraded_link),
2849                None,
2850            )
2851            .await;
2852
2853            // Create engines
2854            let elector = L::default();
2855            let relay = Arc::new(mocks::relay::Relay::new());
2856            let mut reporters = Vec::new();
2857            let mut engine_handlers = Vec::new();
2858            for (idx, validator) in participants.iter().enumerate() {
2859                // Create scheme context
2860                let context = context
2861                    .child("validator")
2862                    .with_attribute("public_key", validator);
2863
2864                // Configure engine
2865                let reporter_config = mocks::reporter::Config {
2866                    participants: participants.clone().try_into().unwrap(),
2867                    scheme: schemes[idx].clone(),
2868                    elector: elector.clone(),
2869                };
2870                let reporter =
2871                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2872                reporters.push(reporter.clone());
2873                let application_cfg = mocks::application::Config {
2874                    hasher: Sha256::default(),
2875                    relay: relay.clone(),
2876                    me: validator.clone(),
2877                    propose_latency: (10.0, 5.0),
2878                    verify_latency: (10.0, 5.0),
2879                    certify_latency: (10.0, 5.0),
2880                    should_certify: mocks::application::Certifier::Always,
2881                };
2882                let (actor, application) = mocks::application::Application::new(
2883                    context.child("application"),
2884                    application_cfg,
2885                );
2886                actor.start();
2887                let blocker = oracle.control(validator.clone());
2888                let cfg = config::Config {
2889                    scheme: schemes[idx].clone(),
2890                    elector: elector.clone(),
2891                    blocker,
2892                    automaton: application.clone(),
2893                    relay: application.clone(),
2894                    reporter: reporter.clone(),
2895                    strategy: Sequential,
2896                    partition: validator.to_string(),
2897                    mailbox_size: NZUsize!(1024),
2898                    epoch: Epoch::new(333),
2899                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2900                        Epoch::new(333),
2901                    )),
2902                    leader_timeout: Duration::from_secs(1),
2903                    certification_timeout: Duration::from_secs(2),
2904                    timeout_retry: Duration::from_secs(10),
2905                    fetch_timeout: Duration::from_secs(1),
2906                    activity_timeout,
2907                    skip_timeout,
2908                    fetch_concurrent: NZUsize!(4),
2909                    replay_buffer: NZUsize!(1024 * 1024),
2910                    write_buffer: NZUsize!(1024 * 1024),
2911                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2912                    forwarding: ForwardingPolicy::Disabled,
2913                };
2914                let engine = Engine::new(context.child("engine"), cfg);
2915
2916                // Start engine
2917                let (pending, recovered, resolver) = registrations
2918                    .remove(validator)
2919                    .expect("validator should be registered");
2920                engine_handlers.push(engine.start(pending, recovered, resolver));
2921            }
2922
2923            // Wait for all engines to finish
2924            let mut finalizers = Vec::new();
2925            for reporter in reporters.iter_mut() {
2926                let (mut latest, mut monitor) = reporter.subscribe().await;
2927                finalizers.push(context.child("finalizer").spawn(move |_| async move {
2928                    while latest < required_containers {
2929                        latest = monitor.recv().await.expect("event missing");
2930                    }
2931                }));
2932            }
2933            join_all(finalizers).await;
2934
2935            // Check reporters for correct activity
2936            for reporter in reporters.iter() {
2937                // Ensure no faults
2938                reporter.assert_no_faults();
2939
2940                // Ensure no invalid signatures
2941                reporter.assert_no_invalid();
2942            }
2943
2944            // Ensure no blocked connections
2945            let blocked = oracle.blocked().await.unwrap();
2946            assert!(blocked.is_empty());
2947
2948            context.auditor().state()
2949        })
2950    }
2951
2952    fn slow_and_lossy_links<S, F, L>(fixture: F) -> String
2953    where
2954        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2955        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
2956        L: Elector<S>,
2957    {
2958        slow_and_lossy_links_seeded::<_, _, L>(6, fixture)
2959    }
2960
2961    test_for_all_fixtures!(slow_and_lossy_links);
2962
2963    fn determinism<S, F, L>(seed: u64, fixture: F)
2964    where
2965        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2966        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S> + Copy,
2967        L: Elector<S>,
2968    {
2969        // We use slow and lossy links as the deterministic test
2970        // because it is the most complex test.
2971        assert_eq!(
2972            slow_and_lossy_links_seeded::<_, _, L>(seed, fixture),
2973            slow_and_lossy_links_seeded::<_, _, L>(seed, fixture),
2974        );
2975    }
2976
2977    test_for_all_fixtures!(determinism, seeds = 5);
2978
2979    #[test_group("slow")]
2980    #[test_traced]
2981    fn test_distinct_states() {
2982        // Sanity check that different schemes produce different audit states.
2983        macro_rules! collect {
2984            ($vec:ident, $suffix:ident, $elector:ty, $fixture:expr) => {
2985                $vec.push((
2986                    stringify!($suffix),
2987                    slow_and_lossy_links_seeded::<_, _, $elector>(7, $fixture),
2988                ));
2989            };
2990        }
2991        let mut states = Vec::new();
2992        for_each_fixture!(collect!(states));
2993        for pair in states.windows(2) {
2994            assert_ne!(
2995                pair[0].1, pair[1].1,
2996                "state {} equals state {}",
2997                pair[0].0, pair[1].0
2998            );
2999        }
3000    }
3001
3002    fn conflicter<S, F, L>(seed: u64, mut fixture: F)
3003    where
3004        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3005        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3006        L: Elector<S>,
3007    {
3008        // Create context
3009        let n = 4;
3010        let required_containers = View::new(50);
3011        let activity_timeout = ViewDelta::new(10);
3012        let skip_timeout = ViewDelta::new(5);
3013        let namespace = b"consensus".to_vec();
3014        let cfg = deterministic::Config::new()
3015            .with_seed(seed)
3016            .with_timeout(Some(Duration::from_secs(30)));
3017        let executor = deterministic::Runner::new(cfg);
3018        executor.start(|mut context| async move {
3019            // Register participants
3020            let Fixture {
3021                participants,
3022                schemes,
3023                ..
3024            } = fixture(&mut context, &namespace, n);
3025            let mut oracle =
3026                start_test_network_with_peers(context.child("network"), participants.clone(), true)
3027                    .await;
3028            let mut registrations = register_validators(&mut oracle, &participants).await;
3029
3030            // Link all validators
3031            let link = Link {
3032                latency: Duration::from_millis(10),
3033                jitter: Duration::from_millis(1),
3034                success_rate: 1.0,
3035            };
3036            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
3037
3038            // Create engines
3039            let elector = L::default();
3040            let relay = Arc::new(mocks::relay::Relay::new());
3041            let mut reporters = Vec::new();
3042            for (idx_scheme, validator) in participants.iter().enumerate() {
3043                // Create scheme context
3044                let context = context
3045                    .child("validator")
3046                    .with_attribute("public_key", validator);
3047
3048                // Start engine
3049                let reporter_config = mocks::reporter::Config {
3050                    participants: participants.clone().try_into().unwrap(),
3051                    scheme: schemes[idx_scheme].clone(),
3052                    elector: elector.clone(),
3053                };
3054                let reporter =
3055                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3056                let (pending, recovered, resolver) = registrations
3057                    .remove(validator)
3058                    .expect("validator should be registered");
3059                if idx_scheme == 0 {
3060                    let cfg = mocks::conflicter::Config {
3061                        scheme: schemes[idx_scheme].clone(),
3062                    };
3063
3064                    let engine: mocks::conflicter::Conflicter<_, _, Sha256> =
3065                        mocks::conflicter::Conflicter::new(context.child("byzantine_engine"), cfg);
3066                    engine.start(pending);
3067                } else {
3068                    reporters.push(reporter.clone());
3069                    let application_cfg = mocks::application::Config {
3070                        hasher: Sha256::default(),
3071                        relay: relay.clone(),
3072                        me: validator.clone(),
3073                        propose_latency: (10.0, 5.0),
3074                        verify_latency: (10.0, 5.0),
3075                        certify_latency: (10.0, 5.0),
3076                        should_certify: mocks::application::Certifier::Always,
3077                    };
3078                    let (actor, application) = mocks::application::Application::new(
3079                        context.child("application"),
3080                        application_cfg,
3081                    );
3082                    actor.start();
3083                    let blocker = oracle.control(validator.clone());
3084                    let cfg = config::Config {
3085                        scheme: schemes[idx_scheme].clone(),
3086                        elector: elector.clone(),
3087                        blocker,
3088                        automaton: application.clone(),
3089                        relay: application.clone(),
3090                        reporter: reporter.clone(),
3091                        strategy: Sequential,
3092                        partition: validator.to_string(),
3093                        mailbox_size: NZUsize!(1024),
3094                        epoch: Epoch::new(333),
3095                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3096                            Epoch::new(333),
3097                        )),
3098                        leader_timeout: Duration::from_secs(1),
3099                        certification_timeout: Duration::from_secs(2),
3100                        timeout_retry: Duration::from_secs(10),
3101                        fetch_timeout: Duration::from_secs(1),
3102                        activity_timeout,
3103                        skip_timeout,
3104                        fetch_concurrent: NZUsize!(4),
3105                        replay_buffer: NZUsize!(1024 * 1024),
3106                        write_buffer: NZUsize!(1024 * 1024),
3107                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3108                        forwarding: ForwardingPolicy::Disabled,
3109                    };
3110                    let engine = Engine::new(context.child("engine"), cfg);
3111                    engine.start(pending, recovered, resolver);
3112                }
3113            }
3114
3115            // Wait for all engines to finish
3116            let mut finalizers = Vec::new();
3117            for reporter in reporters.iter_mut() {
3118                let (mut latest, mut monitor) = reporter.subscribe().await;
3119                finalizers.push(context.child("finalizer").spawn(move |_| async move {
3120                    while latest < required_containers {
3121                        latest = monitor.recv().await.expect("event missing");
3122                    }
3123                }));
3124            }
3125            join_all(finalizers).await;
3126
3127            // Check reporters for correct activity
3128            let byz = &participants[0];
3129            let mut count_conflicting = 0;
3130            for reporter in reporters.iter() {
3131                // Ensure only faults for byz
3132                {
3133                    let faults = reporter.faults.lock();
3134                    assert_eq!(faults.len(), 1);
3135                    let faulter = faults.get(byz).expect("byzantine party is not faulter");
3136                    for faults in faulter.values() {
3137                        for fault in faults.iter() {
3138                            match fault {
3139                                Activity::ConflictingNotarize(_) => {
3140                                    count_conflicting += 1;
3141                                }
3142                                Activity::ConflictingFinalize(_) => {
3143                                    count_conflicting += 1;
3144                                }
3145                                _ => panic!("unexpected fault: {fault:?}"),
3146                            }
3147                        }
3148                    }
3149                }
3150
3151                // Ensure no invalid signatures
3152                reporter.assert_no_invalid();
3153            }
3154            assert!(count_conflicting > 0);
3155
3156            // Ensure conflicter is blocked
3157            let blocked = oracle.blocked().await.unwrap();
3158            assert!(!blocked.is_empty());
3159            for (a, b) in blocked {
3160                assert_ne!(&a, byz);
3161                assert_eq!(&b, byz);
3162            }
3163        });
3164    }
3165
3166    test_for_all_fixtures!(conflicter, seeds = 5);
3167
3168    fn invalid<S, F, L>(seed: u64, mut fixture: F)
3169    where
3170        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3171        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3172        L: Elector<S>,
3173    {
3174        // Create context
3175        let n = 4;
3176        let required_containers = View::new(50);
3177        let activity_timeout = ViewDelta::new(10);
3178        let skip_timeout = ViewDelta::new(5);
3179        let namespace = b"consensus".to_vec();
3180        let cfg = deterministic::Config::new()
3181            .with_seed(seed)
3182            .with_timeout(Some(Duration::from_secs(30)));
3183        let executor = deterministic::Runner::new(cfg);
3184        executor.start(|mut context| async move {
3185            // Register participants
3186            let Fixture {
3187                participants,
3188                schemes,
3189                ..
3190            } = fixture(&mut context, &namespace, n);
3191
3192            let schemes: Vec<_> = schemes
3193                .into_iter()
3194                .enumerate()
3195                .map(|(idx, scheme)| {
3196                    let is_byzantine = idx == 0;
3197                    let behavior = if is_byzantine {
3198                        wrapped::Behavior::CorruptSignature
3199                    } else {
3200                        wrapped::Behavior::Honest
3201                    };
3202                    wrapped::Scheme::new(scheme, behavior)
3203                })
3204                .collect();
3205
3206            let mut oracle =
3207                start_test_network_with_peers(context.child("network"), participants.clone(), true)
3208                    .await;
3209            let mut registrations = register_validators(&mut oracle, &participants).await;
3210
3211            // Link all validators
3212            let link = Link {
3213                latency: Duration::from_millis(10),
3214                jitter: Duration::from_millis(1),
3215                success_rate: 1.0,
3216            };
3217            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
3218
3219            // Create engines
3220            let elector = wrapped::Config(L::default());
3221            let relay = Arc::new(mocks::relay::Relay::new());
3222            let mut reporters = Vec::new();
3223            for (idx_scheme, validator) in participants.iter().enumerate() {
3224                // Create scheme context
3225                let context = context
3226                    .child("validator")
3227                    .with_attribute("public_key", validator);
3228
3229                let reporter_config = mocks::reporter::Config {
3230                    participants: participants.clone().try_into().unwrap(),
3231                    scheme: schemes[idx_scheme].clone(),
3232                    elector: elector.clone(),
3233                };
3234                let reporter =
3235                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3236                reporters.push(reporter.clone());
3237
3238                let application_cfg = mocks::application::Config {
3239                    hasher: Sha256::default(),
3240                    relay: relay.clone(),
3241                    me: validator.clone(),
3242                    propose_latency: (10.0, 5.0),
3243                    verify_latency: (10.0, 5.0),
3244                    certify_latency: (10.0, 5.0),
3245                    should_certify: mocks::application::Certifier::Always,
3246                };
3247                let (actor, application) = mocks::application::Application::new(
3248                    context.child("application"),
3249                    application_cfg,
3250                );
3251                actor.start();
3252                let blocker = oracle.control(validator.clone());
3253                let cfg = config::Config {
3254                    scheme: schemes[idx_scheme].clone(),
3255                    elector: elector.clone(),
3256                    blocker,
3257                    automaton: application.clone(),
3258                    relay: application.clone(),
3259                    reporter: reporter.clone(),
3260                    strategy: Sequential,
3261                    partition: validator.clone().to_string(),
3262                    mailbox_size: NZUsize!(1024),
3263                    epoch: Epoch::new(333),
3264                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3265                        Epoch::new(333),
3266                    )),
3267                    leader_timeout: Duration::from_secs(1),
3268                    certification_timeout: Duration::from_secs(2),
3269                    timeout_retry: Duration::from_secs(10),
3270                    fetch_timeout: Duration::from_secs(1),
3271                    activity_timeout,
3272                    skip_timeout,
3273                    fetch_concurrent: NZUsize!(4),
3274                    replay_buffer: NZUsize!(1024 * 1024),
3275                    write_buffer: NZUsize!(1024 * 1024),
3276                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3277                    forwarding: ForwardingPolicy::Disabled,
3278                };
3279                let engine = Engine::new(context.child("engine"), cfg);
3280                let (pending, recovered, resolver) = registrations
3281                    .remove(validator)
3282                    .expect("validator should be registered");
3283                engine.start(pending, recovered, resolver);
3284            }
3285
3286            // Wait for all engines to finish.
3287            // The byzantine node will not finish since it will mark any finalization
3288            // certificates it creates (using its own invalid signature) as invalid.
3289            let mut finalizers = Vec::new();
3290            for reporter in reporters.iter_mut().skip(1) {
3291                let (mut latest, mut monitor) = reporter.subscribe().await;
3292                finalizers.push(context.child("finalizer").spawn(move |_| async move {
3293                    while latest < required_containers {
3294                        latest = monitor.recv().await.expect("event missing");
3295                    }
3296                }));
3297            }
3298            join_all(finalizers).await;
3299
3300            // Check all reporters for activity
3301            for (i, reporter) in reporters.iter().enumerate() {
3302                // Ensure no faults
3303                reporter.assert_no_faults();
3304
3305                // All nodes see invalid signatures since the honest reporters get unfiltered votes
3306                // once they pass the view.
3307                assert!(*reporter.invalid_votes.lock() > 0);
3308
3309                // Only the byzantine node sees invalid certificates since it constructs them from
3310                // its own invalid vote. The honest nodes reject them before reaching the reporter.
3311                let is_byzantine = i == 0;
3312                if is_byzantine {
3313                    assert!(*reporter.invalid_certificates.lock() > 0);
3314                } else {
3315                    assert_eq!(*reporter.invalid_certificates.lock(), 0);
3316                }
3317            }
3318
3319            // Ensure byzantine node is blocked by honest nodes.
3320            // The ">=" is because the Byzantine node may block itself.
3321            let blocked = oracle.blocked().await.unwrap();
3322            assert!(blocked.len() >= participants.len() - 1);
3323            let byz = &participants[0];
3324            for (_, b) in blocked {
3325                // Assert only the byzantine node is blocked.
3326                assert_eq!(&b, byz);
3327            }
3328        });
3329    }
3330
3331    test_for_all_fixtures!(invalid, seeds = 5);
3332
3333    // Test that when a node receives finalizations, it reports them.
3334    fn received_certificates_are_reported<S, F, L>(mut fixture: F)
3335    where
3336        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3337        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3338        L: Elector<S>,
3339    {
3340        let n = 4;
3341        let required_containers = View::new(10);
3342        let activity_timeout = ViewDelta::new(10);
3343        let skip_timeout = ViewDelta::new(5);
3344        let namespace = b"consensus".to_vec();
3345        let cfg = deterministic::Config::new()
3346            .with_seed(0)
3347            .with_timeout(Some(Duration::from_secs(30)));
3348        let executor = deterministic::Runner::new(cfg);
3349        executor.start(|mut context| async move {
3350            let Fixture {
3351                participants,
3352                schemes,
3353                ..
3354            } = fixture(&mut context, &namespace, n);
3355
3356            let mut oracle = start_test_network_with_peers(
3357                context.child("network"),
3358                participants.clone(),
3359                false,
3360            )
3361            .await;
3362            let mut registrations = register_validators(&mut oracle, &participants).await;
3363
3364            // Link all honest nodes. Only link node 0 to node 1.
3365            //
3366            // Node 0 cannot locally form a certificate because it only sees itself plus one honest
3367            // peer, but it should still receive the certificates relayed by that peer.
3368            let link = Link {
3369                latency: Duration::from_millis(100),
3370                jitter: Duration::from_millis(1),
3371                success_rate: 1.0,
3372            };
3373            fn link_graph(_: usize, i: usize, j: usize) -> bool {
3374                if i == 0 || j == 0 {
3375                    return i == 1 || j == 1;
3376                }
3377                true
3378            }
3379            link_validators(
3380                &mut oracle,
3381                &participants,
3382                Action::Link(link),
3383                Some(link_graph),
3384            )
3385            .await;
3386
3387            let elector = L::default();
3388            let relay = Arc::new(mocks::relay::Relay::new());
3389            let mut reporters = Vec::new();
3390            for (idx_scheme, validator) in participants.iter().enumerate() {
3391                let context = context
3392                    .child("validator")
3393                    .with_attribute("public_key", validator);
3394                let reporter_config = mocks::reporter::Config {
3395                    participants: participants.clone().try_into().unwrap(),
3396                    scheme: schemes[idx_scheme].clone(),
3397                    elector: elector.clone(),
3398                };
3399                let reporter =
3400                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3401                reporters.push(reporter.clone());
3402
3403                let application_cfg = mocks::application::Config {
3404                    hasher: Sha256::default(),
3405                    relay: relay.clone(),
3406                    me: validator.clone(),
3407                    propose_latency: (10.0, 5.0),
3408                    verify_latency: (10.0, 5.0),
3409                    certify_latency: (10.0, 5.0),
3410                    should_certify: mocks::application::Certifier::Always,
3411                };
3412                let (actor, application) = mocks::application::Application::new(
3413                    context.child("application"),
3414                    application_cfg,
3415                );
3416                actor.start();
3417                let blocker = oracle.control(validator.clone());
3418                let cfg = config::Config {
3419                    scheme: schemes[idx_scheme].clone(),
3420                    elector: elector.clone(),
3421                    blocker,
3422                    automaton: application.clone(),
3423                    relay: application.clone(),
3424                    reporter: reporter.clone(),
3425                    strategy: Sequential,
3426                    partition: validator.clone().to_string(),
3427                    mailbox_size: NZUsize!(1024),
3428                    epoch: Epoch::new(333),
3429                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3430                        Epoch::new(333),
3431                    )),
3432                    leader_timeout: Duration::from_secs(1),
3433                    certification_timeout: Duration::from_secs(2),
3434                    timeout_retry: Duration::from_secs(10),
3435                    fetch_timeout: Duration::from_secs(1),
3436                    activity_timeout,
3437                    skip_timeout,
3438                    fetch_concurrent: NZUsize!(4),
3439                    replay_buffer: NZUsize!(1024 * 1024),
3440                    write_buffer: NZUsize!(1024 * 1024),
3441                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3442                    forwarding: ForwardingPolicy::Disabled,
3443                };
3444                let engine = Engine::new(context.child("engine"), cfg);
3445                let (pending, recovered, resolver) = registrations
3446                    .remove(validator)
3447                    .expect("validator should be registered");
3448                engine.start(pending, recovered, resolver);
3449            }
3450            // Wait for an honest node to observe the finalizations
3451            let excluded_reporter = reporters[0].clone();
3452            let mut honest_reporter = reporters[1].clone();
3453            let (mut honest_latest, mut honest_monitor) = honest_reporter.subscribe().await;
3454            while honest_latest < required_containers {
3455                honest_latest = honest_monitor.recv().await.expect("event missing");
3456            }
3457
3458            // Wait for all in-flight certificates to arrive at excluded node and be reported.
3459            context.sleep(Duration::from_secs(1)).await;
3460
3461            // It should have received similar certificates to the honest node (with some
3462            // tolerance for initial views in which may not have yet been connected).
3463            let honest_notarized = {
3464                let notarizations = honest_reporter.notarizations.lock();
3465                View::range(View::new(1), required_containers.next())
3466                    .filter(|view| notarizations.contains_key(view))
3467                    .count()
3468            };
3469            let excluded_notarized = {
3470                let notarizations = excluded_reporter.notarizations.lock();
3471                View::range(View::new(1), required_containers.next())
3472                    .filter(|view| notarizations.contains_key(view))
3473                    .count()
3474            };
3475            assert!(
3476                excluded_notarized >= honest_notarized.saturating_sub(2),
3477                "honest_notarized: {honest_notarized}, excluded_notarized: {excluded_notarized}"
3478            );
3479
3480            let honest_finalized = {
3481                let finalizations = honest_reporter.finalizations.lock();
3482                View::range(View::new(1), required_containers.next())
3483                    .filter(|view| finalizations.contains_key(view))
3484                    .count()
3485            };
3486            let excluded_finalized = {
3487                let finalizations = excluded_reporter.finalizations.lock();
3488                View::range(View::new(1), required_containers.next())
3489                    .filter(|view| finalizations.contains_key(view))
3490                    .count()
3491            };
3492            assert!(
3493                excluded_finalized >= honest_finalized.saturating_sub(2),
3494                "honest_finalized: {honest_finalized}, excluded_finalized: {excluded_finalized}"
3495            );
3496        });
3497    }
3498
3499    test_for_all_fixtures!(received_certificates_are_reported);
3500
3501    fn survives_burst<S, F, L>(mut fixture: F)
3502    where
3503        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3504        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3505        L: Elector<S>,
3506    {
3507        let n = 4;
3508        let epoch = Epoch::new(333);
3509        let namespace = b"mailbox_size_one_certificate_burst".to_vec();
3510        let executor = deterministic::Runner::default();
3511        executor.start(|mut context| async move {
3512            let Fixture {
3513                participants,
3514                schemes,
3515                ..
3516            } = fixture(&mut context, &namespace, n);
3517            let me = participants[0].clone();
3518            let mut oracle =
3519                start_test_network_with_peers(context.child("network"), participants.clone(), true)
3520                    .await;
3521            let (pending, recovered, resolver) = register_validator(&mut oracle, me.clone()).await;
3522
3523            let injector_pk = PrivateKey::from_seed(9_000_000).public_key();
3524            let (mut injector_sender, _injector_receiver) = oracle
3525                .control(injector_pk.clone())
3526                .register(1, TEST_QUOTA)
3527                .await
3528                .unwrap();
3529            let link = Link {
3530                latency: Duration::from_millis(0),
3531                jitter: Duration::from_millis(0),
3532                success_rate: 1.0,
3533            };
3534            oracle
3535                .add_link(injector_pk.clone(), me.clone(), link)
3536                .await
3537                .unwrap();
3538            oracle.manager().track(
3539                1,
3540                TrackedPeers::new(
3541                    Set::from_iter_dedup(std::iter::once(me.clone())),
3542                    Set::from_iter_dedup(std::iter::once(injector_pk.clone())),
3543                ),
3544            );
3545            context.sleep(Duration::from_millis(1)).await;
3546
3547            let quorum = quorum(n) as usize;
3548            let notarization = |view: View, parent: View, payload: &[u8]| {
3549                let proposal =
3550                    Proposal::new(Round::new(epoch, view), parent, Sha256::hash(payload));
3551                let votes: Vec<_> = schemes
3552                    .iter()
3553                    .take(quorum)
3554                    .map(|scheme| TNotarize::sign(scheme, proposal.clone()).unwrap())
3555                    .collect();
3556                TNotarization::from_notarizes(&schemes[0], &votes, &Sequential)
3557                    .expect("notarization requires quorum")
3558            };
3559            let finalization = |view: View, parent: View, payload: &[u8]| {
3560                let proposal =
3561                    Proposal::new(Round::new(epoch, view), parent, Sha256::hash(payload));
3562                let votes: Vec<_> = schemes
3563                    .iter()
3564                    .take(quorum)
3565                    .map(|scheme| TFinalize::sign(scheme, proposal.clone()).unwrap())
3566                    .collect();
3567                TFinalization::from_finalizes(&schemes[0], &votes, &Sequential)
3568                    .expect("finalization requires quorum")
3569            };
3570
3571            // Load the network with certificates that the batcher will want to pass to the voter
3572            for certificate in [
3573                Certificate::Notarization(notarization(View::new(1), View::zero(), b"payload-1")),
3574                Certificate::Notarization(notarization(View::new(2), View::new(1), b"payload-2")),
3575                Certificate::Notarization(notarization(View::new(3), View::new(2), b"payload-3")),
3576                Certificate::Finalization(finalization(View::new(3), View::new(2), b"payload-3")),
3577            ] {
3578                injector_sender.send(Recipients::One(me.clone()), certificate.encode(), true);
3579            }
3580
3581            let elector = L::default();
3582            let reporter_config = mocks::reporter::Config {
3583                participants: participants.clone().try_into().unwrap(),
3584                scheme: schemes[0].clone(),
3585                elector: elector.clone(),
3586            };
3587            let reporter =
3588                mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3589            let mut monitor_reporter = reporter.clone();
3590            let (mut latest, mut monitor) = monitor_reporter.subscribe().await;
3591
3592            let relay = Arc::new(mocks::relay::Relay::new());
3593            let application_cfg = mocks::application::Config {
3594                hasher: Sha256::default(),
3595                relay: relay.clone(),
3596                me: me.clone(),
3597                propose_latency: (0.0, 0.0),
3598                verify_latency: (0.0, 0.0),
3599                certify_latency: (0.0, 0.0),
3600                should_certify: mocks::application::Certifier::Always,
3601            };
3602            let (mut application_actor, application) =
3603                mocks::application::Application::new(context.child("application"), application_cfg);
3604            application_actor.set_stall_proposals(true);
3605            application_actor.start();
3606
3607            let cfg = config::Config {
3608                scheme: schemes[0].clone(),
3609                elector,
3610                blocker: oracle.control(me.clone()),
3611                automaton: application.clone(),
3612                relay: application,
3613                reporter,
3614                strategy: Sequential,
3615                partition: me.to_string(),
3616                mailbox_size: NZUsize!(1),
3617                epoch,
3618                floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(epoch)),
3619                leader_timeout: Duration::from_secs(1),
3620                certification_timeout: Duration::from_secs(2),
3621                timeout_retry: Duration::from_secs(10),
3622                fetch_timeout: Duration::from_secs(1),
3623                activity_timeout: ViewDelta::new(10),
3624                skip_timeout: ViewDelta::new(5),
3625                fetch_concurrent: NZUsize!(4),
3626                replay_buffer: NZUsize!(1024 * 1024),
3627                write_buffer: NZUsize!(1024 * 1024),
3628                page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3629                forwarding: ForwardingPolicy::Disabled,
3630            };
3631            let engine = Engine::new(context.child("engine"), cfg);
3632            engine.start(pending, recovered, resolver);
3633
3634            while latest < View::new(3) {
3635                latest = monitor.recv().await.expect("finalization event missing");
3636            }
3637        });
3638    }
3639
3640    test_for_all_fixtures!(survives_burst);
3641
3642    fn impersonator<S, F, L>(seed: u64, mut fixture: F)
3643    where
3644        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3645        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3646        L: Elector<S>,
3647    {
3648        // Create context
3649        let n = 4;
3650        let required_containers = View::new(50);
3651        let activity_timeout = ViewDelta::new(10);
3652        let skip_timeout = ViewDelta::new(5);
3653        let namespace = b"consensus".to_vec();
3654        let cfg = deterministic::Config::new()
3655            .with_seed(seed)
3656            .with_timeout(Some(Duration::from_secs(30)));
3657        let executor = deterministic::Runner::new(cfg);
3658        executor.start(|mut context| async move {
3659            // Register participants
3660            let Fixture {
3661                participants,
3662                schemes,
3663                ..
3664            } = fixture(&mut context, &namespace, n);
3665            let mut oracle =
3666                start_test_network_with_peers(context.child("network"), participants.clone(), true)
3667                    .await;
3668            let mut registrations = register_validators(&mut oracle, &participants).await;
3669
3670            // Link all validators
3671            let link = Link {
3672                latency: Duration::from_millis(10),
3673                jitter: Duration::from_millis(1),
3674                success_rate: 1.0,
3675            };
3676            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
3677
3678            // Create engines
3679            let elector = L::default();
3680            let relay = Arc::new(mocks::relay::Relay::new());
3681            let mut reporters = Vec::new();
3682            for (idx_scheme, validator) in participants.iter().enumerate() {
3683                // Create scheme context
3684                let context = context
3685                    .child("validator")
3686                    .with_attribute("public_key", validator);
3687
3688                // Start engine
3689                let reporter_config = mocks::reporter::Config {
3690                    participants: participants.clone().try_into().unwrap(),
3691                    scheme: schemes[idx_scheme].clone(),
3692                    elector: elector.clone(),
3693                };
3694                let reporter =
3695                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3696                let (pending, recovered, resolver) = registrations
3697                    .remove(validator)
3698                    .expect("validator should be registered");
3699                if idx_scheme == 0 {
3700                    let cfg = mocks::impersonator::Config {
3701                        scheme: schemes[idx_scheme].clone(),
3702                    };
3703
3704                    let engine: mocks::impersonator::Impersonator<_, _, Sha256> =
3705                        mocks::impersonator::Impersonator::new(
3706                            context.child("byzantine_engine"),
3707                            cfg,
3708                        );
3709                    engine.start(pending);
3710                } else {
3711                    reporters.push(reporter.clone());
3712                    let application_cfg = mocks::application::Config {
3713                        hasher: Sha256::default(),
3714                        relay: relay.clone(),
3715                        me: validator.clone(),
3716                        propose_latency: (10.0, 5.0),
3717                        verify_latency: (10.0, 5.0),
3718                        certify_latency: (10.0, 5.0),
3719                        should_certify: mocks::application::Certifier::Always,
3720                    };
3721                    let (actor, application) = mocks::application::Application::new(
3722                        context.child("application"),
3723                        application_cfg,
3724                    );
3725                    actor.start();
3726                    let blocker = oracle.control(validator.clone());
3727                    let cfg = config::Config {
3728                        scheme: schemes[idx_scheme].clone(),
3729                        elector: elector.clone(),
3730                        blocker,
3731                        automaton: application.clone(),
3732                        relay: application.clone(),
3733                        reporter: reporter.clone(),
3734                        strategy: Sequential,
3735                        partition: validator.clone().to_string(),
3736                        mailbox_size: NZUsize!(1024),
3737                        epoch: Epoch::new(333),
3738                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3739                            Epoch::new(333),
3740                        )),
3741                        leader_timeout: Duration::from_secs(1),
3742                        certification_timeout: Duration::from_secs(2),
3743                        timeout_retry: Duration::from_secs(10),
3744                        fetch_timeout: Duration::from_secs(1),
3745                        activity_timeout,
3746                        skip_timeout,
3747                        fetch_concurrent: NZUsize!(4),
3748                        replay_buffer: NZUsize!(1024 * 1024),
3749                        write_buffer: NZUsize!(1024 * 1024),
3750                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3751                        forwarding: ForwardingPolicy::Disabled,
3752                    };
3753                    let engine = Engine::new(context.child("engine"), cfg);
3754                    engine.start(pending, recovered, resolver);
3755                }
3756            }
3757
3758            // Wait for all engines to finish
3759            let mut finalizers = Vec::new();
3760            for reporter in reporters.iter_mut() {
3761                let (mut latest, mut monitor) = reporter.subscribe().await;
3762                finalizers.push(context.child("finalizer").spawn(move |_| async move {
3763                    while latest < required_containers {
3764                        latest = monitor.recv().await.expect("event missing");
3765                    }
3766                }));
3767            }
3768            join_all(finalizers).await;
3769
3770            // Check reporters for correct activity
3771            let byz = &participants[0];
3772            for reporter in reporters.iter() {
3773                // Ensure no faults
3774                reporter.assert_no_faults();
3775
3776                // Ensure no invalid signatures
3777                reporter.assert_no_invalid();
3778            }
3779
3780            // Ensure invalid is blocked
3781            let blocked = oracle.blocked().await.unwrap();
3782            assert!(!blocked.is_empty());
3783            for (a, b) in blocked {
3784                assert_ne!(&a, byz);
3785                assert_eq!(&b, byz);
3786            }
3787        });
3788    }
3789
3790    test_for_all_fixtures!(impersonator, seeds = 5);
3791
3792    fn equivocator_seeded<S, F, L>(seed: u64, mut fixture: F) -> bool
3793    where
3794        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3795        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3796        L: Elector<S>,
3797    {
3798        // Create context
3799        let n = 7;
3800        let required_containers = View::new(50);
3801        let activity_timeout = ViewDelta::new(10);
3802        let skip_timeout = ViewDelta::new(5);
3803        let namespace = b"consensus".to_vec();
3804        let cfg = deterministic::Config::new()
3805            .with_seed(seed)
3806            .with_timeout(Some(Duration::from_secs(60)));
3807        let executor = deterministic::Runner::new(cfg);
3808        executor.start(|mut context| async move {
3809            // Register participants
3810            let Fixture {
3811                participants,
3812                schemes,
3813                ..
3814            } = fixture(&mut context, &namespace, n);
3815            let mut oracle =
3816                start_test_network_with_peers(context.child("network"), participants.clone(), true)
3817                    .await;
3818            let mut registrations = register_validators(&mut oracle, &participants).await;
3819
3820            // Link all validators
3821            let link = Link {
3822                latency: Duration::from_millis(10),
3823                jitter: Duration::from_millis(1),
3824                success_rate: 1.0,
3825            };
3826            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
3827
3828            // Create engines
3829            let elector = L::default();
3830            let mut engines = Vec::new();
3831            let relay = Arc::new(mocks::relay::Relay::new());
3832            let mut reporters = Vec::new();
3833            for (idx_scheme, validator) in participants.iter().enumerate() {
3834                // Create scheme context
3835                let context = context
3836                    .child("validator")
3837                    .with_attribute("public_key", validator);
3838
3839                // Start engine
3840                let reporter_config = mocks::reporter::Config {
3841                    participants: participants.clone().try_into().unwrap(),
3842                    scheme: schemes[idx_scheme].clone(),
3843                    elector: elector.clone(),
3844                };
3845                let reporter =
3846                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3847                reporters.push(reporter.clone());
3848                let (pending, recovered, resolver) = registrations
3849                    .remove(validator)
3850                    .expect("validator should be registered");
3851                if idx_scheme == 0 {
3852                    let cfg = mocks::equivocator::Config {
3853                        scheme: schemes[idx_scheme].clone(),
3854                        epoch: Epoch::new(333),
3855                        relay: relay.clone(),
3856                        hasher: Sha256::default(),
3857                        elector: elector.clone(),
3858                    };
3859
3860                    let engine = mocks::equivocator::Equivocator::new(
3861                        context.child("byzantine_engine"),
3862                        cfg,
3863                    );
3864                    engines.push(engine.start(pending, recovered));
3865                } else {
3866                    let application_cfg = mocks::application::Config {
3867                        hasher: Sha256::default(),
3868                        relay: relay.clone(),
3869                        me: validator.clone(),
3870                        propose_latency: (10.0, 5.0),
3871                        verify_latency: (10.0, 5.0),
3872                        certify_latency: (10.0, 5.0),
3873                        should_certify: mocks::application::Certifier::Always,
3874                    };
3875                    let (actor, application) = mocks::application::Application::new(
3876                        context.child("application"),
3877                        application_cfg,
3878                    );
3879                    actor.start();
3880                    let blocker = oracle.control(validator.clone());
3881                    let cfg = config::Config {
3882                        scheme: schemes[idx_scheme].clone(),
3883                        elector: elector.clone(),
3884                        blocker,
3885                        automaton: application.clone(),
3886                        relay: application.clone(),
3887                        reporter: reporter.clone(),
3888                        strategy: Sequential,
3889                        partition: validator.to_string(),
3890                        mailbox_size: NZUsize!(1024),
3891                        epoch: Epoch::new(333),
3892                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3893                            Epoch::new(333),
3894                        )),
3895                        leader_timeout: Duration::from_secs(1),
3896                        certification_timeout: Duration::from_secs(2),
3897                        timeout_retry: Duration::from_secs(10),
3898                        fetch_timeout: Duration::from_secs(1),
3899                        activity_timeout,
3900                        skip_timeout,
3901                        fetch_concurrent: NZUsize!(4),
3902                        replay_buffer: NZUsize!(1024 * 1024),
3903                        write_buffer: NZUsize!(1024 * 1024),
3904                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3905                        forwarding: ForwardingPolicy::Disabled,
3906                    };
3907                    let engine = Engine::new(context.child("engine"), cfg);
3908                    engines.push(engine.start(pending, recovered, resolver));
3909                }
3910            }
3911
3912            // Wait for all engines to hit required containers
3913            let mut finalizers = Vec::new();
3914            for reporter in reporters.iter_mut().skip(1) {
3915                let (mut latest, mut monitor) = reporter.subscribe().await;
3916                finalizers.push(context.child("finalizer").spawn(move |_| async move {
3917                    while latest < required_containers {
3918                        latest = monitor.recv().await.expect("event missing");
3919                    }
3920                }));
3921            }
3922            join_all(finalizers).await;
3923
3924            // Abort a validator
3925            let idx = context.random_range(1..engines.len()); // skip byzantine validator
3926            let validator = &participants[idx];
3927            let handle = engines.remove(idx);
3928            handle.abort();
3929            let _ = handle.await;
3930            reporters.remove(idx);
3931            info!(idx, ?validator, "aborted validator");
3932
3933            // Wait for all engines to hit required containers
3934            let mut finalizers = Vec::new();
3935            for reporter in reporters.iter_mut().skip(1) {
3936                let (mut latest, mut monitor) = reporter.subscribe().await;
3937                finalizers.push(context.child("finalizer").spawn(move |_| async move {
3938                    while latest < View::new(required_containers.get() * 2) {
3939                        latest = monitor.recv().await.expect("event missing");
3940                    }
3941                }));
3942            }
3943            join_all(finalizers).await;
3944
3945            // Recreate engine
3946            info!(idx, ?validator, "restarting validator");
3947            let context = context
3948                .child("validator_restarted")
3949                .with_attribute("public_key", validator);
3950
3951            // Start engine
3952            let reporter_config = mocks::reporter::Config {
3953                participants: participants.clone().try_into().unwrap(),
3954                scheme: schemes[idx].clone(),
3955                elector: elector.clone(),
3956            };
3957            let reporter =
3958                mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3959            let (pending, recovered, resolver) =
3960                register_validator(&mut oracle, validator.clone()).await;
3961            reporters.push(reporter.clone());
3962            let application_cfg = mocks::application::Config {
3963                hasher: Sha256::default(),
3964                relay: relay.clone(),
3965                me: validator.clone(),
3966                propose_latency: (10.0, 5.0),
3967                verify_latency: (10.0, 5.0),
3968                certify_latency: (10.0, 5.0),
3969                should_certify: mocks::application::Certifier::Always,
3970            };
3971            let (actor, application) =
3972                mocks::application::Application::new(context.child("application"), application_cfg);
3973            actor.start();
3974            let blocker = oracle.control(validator.clone());
3975            let cfg = config::Config {
3976                scheme: schemes[idx].clone(),
3977                elector: elector.clone(),
3978                blocker,
3979                automaton: application.clone(),
3980                relay: application.clone(),
3981                reporter: reporter.clone(),
3982                strategy: Sequential,
3983                partition: validator.to_string(),
3984                mailbox_size: NZUsize!(1024),
3985                epoch: Epoch::new(333),
3986                floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(Epoch::new(
3987                    333,
3988                ))),
3989                leader_timeout: Duration::from_secs(1),
3990                certification_timeout: Duration::from_secs(2),
3991                timeout_retry: Duration::from_secs(10),
3992                fetch_timeout: Duration::from_secs(1),
3993                activity_timeout,
3994                skip_timeout,
3995                fetch_concurrent: NZUsize!(4),
3996                replay_buffer: NZUsize!(1024 * 1024),
3997                write_buffer: NZUsize!(1024 * 1024),
3998                page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3999                forwarding: ForwardingPolicy::Disabled,
4000            };
4001            let engine = Engine::new(context.child("engine"), cfg);
4002            engine.start(pending, recovered, resolver);
4003
4004            // Wait for all engines to hit required containers
4005            let mut finalizers = Vec::new();
4006            for reporter in reporters.iter_mut().skip(1) {
4007                let (mut latest, mut monitor) = reporter.subscribe().await;
4008                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4009                    while latest < View::new(required_containers.get() * 3) {
4010                        latest = monitor.recv().await.expect("event missing");
4011                    }
4012                }));
4013            }
4014            join_all(finalizers).await;
4015
4016            // Check equivocator blocking (we aren't guaranteed a fault will be produced
4017            // because it may not be possible to extract a conflicting vote from the certificate
4018            // we receive)
4019            let byz = &participants[0];
4020            let blocked = oracle.blocked().await.unwrap();
4021            for (a, b) in &blocked {
4022                assert_ne!(a, byz);
4023                assert_eq!(b, byz);
4024            }
4025            !blocked.is_empty()
4026        })
4027    }
4028
4029    fn equivocator<S, F, L>(fixture: F)
4030    where
4031        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4032        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S> + Copy,
4033        L: Elector<S>,
4034    {
4035        let detected = (0..5).any(|seed| equivocator_seeded::<_, _, L>(seed, fixture));
4036        assert!(
4037            detected,
4038            "expected at least one seed to detect equivocation"
4039        );
4040    }
4041
4042    test_for_all_fixtures!(equivocator);
4043
4044    fn reconfigurer<S, F, L>(seed: u64, mut fixture: F)
4045    where
4046        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4047        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4048        L: Elector<S>,
4049    {
4050        // Create context
4051        let n = 4;
4052        let required_containers = View::new(50);
4053        let activity_timeout = ViewDelta::new(10);
4054        let skip_timeout = ViewDelta::new(5);
4055        let namespace = b"consensus".to_vec();
4056        let cfg = deterministic::Config::new()
4057            .with_seed(seed)
4058            .with_timeout(Some(Duration::from_secs(30)));
4059        let executor = deterministic::Runner::new(cfg);
4060        executor.start(|mut context| async move {
4061            // Register participants
4062            let Fixture {
4063                participants,
4064                schemes,
4065                ..
4066            } = fixture(&mut context, &namespace, n);
4067            let mut oracle =
4068                start_test_network_with_peers(context.child("network"), participants.clone(), true)
4069                    .await;
4070            let mut registrations = register_validators(&mut oracle, &participants).await;
4071
4072            // Link all validators
4073            let link = Link {
4074                latency: Duration::from_millis(10),
4075                jitter: Duration::from_millis(1),
4076                success_rate: 1.0,
4077            };
4078            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4079
4080            // Create engines
4081            let elector = L::default();
4082            let relay = Arc::new(mocks::relay::Relay::new());
4083            let mut reporters = Vec::new();
4084            for (idx_scheme, validator) in participants.iter().enumerate() {
4085                // Create scheme context
4086                let context = context
4087                    .child("validator")
4088                    .with_attribute("public_key", validator);
4089
4090                // Start engine
4091                let reporter_config = mocks::reporter::Config {
4092                    participants: participants.clone().try_into().unwrap(),
4093                    scheme: schemes[idx_scheme].clone(),
4094                    elector: elector.clone(),
4095                };
4096                let reporter =
4097                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4098                let (pending, recovered, resolver) = registrations
4099                    .remove(validator)
4100                    .expect("validator should be registered");
4101                if idx_scheme == 0 {
4102                    let cfg = mocks::reconfigurer::Config {
4103                        scheme: schemes[idx_scheme].clone(),
4104                    };
4105                    let engine: mocks::reconfigurer::Reconfigurer<_, _, Sha256> =
4106                        mocks::reconfigurer::Reconfigurer::new(
4107                            context.child("byzantine_engine"),
4108                            cfg,
4109                        );
4110                    engine.start(pending);
4111                } else {
4112                    reporters.push(reporter.clone());
4113                    let application_cfg = mocks::application::Config {
4114                        hasher: Sha256::default(),
4115                        relay: relay.clone(),
4116                        me: validator.clone(),
4117                        propose_latency: (10.0, 5.0),
4118                        verify_latency: (10.0, 5.0),
4119                        certify_latency: (10.0, 5.0),
4120                        should_certify: mocks::application::Certifier::Always,
4121                    };
4122                    let (actor, application) = mocks::application::Application::new(
4123                        context.child("application"),
4124                        application_cfg,
4125                    );
4126                    actor.start();
4127                    let blocker = oracle.control(validator.clone());
4128                    let cfg = config::Config {
4129                        scheme: schemes[idx_scheme].clone(),
4130                        elector: elector.clone(),
4131                        blocker,
4132                        automaton: application.clone(),
4133                        relay: application.clone(),
4134                        reporter: reporter.clone(),
4135                        strategy: Sequential,
4136                        partition: validator.to_string(),
4137                        mailbox_size: NZUsize!(1024),
4138                        epoch: Epoch::new(333),
4139                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4140                            Epoch::new(333),
4141                        )),
4142                        leader_timeout: Duration::from_secs(1),
4143                        certification_timeout: Duration::from_secs(2),
4144                        timeout_retry: Duration::from_secs(10),
4145                        fetch_timeout: Duration::from_secs(1),
4146                        activity_timeout,
4147                        skip_timeout,
4148                        fetch_concurrent: NZUsize!(4),
4149                        replay_buffer: NZUsize!(1024 * 1024),
4150                        write_buffer: NZUsize!(1024 * 1024),
4151                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4152                        forwarding: ForwardingPolicy::Disabled,
4153                    };
4154                    let engine = Engine::new(context.child("engine"), cfg);
4155                    engine.start(pending, recovered, resolver);
4156                }
4157            }
4158
4159            // Wait for all engines to finish
4160            let mut finalizers = Vec::new();
4161            for reporter in reporters.iter_mut() {
4162                let (mut latest, mut monitor) = reporter.subscribe().await;
4163                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4164                    while latest < required_containers {
4165                        latest = monitor.recv().await.expect("event missing");
4166                    }
4167                }));
4168            }
4169            join_all(finalizers).await;
4170
4171            // Check reporters for correct activity
4172            let byz = &participants[0];
4173            for reporter in reporters.iter() {
4174                // Ensure no faults
4175                reporter.assert_no_faults();
4176
4177                // Ensure no invalid signatures
4178                reporter.assert_no_invalid();
4179            }
4180
4181            // Ensure reconfigurer is blocked (epoch mismatch)
4182            let blocked = oracle.blocked().await.unwrap();
4183            assert!(!blocked.is_empty());
4184            for (a, b) in blocked {
4185                assert_ne!(&a, byz);
4186                assert_eq!(&b, byz);
4187            }
4188        });
4189    }
4190
4191    test_for_all_fixtures!(reconfigurer, seeds = 5);
4192
4193    fn nuller<S, F, L>(seed: u64, mut fixture: F)
4194    where
4195        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4196        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4197        L: Elector<S>,
4198    {
4199        // Create context
4200        let n = 4;
4201        let required_containers = View::new(50);
4202        let activity_timeout = ViewDelta::new(10);
4203        let skip_timeout = ViewDelta::new(5);
4204        let namespace = b"consensus".to_vec();
4205        let cfg = deterministic::Config::new()
4206            .with_seed(seed)
4207            .with_timeout(Some(Duration::from_secs(30)));
4208        let executor = deterministic::Runner::new(cfg);
4209        executor.start(|mut context| async move {
4210            // Register participants
4211            let Fixture {
4212                participants,
4213                schemes,
4214                ..
4215            } = fixture(&mut context, &namespace, n);
4216            let mut oracle =
4217                start_test_network_with_peers(context.child("network"), participants.clone(), true)
4218                    .await;
4219            let mut registrations = register_validators(&mut oracle, &participants).await;
4220
4221            // Link all validators
4222            let link = Link {
4223                latency: Duration::from_millis(10),
4224                jitter: Duration::from_millis(1),
4225                success_rate: 1.0,
4226            };
4227            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4228
4229            // Create engines
4230            let elector = L::default();
4231            let relay = Arc::new(mocks::relay::Relay::new());
4232            let mut reporters = Vec::new();
4233            for (idx_scheme, validator) in participants.iter().enumerate() {
4234                // Create scheme context
4235                let context = context
4236                    .child("validator")
4237                    .with_attribute("public_key", validator);
4238
4239                // Start engine
4240                let reporter_config = mocks::reporter::Config {
4241                    participants: participants.clone().try_into().unwrap(),
4242                    scheme: schemes[idx_scheme].clone(),
4243                    elector: elector.clone(),
4244                };
4245                let reporter =
4246                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4247                let (pending, recovered, resolver) = registrations
4248                    .remove(validator)
4249                    .expect("validator should be registered");
4250                if idx_scheme == 0 {
4251                    let cfg = mocks::nuller::Config {
4252                        scheme: schemes[idx_scheme].clone(),
4253                    };
4254                    let engine: mocks::nuller::Nuller<_, _, Sha256> =
4255                        mocks::nuller::Nuller::new(context.child("byzantine_engine"), cfg);
4256                    engine.start(pending);
4257                } else {
4258                    reporters.push(reporter.clone());
4259                    let application_cfg = mocks::application::Config {
4260                        hasher: Sha256::default(),
4261                        relay: relay.clone(),
4262                        me: validator.clone(),
4263                        propose_latency: (10.0, 5.0),
4264                        verify_latency: (10.0, 5.0),
4265                        certify_latency: (10.0, 5.0),
4266                        should_certify: mocks::application::Certifier::Always,
4267                    };
4268                    let (actor, application) = mocks::application::Application::new(
4269                        context.child("application"),
4270                        application_cfg,
4271                    );
4272                    actor.start();
4273                    let blocker = oracle.control(validator.clone());
4274                    let cfg = config::Config {
4275                        scheme: schemes[idx_scheme].clone(),
4276                        elector: elector.clone(),
4277                        blocker,
4278                        automaton: application.clone(),
4279                        relay: application.clone(),
4280                        reporter: reporter.clone(),
4281                        strategy: Sequential,
4282                        partition: validator.clone().to_string(),
4283                        mailbox_size: NZUsize!(1024),
4284                        epoch: Epoch::new(333),
4285                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4286                            Epoch::new(333),
4287                        )),
4288                        leader_timeout: Duration::from_secs(1),
4289                        certification_timeout: Duration::from_secs(2),
4290                        timeout_retry: Duration::from_secs(10),
4291                        fetch_timeout: Duration::from_secs(1),
4292                        activity_timeout,
4293                        skip_timeout,
4294                        fetch_concurrent: NZUsize!(4),
4295                        replay_buffer: NZUsize!(1024 * 1024),
4296                        write_buffer: NZUsize!(1024 * 1024),
4297                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4298                        forwarding: ForwardingPolicy::Disabled,
4299                    };
4300                    let engine = Engine::new(context.child("engine"), cfg);
4301                    engine.start(pending, recovered, resolver);
4302                }
4303            }
4304
4305            // Wait for all engines to finish
4306            let mut finalizers = Vec::new();
4307            for reporter in reporters.iter_mut() {
4308                let (mut latest, mut monitor) = reporter.subscribe().await;
4309                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4310                    while latest < required_containers {
4311                        latest = monitor.recv().await.expect("event missing");
4312                    }
4313                }));
4314            }
4315            join_all(finalizers).await;
4316
4317            // Check reporters for correct activity
4318            let byz = &participants[0];
4319            let mut count_nullify_and_finalize = 0;
4320            for reporter in reporters.iter() {
4321                // Ensure only faults for byz
4322                {
4323                    let faults = reporter.faults.lock();
4324                    assert_eq!(faults.len(), 1);
4325                    let faulter = faults.get(byz).expect("byzantine party is not faulter");
4326                    for faults in faulter.values() {
4327                        for fault in faults.iter() {
4328                            match fault {
4329                                Activity::NullifyFinalize(_) => {
4330                                    count_nullify_and_finalize += 1;
4331                                }
4332                                _ => panic!("unexpected fault: {fault:?}"),
4333                            }
4334                        }
4335                    }
4336                }
4337
4338                // Ensure no invalid signatures
4339                reporter.assert_no_invalid();
4340            }
4341            assert!(count_nullify_and_finalize > 0);
4342
4343            // Ensure nullifier is blocked
4344            let blocked = oracle.blocked().await.unwrap();
4345            assert!(!blocked.is_empty());
4346            for (a, b) in blocked {
4347                assert_ne!(&a, byz);
4348                assert_eq!(&b, byz);
4349            }
4350        });
4351    }
4352
4353    test_for_all_fixtures!(nuller, seeds = 5);
4354
4355    fn outdated<S, F, L>(seed: u64, mut fixture: F)
4356    where
4357        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4358        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4359        L: Elector<S>,
4360    {
4361        // Create context
4362        let n = 4;
4363        let required_containers = View::new(100);
4364        let activity_timeout = ViewDelta::new(10);
4365        let skip_timeout = ViewDelta::new(5);
4366        let namespace = b"consensus".to_vec();
4367        let cfg = deterministic::Config::new()
4368            .with_seed(seed)
4369            .with_timeout(Some(Duration::from_secs(30)));
4370        let executor = deterministic::Runner::new(cfg);
4371        executor.start(|mut context| async move {
4372            // Register participants
4373            let Fixture {
4374                participants,
4375                schemes,
4376                ..
4377            } = fixture(&mut context, &namespace, n);
4378            let mut oracle =
4379                start_test_network_with_peers(context.child("network"), participants.clone(), true)
4380                    .await;
4381            let mut registrations = register_validators(&mut oracle, &participants).await;
4382
4383            // Link all validators
4384            let link = Link {
4385                latency: Duration::from_millis(10),
4386                jitter: Duration::from_millis(1),
4387                success_rate: 1.0,
4388            };
4389            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4390
4391            // Create engines
4392            let elector = L::default();
4393            let relay = Arc::new(mocks::relay::Relay::new());
4394            let mut reporters = Vec::new();
4395            for (idx_scheme, validator) in participants.iter().enumerate() {
4396                // Create scheme context
4397                let context = context
4398                    .child("validator")
4399                    .with_attribute("public_key", validator);
4400
4401                // Start engine
4402                let reporter_config = mocks::reporter::Config {
4403                    participants: participants.clone().try_into().unwrap(),
4404                    scheme: schemes[idx_scheme].clone(),
4405                    elector: elector.clone(),
4406                };
4407                let reporter =
4408                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4409                let (pending, recovered, resolver) = registrations
4410                    .remove(validator)
4411                    .expect("validator should be registered");
4412                if idx_scheme == 0 {
4413                    let cfg = mocks::outdated::Config {
4414                        scheme: schemes[idx_scheme].clone(),
4415                        view_delta: ViewDelta::new(activity_timeout.get().saturating_mul(4)),
4416                    };
4417                    let engine: mocks::outdated::Outdated<_, _, Sha256> =
4418                        mocks::outdated::Outdated::new(context.child("byzantine_engine"), cfg);
4419                    engine.start(pending);
4420                } else {
4421                    reporters.push(reporter.clone());
4422                    let application_cfg = mocks::application::Config {
4423                        hasher: Sha256::default(),
4424                        relay: relay.clone(),
4425                        me: validator.clone(),
4426                        propose_latency: (10.0, 5.0),
4427                        verify_latency: (10.0, 5.0),
4428                        certify_latency: (10.0, 5.0),
4429                        should_certify: mocks::application::Certifier::Always,
4430                    };
4431                    let (actor, application) = mocks::application::Application::new(
4432                        context.child("application"),
4433                        application_cfg,
4434                    );
4435                    actor.start();
4436                    let blocker = oracle.control(validator.clone());
4437                    let cfg = config::Config {
4438                        scheme: schemes[idx_scheme].clone(),
4439                        elector: elector.clone(),
4440                        blocker,
4441                        automaton: application.clone(),
4442                        relay: application.clone(),
4443                        reporter: reporter.clone(),
4444                        strategy: Sequential,
4445                        partition: validator.clone().to_string(),
4446                        mailbox_size: NZUsize!(1024),
4447                        epoch: Epoch::new(333),
4448                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4449                            Epoch::new(333),
4450                        )),
4451                        leader_timeout: Duration::from_secs(1),
4452                        certification_timeout: Duration::from_secs(2),
4453                        timeout_retry: Duration::from_secs(10),
4454                        fetch_timeout: Duration::from_secs(1),
4455                        activity_timeout,
4456                        skip_timeout,
4457                        fetch_concurrent: NZUsize!(4),
4458                        replay_buffer: NZUsize!(1024 * 1024),
4459                        write_buffer: NZUsize!(1024 * 1024),
4460                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4461                        forwarding: ForwardingPolicy::Disabled,
4462                    };
4463                    let engine = Engine::new(context.child("engine"), cfg);
4464                    engine.start(pending, recovered, resolver);
4465                }
4466            }
4467
4468            // Wait for all engines to finish
4469            let mut finalizers = Vec::new();
4470            for reporter in reporters.iter_mut() {
4471                let (mut latest, mut monitor) = reporter.subscribe().await;
4472                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4473                    while latest < required_containers {
4474                        latest = monitor.recv().await.expect("event missing");
4475                    }
4476                }));
4477            }
4478            join_all(finalizers).await;
4479
4480            // Check reporters for correct activity
4481            for reporter in reporters.iter() {
4482                // Ensure no faults
4483                reporter.assert_no_faults();
4484
4485                // Ensure no invalid signatures
4486                reporter.assert_no_invalid();
4487            }
4488
4489            // Ensure no blocked connections
4490            let blocked = oracle.blocked().await.unwrap();
4491            assert!(blocked.is_empty());
4492        });
4493    }
4494
4495    test_for_all_fixtures!(outdated, seeds = 5);
4496
4497    fn run_1k<S, F, L>(mut fixture: F)
4498    where
4499        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4500        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4501        L: Elector<S>,
4502    {
4503        // Create context
4504        let n = 10;
4505        let required_containers = View::new(1_000);
4506        let activity_timeout = ViewDelta::new(10);
4507        let skip_timeout = ViewDelta::new(5);
4508        let namespace = b"consensus".to_vec();
4509        let cfg = deterministic::Config::new();
4510        let executor = deterministic::Runner::new(cfg);
4511        executor.start(|mut context| async move {
4512            // Register participants
4513            let Fixture {
4514                participants,
4515                schemes,
4516                ..
4517            } = fixture(&mut context, &namespace, n);
4518            let mut oracle =
4519                start_test_network_with_peers(context.child("network"), participants.clone(), true)
4520                    .await;
4521            let mut registrations = register_validators(&mut oracle, &participants).await;
4522
4523            // Link all validators
4524            let link = Link {
4525                latency: Duration::from_millis(80),
4526                jitter: Duration::from_millis(10),
4527                success_rate: 0.98,
4528            };
4529            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4530
4531            // Create engines
4532            let elector = L::default();
4533            let relay = Arc::new(mocks::relay::Relay::new());
4534            let mut reporters = Vec::new();
4535            let mut engine_handlers = Vec::new();
4536            for (idx, validator) in participants.iter().enumerate() {
4537                // Create scheme context
4538                let context = context
4539                    .child("validator")
4540                    .with_attribute("public_key", validator);
4541
4542                // Configure engine
4543                let reporter_config = mocks::reporter::Config {
4544                    participants: participants.clone().try_into().unwrap(),
4545                    scheme: schemes[idx].clone(),
4546                    elector: elector.clone(),
4547                };
4548                let reporter =
4549                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4550                reporters.push(reporter.clone());
4551                let application_cfg = mocks::application::Config {
4552                    hasher: Sha256::default(),
4553                    relay: relay.clone(),
4554                    me: validator.clone(),
4555                    propose_latency: (100.0, 50.0),
4556                    verify_latency: (50.0, 40.0),
4557                    certify_latency: (50.0, 40.0),
4558                    should_certify: mocks::application::Certifier::Always,
4559                };
4560                let (actor, application) = mocks::application::Application::new(
4561                    context.child("application"),
4562                    application_cfg,
4563                );
4564                actor.start();
4565                let blocker = oracle.control(validator.clone());
4566                let cfg = config::Config {
4567                    scheme: schemes[idx].clone(),
4568                    elector: elector.clone(),
4569                    blocker,
4570                    automaton: application.clone(),
4571                    relay: application.clone(),
4572                    reporter: reporter.clone(),
4573                    strategy: Sequential,
4574                    partition: validator.to_string(),
4575                    mailbox_size: NZUsize!(1024),
4576                    epoch: Epoch::new(333),
4577                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4578                        Epoch::new(333),
4579                    )),
4580                    leader_timeout: Duration::from_secs(1),
4581                    certification_timeout: Duration::from_secs(2),
4582                    timeout_retry: Duration::from_secs(10),
4583                    fetch_timeout: Duration::from_secs(1),
4584                    activity_timeout,
4585                    skip_timeout,
4586                    fetch_concurrent: NZUsize!(4),
4587                    replay_buffer: NZUsize!(1024 * 1024),
4588                    write_buffer: NZUsize!(1024 * 1024),
4589                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4590                    forwarding: ForwardingPolicy::Disabled,
4591                };
4592                let engine = Engine::new(context.child("engine"), cfg);
4593
4594                // Start engine
4595                let (pending, recovered, resolver) = registrations
4596                    .remove(validator)
4597                    .expect("validator should be registered");
4598                engine_handlers.push(engine.start(pending, recovered, resolver));
4599            }
4600
4601            // Wait for all engines to finish
4602            let mut finalizers = Vec::new();
4603            for reporter in reporters.iter_mut() {
4604                let (mut latest, mut monitor) = reporter.subscribe().await;
4605                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4606                    while latest < required_containers {
4607                        latest = monitor.recv().await.expect("event missing");
4608                    }
4609                }));
4610            }
4611            join_all(finalizers).await;
4612
4613            // Check reporters for correct activity
4614            for reporter in reporters.iter() {
4615                // Ensure no faults
4616                reporter.assert_no_faults();
4617
4618                // Ensure no invalid signatures
4619                reporter.assert_no_invalid();
4620            }
4621
4622            // Ensure no blocked connections
4623            let blocked = oracle.blocked().await.unwrap();
4624            assert!(blocked.is_empty());
4625        })
4626    }
4627
4628    #[test_group("slow")]
4629    #[test_traced]
4630    fn test_1k() {
4631        run_1k::<_, _, RoundRobin>(scheme_mocks::fixture);
4632    }
4633
4634    fn engine_shutdown<S, F, L>(seed: u64, mut fixture: F, graceful: bool)
4635    where
4636        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4637        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4638        L: Elector<S>,
4639    {
4640        let n = 1;
4641        let namespace = b"consensus".to_vec();
4642        let cfg = deterministic::Config::default()
4643            .with_seed(seed)
4644            .with_timeout(Some(Duration::from_secs(10)));
4645        let executor = deterministic::Runner::new(cfg);
4646        executor.start(|mut context| async move {
4647            // Register a single participant
4648            let Fixture {
4649                participants,
4650                schemes,
4651                ..
4652            } = fixture(&mut context, &namespace, n);
4653            let mut oracle =
4654                start_test_network_with_peers(context.child("network"), participants.clone(), true)
4655                    .await;
4656            let mut registrations = register_validators(&mut oracle, &participants).await;
4657
4658            // Link the single validator to itself (no-ops for completeness)
4659            let link = Link {
4660                latency: Duration::from_millis(1),
4661                jitter: Duration::from_millis(0),
4662                success_rate: 1.0,
4663            };
4664            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4665
4666            // Create engine
4667            let elector = L::default();
4668            let reporter_config = mocks::reporter::Config {
4669                participants: participants.clone().try_into().unwrap(),
4670                scheme: schemes[0].clone(),
4671                elector: elector.clone(),
4672            };
4673            let reporter =
4674                mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4675            let relay = Arc::new(mocks::relay::Relay::new());
4676            let application_cfg = mocks::application::Config {
4677                hasher: Sha256::default(),
4678                relay: relay.clone(),
4679                me: participants[0].clone(),
4680                propose_latency: (1.0, 0.0),
4681                verify_latency: (1.0, 0.0),
4682                certify_latency: (1.0, 0.0),
4683                should_certify: mocks::application::Certifier::Always,
4684            };
4685            let (actor, application) =
4686                mocks::application::Application::new(context.child("application"), application_cfg);
4687            actor.start();
4688            let blocker = oracle.control(participants[0].clone());
4689            let cfg = config::Config {
4690                scheme: schemes[0].clone(),
4691                elector: elector.clone(),
4692                blocker,
4693                automaton: application.clone(),
4694                relay: application.clone(),
4695                reporter: reporter.clone(),
4696                strategy: Sequential,
4697                partition: participants[0].clone().to_string(),
4698                mailbox_size: NZUsize!(64),
4699                epoch: Epoch::new(333),
4700                floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(Epoch::new(
4701                    333,
4702                ))),
4703                leader_timeout: Duration::from_millis(50),
4704                certification_timeout: Duration::from_millis(100),
4705                timeout_retry: Duration::from_millis(250),
4706                fetch_timeout: Duration::from_millis(50),
4707                activity_timeout: ViewDelta::new(4),
4708                skip_timeout: ViewDelta::new(2),
4709                fetch_concurrent: NZUsize!(4),
4710                replay_buffer: NZUsize!(1024 * 16),
4711                write_buffer: NZUsize!(1024 * 16),
4712                page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4713                forwarding: ForwardingPolicy::Disabled,
4714            };
4715            let engine = Engine::new(context.child("engine"), cfg);
4716
4717            // Start engine
4718            let (pending, recovered, resolver) = registrations
4719                .remove(&participants[0])
4720                .expect("validator should be registered");
4721            let handle = engine.start(pending, recovered, resolver);
4722
4723            // Allow tasks to start
4724            context.sleep(Duration::from_millis(1000)).await;
4725
4726            // Count running tasks under the engine prefix
4727            let running_before = count_running_tasks(&context, "engine");
4728            assert!(
4729                running_before > 0,
4730                "at least one engine task should be running"
4731            );
4732
4733            // Make sure the engine is still running after some time
4734            context.sleep(Duration::from_millis(1500)).await;
4735            assert!(
4736                count_running_tasks(&context, "engine") > 0,
4737                "engine tasks should still be running"
4738            );
4739
4740            // Shutdown engine and ensure children stop
4741            let running_after = if graceful {
4742                let result = context
4743                    .child("stop")
4744                    .stop(0, Some(Duration::from_secs(5)))
4745                    .await;
4746                assert!(
4747                    result.is_ok(),
4748                    "graceful shutdown should complete: {result:?}"
4749                );
4750                count_running_tasks(&context, "engine")
4751            } else {
4752                handle.abort();
4753                let _ = handle.await; // ensure parent tear-down runs
4754
4755                // Give the runtime a tick to process aborts
4756                context.sleep(Duration::from_millis(1000)).await;
4757                count_running_tasks(&context, "engine")
4758            };
4759            assert_eq!(
4760                running_after, 0,
4761                "all engine tasks should be stopped, but {running_after} still running"
4762            );
4763        });
4764    }
4765
4766    fn children_shutdown_on_engine_abort<S, F, L>(seed: u64, fixture: F)
4767    where
4768        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4769        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4770        L: Elector<S>,
4771    {
4772        engine_shutdown::<S, F, L>(seed, fixture, false);
4773    }
4774
4775    test_for_all_fixtures!(children_shutdown_on_engine_abort, seeds = 10);
4776
4777    fn graceful_shutdown<S, F, L>(seed: u64, fixture: F)
4778    where
4779        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4780        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4781        L: Elector<S>,
4782    {
4783        engine_shutdown::<S, F, L>(seed, fixture, true);
4784    }
4785
4786    test_for_all_fixtures!(graceful_shutdown, seeds = 10);
4787
4788    fn attributable_reporter_filtering<S, F, L>(mut fixture: F)
4789    where
4790        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4791        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4792        L: Elector<S>,
4793    {
4794        let n = 3;
4795        let required_containers = View::new(10);
4796        let activity_timeout = ViewDelta::new(10);
4797        let skip_timeout = ViewDelta::new(5);
4798        let namespace = b"consensus".to_vec();
4799        let executor = deterministic::Runner::timed(Duration::from_secs(30));
4800        executor.start(|mut context| async move {
4801            // Register participants
4802            let Fixture {
4803                participants,
4804                schemes,
4805                ..
4806            } = fixture(&mut context, &namespace, n);
4807            let mut oracle = start_test_network_with_peers(
4808                context.child("network"),
4809                participants.clone(),
4810                false,
4811            )
4812            .await;
4813            let mut registrations = register_validators(&mut oracle, &participants).await;
4814
4815            // Link all validators
4816            let link = Link {
4817                latency: Duration::from_millis(10),
4818                jitter: Duration::from_millis(1),
4819                success_rate: 1.0,
4820            };
4821            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4822
4823            // Create engines with `AttributableReporter` wrapper
4824            let elector = L::default();
4825            let relay = Arc::new(mocks::relay::Relay::new());
4826            let mut reporters = Vec::new();
4827            for (idx, validator) in participants.iter().enumerate() {
4828                let context = context
4829                    .child("validator")
4830                    .with_attribute("public_key", validator);
4831
4832                let reporter_config = mocks::reporter::Config {
4833                    participants: participants.clone().try_into().unwrap(),
4834                    scheme: schemes[idx].clone(),
4835                    elector: elector.clone(),
4836                };
4837                let mock_reporter =
4838                    mocks::reporter::Reporter::new(context.child("mock_reporter"), reporter_config);
4839
4840                // Wrap with `AttributableReporter`
4841                let attributable_reporter = scheme::reporter::AttributableReporter::new(
4842                    context.child("rng"),
4843                    schemes[idx].clone(),
4844                    mock_reporter.clone(),
4845                    Sequential,
4846                    true, // Enable verification
4847                );
4848                reporters.push(mock_reporter.clone());
4849
4850                let application_cfg = mocks::application::Config {
4851                    hasher: Sha256::default(),
4852                    relay: relay.clone(),
4853                    me: validator.clone(),
4854                    propose_latency: (10.0, 5.0),
4855                    verify_latency: (10.0, 5.0),
4856                    certify_latency: (10.0, 5.0),
4857                    should_certify: mocks::application::Certifier::Always,
4858                };
4859                let (actor, application) = mocks::application::Application::new(
4860                    context.child("application"),
4861                    application_cfg,
4862                );
4863                actor.start();
4864                let blocker = oracle.control(validator.clone());
4865                let cfg = config::Config {
4866                    scheme: schemes[idx].clone(),
4867                    elector: elector.clone(),
4868                    blocker,
4869                    automaton: application.clone(),
4870                    relay: application.clone(),
4871                    reporter: attributable_reporter,
4872                    strategy: Sequential,
4873                    partition: validator.to_string(),
4874                    mailbox_size: NZUsize!(1024),
4875                    epoch: Epoch::new(333),
4876                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4877                        Epoch::new(333),
4878                    )),
4879                    leader_timeout: Duration::from_secs(1),
4880                    certification_timeout: Duration::from_secs(2),
4881                    timeout_retry: Duration::from_secs(10),
4882                    fetch_timeout: Duration::from_secs(1),
4883                    activity_timeout,
4884                    skip_timeout,
4885                    fetch_concurrent: NZUsize!(4),
4886                    replay_buffer: NZUsize!(1024 * 1024),
4887                    write_buffer: NZUsize!(1024 * 1024),
4888                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4889                    forwarding: ForwardingPolicy::Disabled,
4890                };
4891                let engine = Engine::new(context.child("engine"), cfg);
4892
4893                // Start engine
4894                let (pending, recovered, resolver) = registrations
4895                    .remove(validator)
4896                    .expect("validator should be registered");
4897                engine.start(pending, recovered, resolver);
4898            }
4899
4900            // Wait for all engines to finish
4901            let mut finalizers = Vec::new();
4902            for reporter in reporters.iter_mut() {
4903                let (mut latest, mut monitor) = reporter.subscribe().await;
4904                finalizers.push(context.child("finalizer").spawn(move |_| async move {
4905                    while latest < required_containers {
4906                        latest = monitor.recv().await.expect("event missing");
4907                    }
4908                }));
4909            }
4910            join_all(finalizers).await;
4911
4912            // Verify filtering behavior based on scheme attributability
4913            for reporter in reporters.iter() {
4914                // Ensure no faults (normal operation)
4915                reporter.assert_no_faults();
4916
4917                // Ensure no invalid signatures
4918                reporter.assert_no_invalid();
4919
4920                // Check that we have certificates reported
4921                {
4922                    let notarizations = reporter.notarizations.lock();
4923                    let finalizations = reporter.finalizations.lock();
4924                    assert!(
4925                        !notarizations.is_empty() || !finalizations.is_empty(),
4926                        "Certificates should be reported"
4927                    );
4928                }
4929
4930                // Check notarizes
4931                let notarizes = reporter.notarizes.lock();
4932                let last_view = notarizes.keys().max().cloned().unwrap_or_default();
4933                for (view, payloads) in notarizes.iter() {
4934                    if *view == last_view {
4935                        continue; // Skip last view
4936                    }
4937
4938                    let signers: usize = payloads.values().map(|signers| signers.len()).sum();
4939
4940                    // For attributable schemes, we should see peer activities
4941                    if S::is_attributable() {
4942                        assert!(signers > 1, "view {view}: {signers}");
4943                    } else {
4944                        // For non-attributable, we shouldn't see any peer activities
4945                        assert_eq!(signers, 0);
4946                    }
4947                }
4948
4949                // Check finalizes
4950                let finalizes = reporter.finalizes.lock();
4951                for payloads in finalizes.values() {
4952                    let signers: usize = payloads.values().map(|signers| signers.len()).sum();
4953
4954                    // For attributable schemes, we should see peer activities
4955                    if S::is_attributable() {
4956                        assert!(signers > 1);
4957                    } else {
4958                        // For non-attributable, we shouldn't see any peer activities
4959                        assert_eq!(signers, 0);
4960                    }
4961                }
4962            }
4963
4964            // Ensure no blocked connections (normal operation)
4965            let blocked = oracle.blocked().await.unwrap();
4966            assert!(blocked.is_empty());
4967        });
4968    }
4969
4970    test_for_all_fixtures!(attributable_reporter_filtering);
4971
4972    fn split_views_no_lockup<S, F, L>(mut fixture: F)
4973    where
4974        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4975        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4976        L: Elector<S>,
4977    {
4978        // Scenario:
4979        // - View F: Finalization of B_1 seen by all participants.
4980        // - View F+1:
4981        //   - Nullification seen by honest (4..=6,7) and all 3 byzantines
4982        //   - Notarization of B_2A seen by honest (1..=3)
4983        // - View F+2:
4984        //   - Nullification seen by honest (1..=3,7) and all 3 byzantines
4985        //   - Notarization of B_2B seen by honest (4..=6)
4986        // - View F+3: Nullification. Seen by all participants.
4987        // - Then ensure progress resumes beyond F+3 after reconnecting
4988
4989        // Define participant types
4990        enum ParticipantType {
4991            Group1,    // receives notarization for f+1, nullification for f+2
4992            Group2,    // receives nullification for f+1, notarization for f+2
4993            Ignorant,  // receives nullification for f+1 and f+2
4994            Byzantine, // nullify-only
4995        }
4996        let get_type = |idx: usize| -> ParticipantType {
4997            match idx {
4998                0..3 => ParticipantType::Group1,
4999                3..6 => ParticipantType::Group2,
5000                6 => ParticipantType::Ignorant,
5001                7..10 => ParticipantType::Byzantine,
5002                _ => unreachable!(),
5003            }
5004        };
5005
5006        // Create context
5007        let n = 10;
5008        let quorum = quorum(n) as usize;
5009        assert_eq!(quorum, 7);
5010        let activity_timeout = ViewDelta::new(10);
5011        let skip_timeout = ViewDelta::new(5);
5012        let namespace = b"consensus".to_vec();
5013        let executor = deterministic::Runner::timed(Duration::from_secs(300));
5014        executor.start(|mut context| async move {
5015            // Register participants
5016            let Fixture {
5017                participants,
5018                schemes,
5019                ..
5020            } = fixture(&mut context, &namespace, n);
5021            let mut oracle = start_test_network_with_peers(
5022                context.child("network"),
5023                participants.clone(),
5024                false,
5025            )
5026            .await;
5027            let mut registrations = register_validators(&mut oracle, &participants).await;
5028
5029            // ========== Build the certificates manually ==========
5030
5031            // Helper: assemble finalization from explicit signer indices
5032            let build_finalization = |proposal: &Proposal<D>| -> TFinalization<_, D> {
5033                let votes: Vec<_> = (0..=quorum)
5034                    .map(|i| TFinalize::sign(&schemes[i], proposal.clone()).unwrap())
5035                    .collect();
5036                TFinalization::from_finalizes(&schemes[0], &votes, &Sequential)
5037                    .expect("finalization quorum")
5038            };
5039            // Helper: assemble notarization from explicit signer indices
5040            let build_notarization = |proposal: &Proposal<D>| -> TNotarization<_, D> {
5041                let votes: Vec<_> = (0..=quorum)
5042                    .map(|i| TNotarize::sign(&schemes[i], proposal.clone()).unwrap())
5043                    .collect();
5044                TNotarization::from_notarizes(&schemes[0], &votes, &Sequential)
5045                    .expect("notarization quorum")
5046            };
5047            let build_nullification = |round: Round| -> TNullification<_> {
5048                let votes: Vec<_> = (0..=quorum)
5049                    .map(|i| TNullify::sign::<D>(&schemes[i], round).unwrap())
5050                    .collect();
5051                TNullification::from_nullifies(&schemes[0], &votes, &Sequential)
5052                    .expect("nullification quorum")
5053            };
5054            // Choose F=1 and construct B_1, B_2A, B_2B
5055            let f_view = 1;
5056            let round_f = Round::new(Epoch::new(333), View::new(f_view));
5057            let payload_b0 = Sha256::hash(b"B_F");
5058            let proposal_b0 = Proposal::new(round_f, View::new(f_view - 1), payload_b0);
5059            let payload_b1a = Sha256::hash(b"B_G1");
5060            let proposal_b1a = Proposal::new(
5061                Round::new(Epoch::new(333), View::new(f_view + 1)),
5062                View::new(f_view),
5063                payload_b1a,
5064            );
5065            let payload_b1b = Sha256::hash(b"B_G2");
5066            let proposal_b1b = Proposal::new(
5067                Round::new(Epoch::new(333), View::new(f_view + 2)),
5068                View::new(f_view),
5069                payload_b1b,
5070            );
5071
5072            // Build notarization and finalization for the first block
5073            let b0_notarization = build_notarization(&proposal_b0);
5074            let b0_finalization = build_finalization(&proposal_b0);
5075            // Build notarizations for F+1 and F+2
5076            let b1a_notarization = build_notarization(&proposal_b1a);
5077            let b1b_notarization = build_notarization(&proposal_b1b);
5078            // Build nullifications for F+1 and F+2
5079            let null_a = build_nullification(Round::new(Epoch::new(333), View::new(f_view + 1)));
5080            let null_b = build_nullification(Round::new(Epoch::new(333), View::new(f_view + 2)));
5081
5082            // Create an 11th non-participant injector and obtain senders
5083            let injector_pk = PrivateKey::from_seed(1_000_000).public_key();
5084            let (mut injector_sender, _inj_certificate_receiver) = oracle
5085                .control(injector_pk.clone())
5086                .register(1, TEST_QUOTA)
5087                .await
5088                .unwrap();
5089
5090            // Create minimal one-way links from injector to all participants (not full mesh)
5091            let link = Link {
5092                latency: Duration::from_millis(10),
5093                jitter: Duration::from_millis(0),
5094                success_rate: 1.0,
5095            };
5096            for p in participants.iter() {
5097                oracle
5098                    .add_link(injector_pk.clone(), p.clone(), link.clone())
5099                    .await
5100                    .unwrap();
5101            }
5102            oracle.manager().track(
5103                1,
5104                TrackedPeers::new(
5105                    Set::from_iter_dedup(participants.iter().cloned()),
5106                    Set::from_iter_dedup(std::slice::from_ref(&injector_pk).iter().cloned()),
5107                ),
5108            );
5109            context.sleep(Duration::from_millis(10)).await;
5110
5111            // ========== Broadcast certificates over recovered network. ==========
5112
5113            // View F:
5114            let msg = Certificate::<_, D>::Notarization(b0_notarization).encode();
5115            injector_sender.send(Recipients::All, msg, true);
5116            let msg = Certificate::<_, D>::Finalization(b0_finalization).encode();
5117            injector_sender.send(Recipients::All, msg, true);
5118            // View F+1:
5119            let notarization_msg = Certificate::<_, D>::Notarization(b1a_notarization);
5120            let nullification_msg = Certificate::<_, D>::Nullification(null_a.clone());
5121            for (i, participant) in participants.iter().enumerate() {
5122                let recipient = Recipients::One(participant.clone());
5123                let msg = match get_type(i) {
5124                    ParticipantType::Group1 => notarization_msg.encode(),
5125                    _ => nullification_msg.encode(),
5126                };
5127                injector_sender.send(recipient, msg, true);
5128            }
5129            // View F+2:
5130            let notarization_msg = Certificate::<_, D>::Notarization(b1b_notarization);
5131            let nullification_msg = Certificate::<_, D>::Nullification(null_b.clone());
5132            for (i, participant) in participants.iter().enumerate() {
5133                let recipient = Recipients::One(participant.clone());
5134                let msg = match get_type(i) {
5135                    ParticipantType::Group2 => notarization_msg.encode(),
5136                    _ => nullification_msg.encode(),
5137                };
5138                injector_sender.send(recipient, msg, true);
5139            }
5140
5141            // ========== Create engines ==========
5142
5143            // Start engines after preloading certificates into each participant's
5144            // recovered channel (ensuring processing before any leader attempts to issue a
5145            // conflicting vote).
5146            let elector = L::default();
5147            let relay = Arc::new(mocks::relay::Relay::new());
5148            let mut honest_reporters = Vec::new();
5149            for (idx, validator) in participants.iter().enumerate() {
5150                let (pending, recovered, resolver) = registrations
5151                    .remove(validator)
5152                    .expect("validator should be registered");
5153                let participant_type = get_type(idx);
5154                if matches!(participant_type, ParticipantType::Byzantine) {
5155                    // Byzantine engines
5156                    let cfg = mocks::nullify_only::Config {
5157                        scheme: schemes[idx].clone(),
5158                    };
5159                    let engine: mocks::nullify_only::NullifyOnly<_, _, Sha256> =
5160                        mocks::nullify_only::NullifyOnly::new(
5161                            context
5162                                .child("byzantine")
5163                                .with_attribute("public_key", validator),
5164                            cfg,
5165                        );
5166                    engine.start(pending);
5167                    // Recovered/resolver channels are unused for byzantine actors.
5168                    drop(recovered);
5169                    drop(resolver);
5170                } else {
5171                    // Honest engines
5172                    let reporter_config = mocks::reporter::Config {
5173                        participants: participants.clone().try_into().unwrap(),
5174                        scheme: schemes[idx].clone(),
5175                        elector: elector.clone(),
5176                    };
5177                    let reporter = mocks::reporter::Reporter::new(
5178                        context
5179                            .child("reporter")
5180                            .with_attribute("public_key", validator),
5181                        reporter_config,
5182                    );
5183                    honest_reporters.push(reporter.clone());
5184
5185                    let application_cfg = mocks::application::Config {
5186                        hasher: Sha256::default(),
5187                        relay: relay.clone(),
5188                        me: validator.clone(),
5189                        propose_latency: (250.0, 50.0), // ensure we process certificates first
5190                        verify_latency: (10.0, 5.0),
5191                        certify_latency: (10.0, 5.0),
5192                        should_certify: mocks::application::Certifier::Always,
5193                    };
5194                    let (actor, application) = mocks::application::Application::new(
5195                        context
5196                            .child("application")
5197                            .with_attribute("public_key", validator),
5198                        application_cfg,
5199                    );
5200                    actor.start();
5201                    let blocker = oracle.control(validator.clone());
5202                    let cfg = config::Config {
5203                        scheme: schemes[idx].clone(),
5204                        elector: elector.clone(),
5205                        blocker,
5206                        automaton: application.clone(),
5207                        relay: application.clone(),
5208                        reporter: reporter.clone(),
5209                        strategy: Sequential,
5210                        partition: validator.to_string(),
5211                        mailbox_size: NZUsize!(1024),
5212                        epoch: Epoch::new(333),
5213                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5214                            Epoch::new(333),
5215                        )),
5216                        leader_timeout: Duration::from_secs(10),
5217                        certification_timeout: Duration::from_secs(10),
5218                        timeout_retry: Duration::from_secs(10),
5219                        fetch_timeout: Duration::from_secs(1),
5220                        activity_timeout,
5221                        skip_timeout,
5222                        fetch_concurrent: NZUsize!(4),
5223                        replay_buffer: NZUsize!(1024 * 1024),
5224                        write_buffer: NZUsize!(1024 * 1024),
5225                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5226                        forwarding: ForwardingPolicy::Disabled,
5227                    };
5228                    let engine = Engine::new(
5229                        context
5230                            .child("engine")
5231                            .with_attribute("public_key", validator),
5232                        cfg,
5233                    );
5234                    engine.start(pending, recovered, resolver);
5235                }
5236            }
5237
5238            // Allow started engines to consume preloaded certificates.
5239            context.sleep(Duration::from_secs(2)).await;
5240
5241            // ========== Assert the exact certificates are seen in each view ==========
5242
5243            // Assert the exact certificates in view F
5244            // All participants should have finalized B_0
5245            let view = View::new(f_view);
5246            for reporter in honest_reporters.iter() {
5247                let finalizations = reporter.finalizations.lock();
5248                assert!(finalizations.contains_key(&view));
5249            }
5250
5251            // Assert the exact certificates in view F+1
5252            // Group 1 should have notarized B_1A only
5253            // All other participants should have nullified F+1
5254            let view = View::new(f_view + 1);
5255            for (i, reporter) in honest_reporters.iter().enumerate() {
5256                let finalizations = reporter.finalizations.lock();
5257                assert!(!finalizations.contains_key(&view));
5258                let nullifications = reporter.nullifications.lock();
5259                let notarizations = reporter.notarizations.lock();
5260                match get_type(i) {
5261                    ParticipantType::Group1 => {
5262                        assert!(notarizations.contains_key(&view));
5263                        assert!(!nullifications.contains_key(&view));
5264                    }
5265                    _ => {
5266                        assert!(nullifications.contains_key(&view));
5267                        assert!(!notarizations.contains_key(&view));
5268                    }
5269                }
5270            }
5271
5272            // Assert the exact certificates in view F+2
5273            // Group 2 should have notarized B_1B only
5274            // All other participants should have nullified F+2
5275            let view = View::new(f_view + 2);
5276            for (i, reporter) in honest_reporters.iter().enumerate() {
5277                let finalizations = reporter.finalizations.lock();
5278                assert!(!finalizations.contains_key(&view));
5279                let nullifications = reporter.nullifications.lock();
5280                let notarizations = reporter.notarizations.lock();
5281                match get_type(i) {
5282                    ParticipantType::Group2 => {
5283                        assert!(notarizations.contains_key(&view));
5284                        assert!(!nullifications.contains_key(&view));
5285                    }
5286                    _ => {
5287                        assert!(nullifications.contains_key(&view));
5288                        assert!(!notarizations.contains_key(&view));
5289                    }
5290                }
5291            }
5292
5293            // Assert no members have yet nullified view F+3
5294            let next_view = View::new(f_view + 3);
5295            for (i, reporter) in honest_reporters.iter().enumerate() {
5296                let nullifies = reporter.nullifies.lock();
5297                assert!(!nullifies.contains_key(&next_view), "reporter {i}");
5298            }
5299
5300            // ========== Reconnect all participants ==========
5301
5302            // Reconnect all participants fully using the helper
5303            link_validators(&mut oracle, &participants, Action::Link(link.clone()), None).await;
5304
5305            // Wait until all honest reporters finalize strictly past F+2 (e.g., at least F+3)
5306            {
5307                let target = View::new(f_view + 3);
5308                let mut finalizers = Vec::new();
5309                for reporter in honest_reporters.iter_mut() {
5310                    let (mut latest, mut monitor) = reporter.subscribe().await;
5311                    finalizers.push(
5312                        context
5313                            .child("resume_finalizer")
5314                            .spawn(move |_| async move {
5315                                while latest < target {
5316                                    latest = monitor.recv().await.expect("event missing");
5317                                }
5318                            }),
5319                    );
5320                }
5321                join_all(finalizers).await;
5322            }
5323
5324            // Sanity checks: no faults/invalid signatures, and no peers blocked
5325            for reporter in honest_reporters.iter() {
5326                reporter.assert_no_faults();
5327                reporter.assert_no_invalid();
5328            }
5329            let blocked = oracle.blocked().await.unwrap();
5330            assert!(blocked.is_empty(), "blocked peers: {blocked:?}");
5331        });
5332    }
5333
5334    test_for_all_fixtures!(split_views_no_lockup);
5335
5336    fn tle<V, L>()
5337    where
5338        V: Variant,
5339        L: Elector<bls12381_threshold_vrf::Scheme<PublicKey, V>>,
5340    {
5341        // Create context
5342        let n = 4;
5343        let namespace = b"consensus".to_vec();
5344        let activity_timeout = ViewDelta::new(100);
5345        let skip_timeout = ViewDelta::new(50);
5346        let executor = deterministic::Runner::timed(Duration::from_secs(30));
5347        executor.start(|mut context| async move {
5348            // Register participants
5349            let Fixture {
5350                participants,
5351                schemes,
5352                ..
5353            } = bls12381_threshold_vrf::fixture::<V, _>(&mut context, &namespace, n);
5354            let mut oracle =
5355                start_test_network_with_peers(context.child("network"), participants.clone(), true)
5356                    .await;
5357            let mut registrations = register_validators(&mut oracle, &participants).await;
5358
5359            // Link all validators
5360            let link = Link {
5361                latency: Duration::from_millis(10),
5362                jitter: Duration::from_millis(5),
5363                success_rate: 1.0,
5364            };
5365            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5366
5367            // Create engines and reporters
5368            let elector = L::default();
5369            let relay = Arc::new(mocks::relay::Relay::new());
5370            let mut reporters = Vec::new();
5371            let mut engine_handlers = Vec::new();
5372            let monitor_reporter = Arc::new(Mutex::new(None));
5373            for (idx, validator) in participants.iter().enumerate() {
5374                // Create scheme context
5375                let context = context
5376                    .child("validator")
5377                    .with_attribute("public_key", validator);
5378
5379                // Store first reporter for monitoring
5380                let reporter_config = mocks::reporter::Config {
5381                    participants: participants.clone().try_into().unwrap(),
5382                    scheme: schemes[idx].clone(),
5383                    elector: elector.clone(),
5384                };
5385                let reporter =
5386                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
5387                reporters.push(reporter.clone());
5388                if idx == 0 {
5389                    *monitor_reporter.lock() = Some(reporter.clone());
5390                }
5391
5392                // Configure application
5393                let application_cfg = mocks::application::Config {
5394                    hasher: Sha256::default(),
5395                    relay: relay.clone(),
5396                    me: validator.clone(),
5397                    propose_latency: (10.0, 5.0),
5398                    verify_latency: (10.0, 5.0),
5399                    certify_latency: (10.0, 5.0),
5400                    should_certify: mocks::application::Certifier::Always,
5401                };
5402                let (actor, application) = mocks::application::Application::new(
5403                    context.child("application"),
5404                    application_cfg,
5405                );
5406                actor.start();
5407                let blocker = oracle.control(validator.clone());
5408                let cfg = config::Config {
5409                    scheme: schemes[idx].clone(),
5410                    elector: elector.clone(),
5411                    blocker,
5412                    automaton: application.clone(),
5413                    relay: application.clone(),
5414                    reporter: reporter.clone(),
5415                    strategy: Sequential,
5416                    partition: validator.to_string(),
5417                    mailbox_size: NZUsize!(1024),
5418                    epoch: Epoch::new(333),
5419                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5420                        Epoch::new(333),
5421                    )),
5422                    leader_timeout: Duration::from_millis(100),
5423                    certification_timeout: Duration::from_millis(200),
5424                    timeout_retry: Duration::from_millis(500),
5425                    fetch_timeout: Duration::from_millis(100),
5426                    activity_timeout,
5427                    skip_timeout,
5428                    fetch_concurrent: NZUsize!(4),
5429                    replay_buffer: NZUsize!(1024 * 1024),
5430                    write_buffer: NZUsize!(1024 * 1024),
5431                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5432                    forwarding: ForwardingPolicy::Disabled,
5433                };
5434                let engine = Engine::new(context.child("engine"), cfg);
5435
5436                // Start engine
5437                let (pending, recovered, resolver) = registrations
5438                    .remove(validator)
5439                    .expect("validator should be registered");
5440                engine_handlers.push(engine.start(pending, recovered, resolver));
5441            }
5442
5443            // Prepare TLE test data
5444            let target = Round::new(Epoch::new(333), View::new(10)); // Encrypt for round (epoch 333, view 10)
5445            let message = b"Secret message for future view10"; // 32 bytes
5446
5447            // Encrypt message
5448            let ciphertext = schemes[0].encrypt(&mut context, target, *message);
5449
5450            // Wait for consensus to reach the target view and then decrypt
5451            let reporter = monitor_reporter.lock().clone().unwrap();
5452            loop {
5453                // Wait for notarization
5454                context.sleep(Duration::from_millis(100)).await;
5455                let notarizations = reporter.notarizations.lock();
5456                let Some(notarization) = notarizations.get(&target.view()) else {
5457                    continue;
5458                };
5459
5460                // Decrypt the message using the seed
5461                let seed = notarization.seed();
5462                let decrypted = seed
5463                    .decrypt(&ciphertext)
5464                    .expect("Decryption should succeed with valid seed signature");
5465                assert_eq!(
5466                    message,
5467                    decrypted.as_ref(),
5468                    "Decrypted message should match original message"
5469                );
5470                break;
5471            }
5472        });
5473    }
5474
5475    #[test_traced]
5476    fn test_tle() {
5477        tle::<MinPk, Random>();
5478        tle::<MinSig, Random>();
5479    }
5480
5481    fn run_hailstorm<S, F, L>(
5482        seed: u64,
5483        shutdowns: usize,
5484        interval: ViewDelta,
5485        mut fixture: F,
5486    ) -> String
5487    where
5488        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5489        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5490        L: Elector<S>,
5491    {
5492        // Create context
5493        let n = 5;
5494        let activity_timeout = ViewDelta::new(10);
5495        let skip_timeout = ViewDelta::new(5);
5496        let namespace = b"consensus".to_vec();
5497        let cfg = deterministic::Config::new().with_seed(seed);
5498        let executor = deterministic::Runner::new(cfg);
5499        executor.start(|mut context| async move {
5500            // Register participants
5501            let Fixture {
5502                participants,
5503                schemes,
5504                ..
5505            } = fixture(&mut context, &namespace, n);
5506            let mut oracle =
5507                start_test_network_with_peers(context.child("network"), participants.clone(), true)
5508                    .await;
5509            let mut registrations = register_validators(&mut oracle, &participants).await;
5510
5511            // Link all validators
5512            let link = Link {
5513                latency: Duration::from_millis(10),
5514                jitter: Duration::from_millis(1),
5515                success_rate: 1.0,
5516            };
5517            link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5518
5519            // Create engines
5520            let elector = L::default();
5521            let relay = Arc::new(mocks::relay::Relay::new());
5522            let mut reporters = BTreeMap::new();
5523            let mut engine_handlers = BTreeMap::new();
5524            for (idx, validator) in participants.iter().enumerate() {
5525                // Create scheme context
5526                let context = context
5527                    .child("validator")
5528                    .with_attribute("public_key", validator);
5529
5530                // Configure engine
5531                let reporter_config = mocks::reporter::Config {
5532                    participants: participants.clone().try_into().unwrap(),
5533                    scheme: schemes[idx].clone(),
5534                    elector: elector.clone(),
5535                };
5536                let reporter =
5537                    mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
5538                reporters.insert(idx, reporter.clone());
5539                let application_cfg = mocks::application::Config {
5540                    hasher: Sha256::default(),
5541                    relay: relay.clone(),
5542                    me: validator.clone(),
5543                    propose_latency: (10.0, 5.0),
5544                    verify_latency: (10.0, 5.0),
5545                    certify_latency: (10.0, 5.0),
5546                    should_certify: mocks::application::Certifier::Always,
5547                };
5548                let (actor, application) = mocks::application::Application::new(
5549                    context.child("application"),
5550                    application_cfg,
5551                );
5552                actor.start();
5553                let blocker = oracle.control(validator.clone());
5554                let cfg = config::Config {
5555                    scheme: schemes[idx].clone(),
5556                    elector: elector.clone(),
5557                    blocker,
5558                    automaton: application.clone(),
5559                    relay: application.clone(),
5560                    reporter: reporter.clone(),
5561                    strategy: Sequential,
5562                    partition: validator.to_string(),
5563                    mailbox_size: NZUsize!(1024),
5564                    epoch: Epoch::new(333),
5565                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5566                        Epoch::new(333),
5567                    )),
5568                    leader_timeout: Duration::from_secs(1),
5569                    certification_timeout: Duration::from_secs(2),
5570                    timeout_retry: Duration::from_secs(10),
5571                    fetch_timeout: Duration::from_secs(1),
5572                    activity_timeout,
5573                    skip_timeout,
5574                    fetch_concurrent: NZUsize!(4),
5575                    replay_buffer: NZUsize!(1024 * 1024),
5576                    write_buffer: NZUsize!(1024 * 1024),
5577                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5578                    forwarding: ForwardingPolicy::Disabled,
5579                };
5580                let engine = Engine::new(context.child("engine"), cfg);
5581
5582                // Start engine
5583                let (pending, recovered, resolver) = registrations
5584                    .remove(validator)
5585                    .expect("validator should be registered");
5586                engine_handlers.insert(idx, engine.start(pending, recovered, resolver));
5587            }
5588
5589            // Run shutdowns
5590            let mut target = View::zero();
5591            for i in 0..shutdowns {
5592                // Update target
5593                target = target.saturating_add(interval);
5594
5595                // Wait for all engines to finish
5596                let mut finalizers = Vec::new();
5597                for reporter in reporters.values_mut() {
5598                    let (mut latest, mut monitor) = reporter.subscribe().await;
5599                    finalizers.push(context.child("finalizer").spawn(move |_| async move {
5600                        while latest < target {
5601                            latest = monitor.recv().await.expect("event missing");
5602                        }
5603                    }));
5604                }
5605                join_all(finalizers).await;
5606                target = target.saturating_add(interval);
5607
5608                // Select a random engine to shutdown
5609                let idx = context.random_range(0..engine_handlers.len());
5610                let validator = &participants[idx];
5611                let handle = engine_handlers.remove(&idx).unwrap();
5612                handle.abort();
5613                let _ = handle.await;
5614                let selected_reporter = reporters.remove(&idx).unwrap();
5615                info!(idx, ?validator, "shutdown validator");
5616
5617                // Wait for all engines to finish
5618                let mut finalizers = Vec::new();
5619                for reporter in reporters.values_mut() {
5620                    let (mut latest, mut monitor) = reporter.subscribe().await;
5621                    finalizers.push(context.child("finalizer").spawn(move |_| async move {
5622                        while latest < target {
5623                            latest = monitor.recv().await.expect("event missing");
5624                        }
5625                    }));
5626                }
5627                join_all(finalizers).await;
5628                target = target.saturating_add(interval);
5629
5630                // Recreate engine
5631                info!(idx, ?validator, "restarting validator");
5632                let context = context
5633                    .child("validator_restarted")
5634                    .with_attribute("public_key", validator)
5635                    .with_attribute("restart", i);
5636
5637                // Start engine
5638                let (pending, recovered, resolver) =
5639                    register_validator(&mut oracle, validator.clone()).await;
5640                let application_cfg = mocks::application::Config {
5641                    hasher: Sha256::default(),
5642                    relay: relay.clone(),
5643                    me: validator.clone(),
5644                    propose_latency: (10.0, 5.0),
5645                    verify_latency: (10.0, 5.0),
5646                    certify_latency: (10.0, 5.0),
5647                    should_certify: mocks::application::Certifier::Always,
5648                };
5649                let (actor, application) = mocks::application::Application::new(
5650                    context.child("application"),
5651                    application_cfg,
5652                );
5653                actor.start();
5654                reporters.insert(idx, selected_reporter.clone());
5655                let blocker = oracle.control(validator.clone());
5656                let cfg = config::Config {
5657                    scheme: schemes[idx].clone(),
5658                    elector: elector.clone(),
5659                    blocker,
5660                    automaton: application.clone(),
5661                    relay: application.clone(),
5662                    reporter: selected_reporter,
5663                    strategy: Sequential,
5664                    partition: validator.to_string(),
5665                    mailbox_size: NZUsize!(1024),
5666                    epoch: Epoch::new(333),
5667                    floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5668                        Epoch::new(333),
5669                    )),
5670                    leader_timeout: Duration::from_secs(1),
5671                    certification_timeout: Duration::from_secs(2),
5672                    timeout_retry: Duration::from_secs(10),
5673                    fetch_timeout: Duration::from_secs(1),
5674                    activity_timeout,
5675                    skip_timeout,
5676                    fetch_concurrent: NZUsize!(4),
5677                    replay_buffer: NZUsize!(1024 * 1024),
5678                    write_buffer: NZUsize!(1024 * 1024),
5679                    page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5680                    forwarding: ForwardingPolicy::Disabled,
5681                };
5682                let engine = Engine::new(context.child("engine"), cfg);
5683                engine_handlers.insert(idx, engine.start(pending, recovered, resolver));
5684
5685                // Wait for all engines to hit required containers
5686                let mut finalizers = Vec::new();
5687                for reporter in reporters.values_mut() {
5688                    let (mut latest, mut monitor) = reporter.subscribe().await;
5689                    finalizers.push(context.child("finalizer").spawn(move |_| async move {
5690                        while latest < target {
5691                            latest = monitor.recv().await.expect("event missing");
5692                        }
5693                    }));
5694                }
5695                join_all(finalizers).await;
5696                info!(idx, ?validator, "validator recovered");
5697            }
5698
5699            // Check reporters for correct activity
5700            let latest_complete = target.saturating_sub(activity_timeout);
5701            for reporter in reporters.values() {
5702                // Ensure no faults
5703                reporter.assert_no_faults();
5704
5705                // Ensure no invalid signatures
5706                reporter.assert_no_invalid();
5707
5708                // Ensure no forks
5709                let mut notarized = HashMap::new();
5710                let mut finalized = HashMap::new();
5711                {
5712                    let notarizes = reporter.notarizes.lock();
5713                    for view in View::range(View::new(1), latest_complete) {
5714                        // Ensure only one payload proposed per view
5715                        let Some(payloads) = notarizes.get(&view) else {
5716                            continue;
5717                        };
5718                        if payloads.len() > 1 {
5719                            panic!("view: {view}");
5720                        }
5721                        let (digest, _) = payloads.iter().next().unwrap();
5722                        notarized.insert(view, *digest);
5723                    }
5724                }
5725                {
5726                    let notarizations = reporter.notarizations.lock();
5727                    for view in View::range(View::new(1), latest_complete) {
5728                        // Ensure notarization matches digest from notarizes
5729                        let Some(notarization) = notarizations.get(&view) else {
5730                            continue;
5731                        };
5732                        let Some(digest) = notarized.get(&view) else {
5733                            continue;
5734                        };
5735                        assert_eq!(&notarization.proposal.payload, digest);
5736                    }
5737                }
5738                {
5739                    let finalizes = reporter.finalizes.lock();
5740                    for view in View::range(View::new(1), latest_complete) {
5741                        // Ensure only one payload proposed per view
5742                        let Some(payloads) = finalizes.get(&view) else {
5743                            continue;
5744                        };
5745                        if payloads.len() > 1 {
5746                            panic!("view: {view}");
5747                        }
5748                        let (digest, _) = payloads.iter().next().unwrap();
5749                        finalized.insert(view, *digest);
5750
5751                        // Only check at views below timeout
5752                        if view > latest_complete {
5753                            continue;
5754                        }
5755
5756                        // Ensure no nullifies for any finalizers
5757                        let nullifies = reporter.nullifies.lock();
5758                        let Some(nullifies) = nullifies.get(&view) else {
5759                            continue;
5760                        };
5761                        for finalizers in payloads.values() {
5762                            for finalizer in finalizers.iter() {
5763                                if nullifies.contains(finalizer) {
5764                                    panic!("should not nullify and finalize at same view");
5765                                }
5766                            }
5767                        }
5768                    }
5769                }
5770                {
5771                    let finalizations = reporter.finalizations.lock();
5772                    for view in View::range(View::new(1), latest_complete) {
5773                        // Ensure finalization matches digest from finalizes
5774                        let Some(finalization) = finalizations.get(&view) else {
5775                            continue;
5776                        };
5777                        let Some(digest) = finalized.get(&view) else {
5778                            continue;
5779                        };
5780                        assert_eq!(&finalization.proposal.payload, digest);
5781                    }
5782                }
5783            }
5784
5785            // Ensure no blocked connections
5786            let blocked = oracle.blocked().await.unwrap();
5787            assert!(blocked.is_empty());
5788
5789            // Return state for audit
5790            context.auditor().state()
5791        })
5792    }
5793
5794    // The hailstorm run must be deterministic: two runs with identical inputs
5795    // must produce identical audit state.
5796    fn hailstorm<S, F, L>(fixture: F)
5797    where
5798        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5799        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S> + Copy,
5800        L: Elector<S>,
5801    {
5802        assert_eq!(
5803            run_hailstorm::<_, _, L>(0, 10, ViewDelta::new(15), fixture),
5804            run_hailstorm::<_, _, L>(0, 10, ViewDelta::new(15), fixture),
5805        );
5806    }
5807
5808    test_for_all_fixtures!(hailstorm);
5809
5810    /// Configuration for a Twins testing campaign.
5811    ///
5812    /// A campaign generates adversarial primary/secondary recipient-set
5813    /// scenarios, splits Byzantine participants into twin halves, and verifies
5814    /// that honest nodes still finalize blocks after the adversarial prefix
5815    /// ends.
5816    ///
5817    /// # Fields
5818    ///
5819    /// - `n`: Total participants. The number of faults is derived as
5820    ///   `N3f1::max_faults(n)`. Larger `n` increases the per-scenario
5821    ///   compromised-set space but also makes each case slower to execute.
5822    ///
5823    /// - `rounds`: Number of adversarial rounds that form the attack prefix.
5824    ///   Each round independently places participants relative to the primary
5825    ///   and secondary recipient sets: outside both, both-halves,
5826    ///   primary-only, or secondary-only. The two recipient sets may overlap;
5827    ///   a participant in `both-halves` is visible to both twins in that view.
5828    ///   After these rounds, the network becomes fully synchronous. More
5829    ///   rounds exponentially increase the canonical scenario space.
5830    ///
5831    /// - `mode`: How multi-round scenarios are constructed. `Sampled` picks
5832    ///   independent recipient sets per round; `Sustained` repeats a single
5833    ///   recipient-set pattern across all rounds (modeling a persistent
5834    ///   adversarial split).
5835    ///
5836    /// - `max_cases`: Upper bound on the total emitted cases. Each case is a
5837    ///   (scenario, compromised-assignment) pair. Also caps scenario
5838    ///   enumeration (sampling uniformly when the space is larger). Cases
5839    ///   are shuffled and truncated to this budget.
5840    ///
5841    /// - `trailing_finalizations`: Number of finalizations each honest node
5842    ///   must produce *after* the adversarial prefix before the case is
5843    ///   considered successful. This is the liveness assertion -- it ensures
5844    ///   the protocol actually commits blocks under synchrony, not just
5845    ///   reaches a high view via nullifications.
5846    #[derive(Clone, Copy, Debug)]
5847    struct TwinsCampaign {
5848        n: u32,
5849        rounds: usize,
5850        mode: twins::Mode,
5851        max_cases: usize,
5852        trailing_finalizations: usize,
5853    }
5854
5855    fn twins_campaign<S, F, L>(
5856        rng: &mut impl CryptoRng,
5857        campaign: TwinsCampaign,
5858        link: Link,
5859        mut fixture: F,
5860    ) where
5861        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5862        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5863        L: Elector<S>,
5864    {
5865        let n = campaign.n;
5866        let faults = N3f1::max_faults(n) as usize;
5867        let cases = twins::cases(
5868            rng,
5869            twins::Framework {
5870                participants: n as usize,
5871                faults,
5872                rounds: campaign.rounds,
5873                mode: campaign.mode,
5874                max_cases: campaign.max_cases,
5875            },
5876        );
5877        assert!(
5878            !cases.is_empty(),
5879            "twins campaign should generate at least one case"
5880        );
5881        for case in cases {
5882            let scenario = case.scenario.clone();
5883            let twin_indices = case.compromised.clone();
5884            assert_eq!(
5885                twin_indices.len(),
5886                faults,
5887                "unexpected twins count for n={n} (expected f={faults})",
5888            );
5889
5890            let activity_timeout = ViewDelta::new(10);
5891            let skip_timeout = ViewDelta::new(5);
5892            let namespace = b"consensus".to_vec();
5893            let link = link.clone();
5894            let trailing_finalizations = campaign.trailing_finalizations;
5895            let mut case_fixture =
5896                |ctx: &mut deterministic::Context, ns: &[u8], n: u32| fixture(ctx, ns, n);
5897            let cfg = deterministic::Config::new().with_rng(Box::new(StdRng::from_rng(&mut *rng)));
5898            let executor = deterministic::Runner::new(cfg);
5899            executor.start(|mut context| async move {
5900                let Fixture {
5901                    participants,
5902                    schemes,
5903                    ..
5904                } = case_fixture(&mut context, &namespace, n);
5905                let participants: Arc<[_]> = participants.into();
5906                let mut oracle = start_test_network_with_peers(
5907                    context.child("network"),
5908                    participants.iter().cloned(),
5909                    false,
5910                )
5911                .await;
5912                let mut registrations = register_validators(&mut oracle, &participants).await;
5913                link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5914
5915                let elector = TwinsElector::new(L::default(), &scenario, n as usize);
5916                let relay = Arc::new(mocks::relay::Relay::new());
5917                let mut reporters = Vec::new();
5918                let mut engine_handlers = Vec::new();
5919                let twin_index_set: HashSet<usize> = twin_indices.iter().copied().collect();
5920
5921                // Create twin engines (f Byzantine twins).
5922                for idx in twin_indices.iter().copied() {
5923                    let validator = &participants[idx];
5924                    let (
5925                        (vote_sender, vote_receiver),
5926                        (certificate_sender, certificate_receiver),
5927                        (_resolver_sender, _resolver_receiver),
5928                    ) = registrations
5929                        .remove(validator)
5930                        .expect("validator should be registered");
5931
5932                    let make_vote_forwarder = || {
5933                        let participants = participants.clone();
5934                        let scenario = scenario.clone();
5935                        move |origin: SplitOrigin, _: &Recipients<_>, message: &IoBuf| {
5936                            let msg: Vote<S, D> = Vote::decode(message.clone()).unwrap();
5937                            let (primary, secondary) =
5938                                scenario.partitions(msg.view(), participants.as_ref());
5939                            match origin {
5940                                SplitOrigin::Primary => Some(Recipients::Some(primary)),
5941                                SplitOrigin::Secondary => Some(Recipients::Some(secondary)),
5942                            }
5943                        }
5944                    };
5945                    let make_certificate_forwarder = || {
5946                        let codec = schemes[idx].certificate_codec_config();
5947                        let participants = participants.clone();
5948                        let scenario = scenario.clone();
5949                        move |origin: SplitOrigin, _: &Recipients<_>, message: &IoBuf| {
5950                            let msg: Certificate<S, D> =
5951                                Certificate::decode_cfg(&mut message.as_ref(), &codec).unwrap();
5952                            let (primary, secondary) =
5953                                scenario.partitions(msg.view(), participants.as_ref());
5954                            match origin {
5955                                SplitOrigin::Primary => Some(Recipients::Some(primary)),
5956                                SplitOrigin::Secondary => Some(Recipients::Some(secondary)),
5957                            }
5958                        }
5959                    };
5960                    let make_vote_router = || {
5961                        let participants = participants.clone();
5962                        let scenario = scenario.clone();
5963                        move |(sender, message): &(_, IoBuf)| {
5964                            let msg: Vote<S, D> = Vote::decode(message.clone()).unwrap();
5965                            scenario.route(msg.view(), sender, participants.as_ref())
5966                        }
5967                    };
5968                    let make_certificate_router = || {
5969                        let codec = schemes[idx].certificate_codec_config();
5970                        let participants = participants.clone();
5971                        let scenario = scenario.clone();
5972                        move |(sender, message): &(_, IoBuf)| {
5973                            let msg: Certificate<S, D> =
5974                                Certificate::decode_cfg(&mut message.as_ref(), &codec).unwrap();
5975                            scenario.route(msg.view(), sender, participants.as_ref())
5976                        }
5977                    };
5978                    let (vote_sender_primary, vote_sender_secondary) =
5979                        vote_sender.split_with(make_vote_forwarder());
5980                    let (vote_receiver_primary, vote_receiver_secondary) = vote_receiver
5981                        .split_with(
5982                            context.child("pending_split").with_attribute("index", idx),
5983                            make_vote_router(),
5984                        );
5985                    let (certificate_sender_primary, certificate_sender_secondary) =
5986                        certificate_sender.split_with(make_certificate_forwarder());
5987                    let (certificate_receiver_primary, certificate_receiver_secondary) =
5988                        certificate_receiver.split_with(
5989                            context
5990                                .child("recovered_split")
5991                                .with_attribute("index", idx),
5992                            make_certificate_router(),
5993                        );
5994
5995                    for (twin_label, pending, recovered) in [
5996                        (
5997                            "primary",
5998                            (vote_sender_primary, vote_receiver_primary),
5999                            (certificate_sender_primary, certificate_receiver_primary),
6000                        ),
6001                        (
6002                            "secondary",
6003                            (vote_sender_secondary, vote_receiver_secondary),
6004                            (certificate_sender_secondary, certificate_receiver_secondary),
6005                        ),
6006                    ] {
6007                        let partition = format!("twin_{idx}_{twin_label}");
6008                        let context = context
6009                            .child("twin")
6010                            .with_attribute("index", idx)
6011                            .with_attribute("side", twin_label);
6012
6013                        let reporter_config = mocks::reporter::Config {
6014                            participants: participants.as_ref().try_into().unwrap(),
6015                            scheme: schemes[idx].clone(),
6016                            elector: elector.clone(),
6017                        };
6018                        let reporter = mocks::reporter::Reporter::new(
6019                            context.child("reporter"),
6020                            reporter_config,
6021                        );
6022                        reporters.push(reporter.clone());
6023
6024                        let application_cfg = mocks::application::Config {
6025                            hasher: Sha256::default(),
6026                            relay: relay.clone(),
6027                            me: validator.clone(),
6028                            propose_latency: (10.0, 5.0),
6029                            verify_latency: (10.0, 5.0),
6030                            certify_latency: (10.0, 5.0),
6031                            should_certify: mocks::application::Certifier::Always,
6032                        };
6033                        let (actor, application) = mocks::application::Application::new(
6034                            context.child("application"),
6035                            application_cfg,
6036                        );
6037                        actor.start();
6038
6039                        let blocker = oracle.control(validator.clone());
6040                        let cfg = config::Config {
6041                            scheme: schemes[idx].clone(),
6042                            elector: elector.clone(),
6043                            blocker,
6044                            automaton: application.clone(),
6045                            relay: application.clone(),
6046                            reporter: reporter.clone(),
6047                            strategy: Sequential,
6048                            partition,
6049                            mailbox_size: NZUsize!(1024),
6050                            epoch: Epoch::new(333),
6051                            floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
6052                                Epoch::new(333),
6053                            )),
6054                            leader_timeout: Duration::from_secs(1),
6055                            certification_timeout: Duration::from_millis(1_500),
6056                            timeout_retry: Duration::from_secs(10),
6057                            fetch_timeout: Duration::from_secs(1),
6058                            activity_timeout,
6059                            skip_timeout,
6060                            fetch_concurrent: NZUsize!(4),
6061                            replay_buffer: NZUsize!(1024 * 1024),
6062                            write_buffer: NZUsize!(1024 * 1024),
6063                            page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
6064                            forwarding: ForwardingPolicy::Disabled,
6065                        };
6066                        let engine = Engine::new(context.child("engine"), cfg);
6067                        engine_handlers.push(engine.start(
6068                            pending,
6069                            recovered,
6070                            inert_channel(participants.as_ref()),
6071                        ));
6072                    }
6073                }
6074
6075                // Create honest engines.
6076                let honest_start = reporters.len();
6077                for (idx, validator) in participants.iter().enumerate() {
6078                    if twin_index_set.contains(&idx) {
6079                        continue;
6080                    }
6081
6082                    let partition = format!("honest_{idx}");
6083                    let context = context.child("honest").with_attribute("index", idx);
6084
6085                    let reporter_config = mocks::reporter::Config {
6086                        participants: participants.as_ref().try_into().unwrap(),
6087                        scheme: schemes[idx].clone(),
6088                        elector: elector.clone(),
6089                    };
6090                    let reporter =
6091                        mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
6092                    reporters.push(reporter.clone());
6093
6094                    let application_cfg = mocks::application::Config {
6095                        hasher: Sha256::default(),
6096                        relay: relay.clone(),
6097                        me: validator.clone(),
6098                        propose_latency: (10.0, 5.0),
6099                        verify_latency: (10.0, 5.0),
6100                        certify_latency: (10.0, 5.0),
6101                        should_certify: mocks::application::Certifier::Always,
6102                    };
6103                    let (actor, application) = mocks::application::Application::new(
6104                        context.child("application"),
6105                        application_cfg,
6106                    );
6107                    actor.start();
6108
6109                    let blocker = oracle.control(validator.clone());
6110                    let cfg = config::Config {
6111                        scheme: schemes[idx].clone(),
6112                        elector: elector.clone(),
6113                        blocker,
6114                        automaton: application.clone(),
6115                        relay: application.clone(),
6116                        reporter: reporter.clone(),
6117                        strategy: Sequential,
6118                        partition,
6119                        mailbox_size: NZUsize!(1024),
6120                        epoch: Epoch::new(333),
6121                        floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
6122                            Epoch::new(333),
6123                        )),
6124                        leader_timeout: Duration::from_secs(1),
6125                        certification_timeout: Duration::from_millis(1_500),
6126                        timeout_retry: Duration::from_secs(10),
6127                        fetch_timeout: Duration::from_secs(1),
6128                        activity_timeout,
6129                        skip_timeout,
6130                        fetch_concurrent: NZUsize!(4),
6131                        replay_buffer: NZUsize!(1024 * 1024),
6132                        write_buffer: NZUsize!(1024 * 1024),
6133                        page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
6134                        forwarding: ForwardingPolicy::Disabled,
6135                    };
6136                    let engine = Engine::new(context.child("engine"), cfg);
6137
6138                    let (
6139                        (pending_sender, pending_receiver),
6140                        (recovered_sender, recovered_receiver),
6141                        _,
6142                    ) = registrations
6143                        .remove(validator)
6144                        .expect("validator should be registered");
6145                    engine_handlers.push(engine.start(
6146                        (pending_sender, pending_receiver),
6147                        (recovered_sender, recovered_receiver),
6148                        inert_channel(participants.as_ref()),
6149                    ));
6150                }
6151
6152                // Wait for progress (liveness check) across honest replicas only.
6153                //
6154                // Only count finalizations after the adversarial prefix so we
6155                // verify the protocol actually recovers and makes progress under
6156                // synchrony. Finalizations during the prefix may be artifacts of
6157                // the attack setup and do not demonstrate liveness.
6158                //
6159                // Twin halves are Byzantine test machinery and are not required to
6160                // make progress for the campaign to establish honest-node liveness.
6161                let prefix_end = View::new(scenario.rounds().len() as u64);
6162                let mut finalizers = Vec::new();
6163                for (i, reporter) in reporters.iter_mut().skip(honest_start).enumerate() {
6164                    let (_latest, mut monitor) = reporter.subscribe().await;
6165                    let required = trailing_finalizations;
6166                    finalizers.push(context.child("finalizer").with_attribute("index", i).spawn(
6167                        move |_| async move {
6168                            let mut count = 0usize;
6169                            while count < required {
6170                                let view = monitor.recv().await.expect("event missing");
6171                                if view > prefix_end {
6172                                    count += 1;
6173                                }
6174                            }
6175                        },
6176                    ));
6177                }
6178                join_all(finalizers).await;
6179
6180                // Verify safety: no conflicting finalizations across honest reporters.
6181                let mut finalized_at_view: BTreeMap<View, D> = BTreeMap::new();
6182                for reporter in reporters.iter().skip(honest_start) {
6183                    let finalizations = reporter.finalizations.lock();
6184                    for (view, finalization) in finalizations.iter() {
6185                        let digest = finalization.proposal.payload;
6186                        if let Some(existing) = finalized_at_view.get(view) {
6187                            assert_eq!(
6188                                existing, &digest,
6189                                "safety violation: conflicting finalizations at view {view}"
6190                            );
6191                        } else {
6192                            finalized_at_view.insert(*view, digest);
6193                        }
6194                    }
6195                }
6196
6197                // Verify no invalid signatures were observed by honest replicas.
6198                for reporter in reporters.iter().skip(honest_start) {
6199                    reporter.assert_no_invalid();
6200                }
6201
6202                // Ensure no honest signer appears under multiple payloads for the same view.
6203                let twin_identities: HashSet<_> = twin_indices
6204                    .iter()
6205                    .map(|idx| participants[*idx].clone())
6206                    .collect();
6207                let mut notarized_by_honest_signer: BTreeMap<View, HashMap<PublicKey, D>> =
6208                    BTreeMap::new();
6209                let mut finalized_by_honest_signer: BTreeMap<View, HashMap<PublicKey, D>> =
6210                    BTreeMap::new();
6211                for reporter in reporters.iter().skip(honest_start) {
6212                    let notarizes = reporter.notarizes.lock();
6213                    for (view, payloads) in notarizes.iter() {
6214                        let signers = notarized_by_honest_signer.entry(*view).or_default();
6215                        for (digest, payload_signers) in payloads.iter() {
6216                            for signer in payload_signers.iter() {
6217                                if twin_identities.contains(signer) {
6218                                    continue;
6219                                }
6220                                if let Some(existing) = signers.insert(signer.clone(), *digest) {
6221                                    assert_eq!(
6222                                    existing, *digest,
6223                                    "honest signer produced conflicting notarizes at view {view}"
6224                                );
6225                                }
6226                            }
6227                        }
6228                    }
6229
6230                    let finalizes = reporter.finalizes.lock();
6231                    for (view, payloads) in finalizes.iter() {
6232                        let signers = finalized_by_honest_signer.entry(*view).or_default();
6233                        for (digest, payload_signers) in payloads.iter() {
6234                            for signer in payload_signers.iter() {
6235                                if twin_identities.contains(signer) {
6236                                    continue;
6237                                }
6238                                if let Some(existing) = signers.insert(signer.clone(), *digest) {
6239                                    assert_eq!(
6240                                    existing, *digest,
6241                                    "honest signer produced conflicting finalizes at view {view}"
6242                                );
6243                                }
6244                            }
6245                        }
6246                    }
6247                }
6248
6249                // Ensure faults are attributable to twins.
6250                for reporter in reporters.iter().skip(honest_start) {
6251                    let faults = reporter.faults.lock();
6252                    for faulter in faults.keys() {
6253                        assert!(
6254                            twin_identities.contains(faulter),
6255                            "fault from non-twin participant"
6256                        );
6257                    }
6258                }
6259
6260                let blocked = oracle.blocked().await.unwrap();
6261                for (_, faulter) in blocked {
6262                    assert!(
6263                        twin_identities.contains(&faulter),
6264                        "blocked peer attributed to non-twin participant"
6265                    );
6266                }
6267            });
6268        }
6269    }
6270
6271    const TWINS_CAMPAIGN: TwinsCampaign = TwinsCampaign {
6272        n: 5,
6273        rounds: 3,
6274        mode: twins::Mode::Sampled,
6275        max_cases: 20,
6276        trailing_finalizations: 10,
6277    };
6278
6279    const TWINS_LINK: Link = Link {
6280        latency: Duration::from_millis(500),
6281        jitter: Duration::from_millis(500),
6282        success_rate: 1.0,
6283    };
6284
6285    #[test_group("slow")]
6286    #[test_traced("INFO")]
6287    fn test_twins_sampled() {
6288        for link in [
6289            Link {
6290                latency: Duration::from_millis(10),
6291                jitter: Duration::from_millis(10),
6292                success_rate: 1.0,
6293            },
6294            TWINS_LINK,
6295        ] {
6296            twins_campaign::<_, _, RoundRobin>(
6297                &mut test_rng(),
6298                TWINS_CAMPAIGN,
6299                link,
6300                scheme_mocks::fixture,
6301            );
6302        }
6303    }
6304
6305    #[test_group("slow")]
6306    #[test_traced("INFO")]
6307    fn test_twins_sustained() {
6308        let campaign = TwinsCampaign {
6309            mode: twins::Mode::Sustained,
6310            ..TWINS_CAMPAIGN
6311        };
6312        for link in [
6313            Link {
6314                latency: Duration::from_millis(10),
6315                jitter: Duration::from_millis(10),
6316                success_rate: 1.0,
6317            },
6318            TWINS_LINK,
6319        ] {
6320            twins_campaign::<_, _, RoundRobin>(
6321                &mut test_rng(),
6322                campaign,
6323                link,
6324                scheme_mocks::fixture,
6325            );
6326        }
6327    }
6328
6329    #[test_group("slow")]
6330    #[test_traced("INFO")]
6331    fn test_twins_large_sampled() {
6332        let campaign = TwinsCampaign {
6333            n: 10,
6334            rounds: 5,
6335            ..TWINS_CAMPAIGN
6336        };
6337        twins_campaign::<_, _, RoundRobin>(
6338            &mut test_rng(),
6339            campaign,
6340            TWINS_LINK,
6341            scheme_mocks::fixture,
6342        );
6343    }
6344
6345    #[test_group("slow")]
6346    #[test_traced("INFO")]
6347    fn test_twins_large_sustained() {
6348        let campaign = TwinsCampaign {
6349            n: 10,
6350            rounds: 5,
6351            mode: twins::Mode::Sustained,
6352            ..TWINS_CAMPAIGN
6353        };
6354        twins_campaign::<_, _, RoundRobin>(
6355            &mut test_rng(),
6356            campaign,
6357            TWINS_LINK,
6358            scheme_mocks::fixture,
6359        );
6360    }
6361
6362    fn twins<S, F, L>(fixture: F)
6363    where
6364        S: Scheme<Sha256Digest, PublicKey = PublicKey>,
6365        F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
6366        L: Elector<S>,
6367    {
6368        twins_campaign::<_, _, L>(&mut test_rng(), TWINS_CAMPAIGN, TWINS_LINK, fixture);
6369    }
6370
6371    test_for_all_fixtures!(twins, level = "INFO");
6372}