Skip to main content

commonware_consensus/ordered_broadcast/
mod.rs

1//! Ordered, reliable broadcast across reconfigurable participants.
2//!
3//! # Concepts
4//!
5//! The system has two types of network participants: `sequencers` and `validators`. Their sets may
6//! overlap and are defined by the current `epoch`, a monotonically increasing integer. This module
7//! can handle reconfiguration of these sets across different epochs.
8//!
9//! Sequencers broadcast data. The smallest unit of data is a `chunk`. Sequencers broadcast `node`s
10//! that contain a chunk and a certificate over the previous chunk, forming a linked chain
11//! of nodes from each sequencer.
12//!
13//! Validators verify and sign chunks. These signatures can be combined to form a quorum
14//! certificate, ensuring a quorum verifies each chunk. The certificate allows external parties
15//! to confirm that the chunk was reliably broadcast.
16//!
17//! Network participants persist any new nodes to a journal. This enables recovery from crashes and
18//! ensures that sequencers do not broadcast conflicting chunks and that validators do not sign
19//! them. "Conflicting" chunks are chunks from the same sequencer at the same height with different
20//! payloads.
21//!
22//! # Pluggable Cryptography
23//!
24//! The ordered broadcast module is generic over the signing scheme, allowing users to choose the
25//! cryptographic scheme best suited for their requirements:
26//!
27//! - [`ed25519`][scheme::ed25519]: Attributable signatures with individual verification.
28//!   HSM-friendly, no trusted setup required. Certificates contain individual signatures.
29//!
30//! - [`secp256r1`][scheme::secp256r1]: Attributable signatures with individual verification.
31//!   HSM-friendly, no trusted setup required. Certificates contain individual signatures.
32//!
33//! - [`bls12381_multisig`][scheme::bls12381_multisig]: Attributable signatures with aggregated
34//!   verification. Produces compact certificates while preserving signer attribution.
35//!
36//! - [`bls12381_threshold`][scheme::bls12381_threshold]: Non-attributable threshold signatures.
37//!   Produces succinct constant-size certificates. Requires trusted setup (DKG).
38//!
39//! # Design
40//!
41//! The core of the module is the [Engine]. It is responsible for:
42//! - Broadcasting nodes (if a sequencer)
43//! - Signing chunks (if a validator)
44//! - Tracking the latest chunk in each sequencer's chain
45//! - Assembling certificates from a quorum of signatures
46//! - Notifying other actors of new chunks and certificates
47//!
48//! # Acknowledgements
49//!
50//! [Autobahn](https://arxiv.org/abs/2401.10369) provided the insight that a succinct
51//! proof-of-availability could be produced by linking sequencer broadcasts.
52
53pub mod scheme;
54pub mod types;
55
56cfg_if::cfg_if! {
57    if #[cfg(not(target_arch = "wasm32"))] {
58        mod ack_manager;
59        use ack_manager::AckManager;
60        mod config;
61        pub use config::Config;
62        mod engine;
63        pub use engine::Engine;
64        mod metrics;
65        mod tip_manager;
66        use tip_manager::TipManager;
67    }
68}
69
70#[cfg(test)]
71pub mod mocks;
72
73#[cfg(test)]
74mod tests {
75    use super::{
76        mocks,
77        types::{ChunkSigner, ChunkVerifier},
78        Config, Engine,
79    };
80    use crate::{
81        ordered_broadcast::scheme::{
82            bls12381_multisig, bls12381_threshold, ed25519, secp256r1, Scheme,
83        },
84        types::{Epoch, EpochDelta, Height, HeightDelta},
85    };
86    use commonware_cryptography::{
87        bls12381::primitives::variant::{MinPk, MinSig},
88        certificate::{self, mocks::Fixture},
89        ed25519::{PrivateKey, PublicKey},
90        sha256::Digest as Sha256Digest,
91        Signer as _,
92    };
93    use commonware_macros::{select, test_group, test_traced};
94    use commonware_p2p::simulated::{Link, Network, Oracle, Receiver, Sender};
95    use commonware_parallel::Sequential;
96    use commonware_runtime::{
97        buffer::paged::CacheRef,
98        deterministic::{self, Context},
99        Clock, Quota, Runner, Spawner, Supervisor as _,
100    };
101    use commonware_utils::{
102        channel::{fallible::OneshotExt, oneshot},
103        NZUsize, NZU16, NZU64,
104    };
105    use futures::future::join_all;
106    use std::{
107        collections::{BTreeMap, HashMap},
108        num::{NonZeroU16, NonZeroU32, NonZeroUsize},
109        time::Duration,
110    };
111    use tracing::debug;
112
113    // Invoke `$cb!($($args)*, $suffix, $fixture)` once per canonical scheme fixture.
114    macro_rules! for_each_fixture {
115        ($cb:ident!($($args:tt)*)) => {
116            $cb!($($args)*, bls12381_threshold_min_pk, bls12381_threshold::fixture::<MinPk, _>);
117            $cb!($($args)*, bls12381_threshold_min_sig, bls12381_threshold::fixture::<MinSig, _>);
118            $cb!($($args)*, bls12381_multisig_min_pk, bls12381_multisig::fixture::<MinPk, _>);
119            $cb!($($args)*, bls12381_multisig_min_sig, bls12381_multisig::fixture::<MinSig, _>);
120            $cb!($($args)*, ed25519, ed25519::fixture);
121            $cb!($($args)*, secp256r1, secp256r1::fixture);
122        };
123    }
124
125    // Generate one `#[test_group("slow")] #[test_traced]` test per scheme
126    // fixture, named `test_<callee>_<suffix>`, calling `callee(fixture)`.
127    macro_rules! test_for_all_fixtures {
128        ($callee:ident) => {
129            for_each_fixture!(test_for_all_fixtures!(@emit $callee));
130        };
131        (@emit $callee:ident, $suffix:ident, $fixture:expr) => {
132            paste::paste! {
133                #[test_group("slow")]
134                #[test_traced]
135                fn [<test_ $callee _ $suffix>]() {
136                    $callee($fixture);
137                }
138            }
139        };
140    }
141
142    const PAGE_SIZE: NonZeroU16 = NZU16!(1024);
143    const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(10);
144    const TEST_QUOTA: Quota = Quota::per_second(NonZeroU32::MAX);
145    const TEST_NAMESPACE: &[u8] = b"ordered_broadcast_test";
146
147    type Registrations<P> = BTreeMap<
148        P,
149        (
150            (Sender<P, deterministic::Context>, Receiver<P>),
151            (Sender<P, deterministic::Context>, Receiver<P>),
152        ),
153    >;
154
155    async fn register_participants(
156        oracle: &mut Oracle<PublicKey, deterministic::Context>,
157        participants: &[PublicKey],
158    ) -> Registrations<PublicKey> {
159        let mut registrations = BTreeMap::new();
160        for participant in participants.iter() {
161            let control = oracle.control(participant.clone());
162            let (a1, a2) = control.register(0, TEST_QUOTA).await.unwrap();
163            let (b1, b2) = control.register(1, TEST_QUOTA).await.unwrap();
164            registrations.insert(participant.clone(), ((a1, a2), (b1, b2)));
165        }
166        registrations
167    }
168
169    enum Action {
170        Link(Link),
171        Update(Link),
172        Unlink,
173    }
174
175    async fn link_participants(
176        oracle: &mut Oracle<PublicKey, deterministic::Context>,
177        participants: &[PublicKey],
178        action: Action,
179        restrict_to: Option<fn(usize, usize, usize) -> bool>,
180    ) {
181        for (i1, v1) in participants.iter().enumerate() {
182            for (i2, v2) in participants.iter().enumerate() {
183                if v2 == v1 {
184                    continue;
185                }
186                if let Some(f) = restrict_to {
187                    if !f(participants.len(), i1, i2) {
188                        continue;
189                    }
190                }
191                if matches!(action, Action::Update(_) | Action::Unlink) {
192                    oracle.remove_link(v1.clone(), v2.clone()).await.unwrap();
193                }
194                if let Action::Link(ref link) | Action::Update(ref link) = action {
195                    oracle
196                        .add_link(v1.clone(), v2.clone(), link.clone())
197                        .await
198                        .unwrap();
199                }
200            }
201        }
202    }
203
204    const RELIABLE_LINK: Link = Link {
205        latency: Duration::from_millis(10),
206        jitter: Duration::from_millis(1),
207        success_rate: 1.0,
208    };
209
210    async fn initialize_simulation<S: certificate::Scheme>(
211        context: Context,
212        fixture: &Fixture<S>,
213        link: Link,
214    ) -> (
215        Oracle<PublicKey, deterministic::Context>,
216        Registrations<PublicKey>,
217    ) {
218        let (network, mut oracle) = Network::new_with_peers(
219            context.child("network"),
220            commonware_p2p::simulated::Config {
221                max_size: 1024 * 1024,
222                disconnect_on_block: true,
223                tracked_peer_sets: NZUsize!(1),
224            },
225            fixture.participants.clone(),
226        )
227        .await;
228        network.start();
229
230        let registrations = register_participants(&mut oracle, &fixture.participants).await;
231        link_participants(&mut oracle, &fixture.participants, Action::Link(link), None).await;
232        (oracle, registrations)
233    }
234
235    #[allow(clippy::too_many_arguments)]
236    fn spawn_validator_engines<S>(
237        context: Context,
238        fixture: &Fixture<S>,
239        sequencer_pks: &[PublicKey],
240        registrations: &mut Registrations<PublicKey>,
241        rebroadcast_timeout: Duration,
242        invalid_when: fn(Height) -> bool,
243        misses_allowed: Option<usize>,
244        epoch: Epoch,
245    ) -> BTreeMap<PublicKey, mocks::ReporterMailbox<PublicKey, S, Sha256Digest>>
246    where
247        S: Scheme<PublicKey, Sha256Digest>,
248    {
249        let mut reporters = BTreeMap::new();
250        let namespace = b"my testing namespace";
251
252        for (idx, validator) in fixture.participants.iter().enumerate() {
253            let context = context
254                .child("validator")
255                .with_attribute("public_key", validator);
256            let monitor = mocks::Monitor::new(epoch);
257            let sequencers = mocks::Sequencers::<PublicKey>::new(sequencer_pks.to_vec());
258
259            // Create Provider and register only this validator's scheme for the epoch
260            let validators_provider = mocks::Provider::new();
261            assert!(validators_provider.register(epoch, fixture.schemes[idx].clone()));
262
263            let automaton = mocks::Automaton::<PublicKey>::new(invalid_when);
264            let chunk_verifier = ChunkVerifier::new(namespace);
265            let (reporter, reporter_mailbox) = mocks::Reporter::new(
266                context.child("reporter"),
267                chunk_verifier.clone(),
268                fixture.verifier.clone(),
269                misses_allowed,
270            );
271            reporter.start();
272            reporters.insert(validator.clone(), reporter_mailbox);
273
274            let engine = Engine::new(
275                context.child("engine"),
276                Config {
277                    sequencer_signer: Some(ChunkSigner::new(
278                        namespace,
279                        fixture.private_keys[idx].clone(),
280                    )),
281                    chunk_verifier,
282                    sequencers_provider: sequencers,
283                    validators_provider,
284                    automaton: automaton.clone(),
285                    relay: automaton.clone(),
286                    reporter: reporters.get(validator).unwrap().clone(),
287                    monitor,
288                    priority_proposals: false,
289                    priority_acks: false,
290                    rebroadcast_timeout,
291                    epoch_bounds: (EpochDelta::new(1), EpochDelta::new(1)),
292                    height_bound: HeightDelta::new(2),
293                    journal_heights_per_section: NZU64!(10),
294                    journal_replay_buffer: NZUsize!(4096),
295                    journal_write_buffer: NZUsize!(4096),
296                    journal_name_prefix: format!("ordered-broadcast-seq-{validator}-"),
297                    journal_compression: Some(3),
298                    journal_page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
299                    strategy: Sequential,
300                },
301            );
302
303            let ((a1, a2), (b1, b2)) = registrations.remove(validator).unwrap();
304            engine.start((a1, a2), (b1, b2));
305        }
306        reporters
307    }
308
309    async fn await_reporters<S>(
310        context: Context,
311        sequencers: Vec<PublicKey>,
312        reporters: &BTreeMap<PublicKey, mocks::ReporterMailbox<PublicKey, S, Sha256Digest>>,
313        threshold: (Height, Epoch, bool),
314    ) where
315        S: certificate::Scheme,
316    {
317        let (threshold_height, threshold_epoch, require_contiguous) =
318            (threshold.0, threshold.1, threshold.2);
319        let mut receivers = Vec::new();
320        for (reporter, mailbox) in reporters.iter() {
321            // Spawn a watcher for the reporter.
322            for sequencer in sequencers.iter() {
323                // Create a oneshot channel to signal when the reporter has reached the threshold.
324                let (tx, rx) = oneshot::channel();
325                receivers.push(rx);
326
327                context.child("reporter_watcher").spawn({
328                    let reporter = reporter.clone();
329                    let sequencer = sequencer.clone();
330                    let mut mailbox = mailbox.clone();
331                    move |context| async move {
332                        loop {
333                            let (height, epoch) = mailbox
334                                .get_tip(sequencer.clone())
335                                .await
336                                .unwrap_or((Height::zero(), Epoch::zero()));
337                            debug!(height = %height, epoch = %epoch, ?sequencer, ?reporter, "reporter");
338                            let contiguous_height = mailbox
339                                .get_contiguous_tip(sequencer.clone())
340                                .await
341                                .unwrap_or(Height::zero());
342                            if height >= threshold_height
343                                && epoch >= threshold_epoch
344                                && (!require_contiguous || contiguous_height >= threshold_height)
345                            {
346                                tx.send_lossy(sequencer.clone());
347                                break;
348                            }
349                            context.sleep(Duration::from_millis(100)).await;
350                        }
351                    }
352                });
353            }
354        }
355
356        // Wait for all oneshot receivers to complete.
357        let results = join_all(receivers).await;
358        assert_eq!(results.len(), sequencers.len() * reporters.len());
359
360        // Check that none were cancelled.
361        for result in results {
362            assert!(result.is_ok(), "reporter was cancelled");
363        }
364    }
365
366    async fn get_max_height<S: certificate::Scheme>(
367        reporters: &mut BTreeMap<PublicKey, mocks::ReporterMailbox<PublicKey, S, Sha256Digest>>,
368    ) -> Height {
369        let mut max_height = Height::zero();
370        for (sequencer, mailbox) in reporters.iter_mut() {
371            let (height, _) = mailbox
372                .get_tip(sequencer.clone())
373                .await
374                .unwrap_or((Height::zero(), Epoch::zero()));
375            if height > max_height {
376                max_height = height;
377            }
378        }
379        max_height
380    }
381
382    fn all_online<S, F>(fixture: F)
383    where
384        S: Scheme<PublicKey, Sha256Digest>,
385        F: FnOnce(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
386    {
387        let runner = deterministic::Runner::timed(Duration::from_secs(120));
388
389        runner.start(|mut context| async move {
390            let epoch = Epoch::new(111);
391            let num_validators = 4;
392            let fixture = fixture(&mut context, TEST_NAMESPACE, num_validators);
393
394            let (_oracle, mut registrations) =
395                initialize_simulation(context.child("simulation"), &fixture, RELIABLE_LINK).await;
396
397            let reporters = spawn_validator_engines(
398                context.child("validators"),
399                &fixture,
400                &fixture.participants,
401                &mut registrations,
402                Duration::from_secs(5),
403                |_| false,
404                Some(5),
405                epoch,
406            );
407
408            await_reporters(
409                context.child("reporter"),
410                reporters.keys().cloned().collect::<Vec<_>>(),
411                &reporters,
412                (Height::new(100), epoch, true),
413            )
414            .await;
415        });
416    }
417
418    test_for_all_fixtures!(all_online);
419
420    fn unclean_shutdown<S, F>(fixture: F)
421    where
422        S: Scheme<PublicKey, Sha256Digest>,
423        F: Fn(&mut deterministic::Context, &[u8], u32) -> Fixture<S> + Clone,
424    {
425        let mut prev_checkpoint = None;
426        let epoch = Epoch::new(111);
427        let num_validators = 4;
428        let crash_after = Duration::from_secs(5);
429        let target_height = Height::new(30);
430
431        loop {
432            let fixture = fixture.clone();
433            let f = |mut context: deterministic::Context| async move {
434                let fixture = fixture(&mut context, TEST_NAMESPACE, num_validators);
435
436                let (network, mut oracle) = Network::new_with_peers(
437                    context.child("network"),
438                    commonware_p2p::simulated::Config {
439                        max_size: 1024 * 1024,
440                        disconnect_on_block: true,
441                        tracked_peer_sets: NZUsize!(1),
442                    },
443                    fixture.participants.clone(),
444                )
445                .await;
446                network.start();
447
448                let mut registrations =
449                    register_participants(&mut oracle, &fixture.participants).await;
450                link_participants(
451                    &mut oracle,
452                    &fixture.participants,
453                    Action::Link(RELIABLE_LINK),
454                    None,
455                )
456                .await;
457
458                let reporters = spawn_validator_engines(
459                    context.child("validator"),
460                    &fixture,
461                    &fixture.participants,
462                    &mut registrations,
463                    Duration::from_secs(5),
464                    |_| false,
465                    None,
466                    epoch,
467                );
468
469                // Either crash after `crash_after` or succeed once everyone reaches `target_height`.
470                let crash = context.sleep(crash_after);
471                let run = await_reporters(
472                    context.child("reporter"),
473                    reporters.keys().cloned().collect::<Vec<_>>(),
474                    &reporters,
475                    (target_height, epoch, true),
476                );
477
478                select! {
479                    _ = crash => false,
480                    _ = run => true,
481                }
482            };
483
484            let (complete, checkpoint) = prev_checkpoint
485                .map_or_else(
486                    || deterministic::Runner::timed(Duration::from_secs(180)),
487                    deterministic::Runner::from,
488                )
489                .start_and_recover(f);
490
491            if complete {
492                break;
493            }
494
495            prev_checkpoint = Some(checkpoint);
496        }
497    }
498
499    test_for_all_fixtures!(unclean_shutdown);
500
501    fn network_partition<S, F>(fixture: F)
502    where
503        S: Scheme<PublicKey, Sha256Digest>,
504        F: FnOnce(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
505    {
506        let runner = deterministic::Runner::timed(Duration::from_secs(60));
507
508        runner.start(|mut context| async move {
509            let epoch = Epoch::new(111);
510            let num_validators = 4;
511            let fixture = fixture(&mut context, TEST_NAMESPACE, num_validators);
512
513            // Configure the network
514            let (mut oracle, mut registrations) =
515                initialize_simulation(context.child("simulation"), &fixture, RELIABLE_LINK).await;
516            let mut reporters = spawn_validator_engines(
517                context.child("validators"),
518                &fixture,
519                &fixture.participants,
520                &mut registrations,
521                Duration::from_secs(1),
522                |_| false,
523                None,
524                epoch,
525            );
526
527            // Simulate partition by removing all links.
528            link_participants(&mut oracle, &fixture.participants, Action::Unlink, None).await;
529            context.sleep(Duration::from_secs(30)).await;
530
531            // Get the maximum height from all reporters.
532            let max_height = get_max_height(&mut reporters).await;
533
534            // Heal the partition by re-adding links.
535            link_participants(
536                &mut oracle,
537                &fixture.participants,
538                Action::Link(RELIABLE_LINK),
539                None,
540            )
541            .await;
542            await_reporters(
543                context.child("reporter"),
544                reporters.keys().cloned().collect::<Vec<_>>(),
545                &reporters,
546                (
547                    max_height.saturating_add(HeightDelta::new(100)),
548                    epoch,
549                    false,
550                ),
551            )
552            .await;
553        });
554    }
555
556    test_for_all_fixtures!(network_partition);
557
558    fn slow_and_lossy_links_seeded<S, F>(fixture: F, seed: u64) -> String
559    where
560        S: Scheme<PublicKey, Sha256Digest>,
561        F: Fn(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
562    {
563        let cfg = deterministic::Config::new()
564            .with_seed(seed)
565            .with_timeout(Some(Duration::from_secs(40)));
566        let runner = deterministic::Runner::new(cfg);
567
568        runner.start(|mut context| async move {
569            let epoch = Epoch::new(111);
570            let num_validators = 4;
571            let fixture = fixture(&mut context, TEST_NAMESPACE, num_validators);
572
573            let (mut oracle, mut registrations) =
574                initialize_simulation(context.child("simulation"), &fixture, RELIABLE_LINK).await;
575            let delayed_link = Link {
576                latency: Duration::from_millis(50),
577                jitter: Duration::from_millis(40),
578                success_rate: 0.5,
579            };
580            link_participants(
581                &mut oracle,
582                &fixture.participants,
583                Action::Update(delayed_link),
584                None,
585            )
586            .await;
587
588            let reporters = spawn_validator_engines(
589                context.child("validators"),
590                &fixture,
591                &fixture.participants,
592                &mut registrations,
593                Duration::from_millis(150),
594                |_| false,
595                None,
596                epoch,
597            );
598
599            await_reporters(
600                context.child("reporter"),
601                reporters.keys().cloned().collect::<Vec<_>>(),
602                &reporters,
603                (Height::new(40), epoch, false),
604            )
605            .await;
606
607            context.auditor().state()
608        })
609    }
610
611    fn slow_and_lossy_links<S, F>(fixture: F)
612    where
613        S: Scheme<PublicKey, Sha256Digest>,
614        F: Fn(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
615    {
616        slow_and_lossy_links_seeded(fixture, 0);
617    }
618
619    test_for_all_fixtures!(slow_and_lossy_links);
620
621    fn determinism<S, F>(fixture: F)
622    where
623        S: Scheme<PublicKey, Sha256Digest>,
624        F: Fn(&mut deterministic::Context, &[u8], u32) -> Fixture<S> + Copy,
625    {
626        // We use slow and lossy links as the deterministic test
627        // because it is the most complex test.
628        for seed in 1..6 {
629            assert_eq!(
630                slow_and_lossy_links_seeded(fixture, seed),
631                slow_and_lossy_links_seeded(fixture, seed),
632            );
633        }
634    }
635
636    test_for_all_fixtures!(determinism);
637
638    #[test_group("slow")]
639    #[test_traced]
640    fn test_distinct_states() {
641        // Sanity check that different schemes produce different audit states.
642        macro_rules! collect {
643            ($vec:ident, $suffix:ident, $fixture:expr) => {
644                $vec.push((
645                    stringify!($suffix),
646                    slow_and_lossy_links_seeded($fixture, 7),
647                ));
648            };
649        }
650        let mut states = Vec::new();
651        for_each_fixture!(collect!(states));
652        for pair in states.windows(2) {
653            assert_ne!(
654                pair[0].1, pair[1].1,
655                "state {} equals state {}",
656                pair[0].0, pair[1].0
657            );
658        }
659    }
660
661    fn invalid_signature_injection<S, F>(fixture: F)
662    where
663        S: Scheme<PublicKey, Sha256Digest>,
664        F: FnOnce(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
665    {
666        let runner = deterministic::Runner::timed(Duration::from_secs(30));
667
668        runner.start(|mut context| async move {
669            let epoch = Epoch::new(111);
670            let num_validators = 4;
671            let fixture = fixture(&mut context, TEST_NAMESPACE, num_validators);
672
673            let (_oracle, mut registrations) =
674                initialize_simulation(context.child("simulation"), &fixture, RELIABLE_LINK).await;
675
676            let reporters = spawn_validator_engines(
677                context.child("validators"),
678                &fixture,
679                &fixture.participants,
680                &mut registrations,
681                Duration::from_secs(5),
682                |i| i.get() % 10 == 0,
683                None,
684                epoch,
685            );
686
687            await_reporters(
688                context.child("reporter"),
689                reporters.keys().cloned().collect::<Vec<_>>(),
690                &reporters,
691                (Height::new(100), epoch, true),
692            )
693            .await;
694        });
695    }
696
697    test_for_all_fixtures!(invalid_signature_injection);
698
699    fn updated_epoch<S, F>(fixture: F)
700    where
701        S: Scheme<PublicKey, Sha256Digest>,
702        F: FnOnce(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
703    {
704        let runner = deterministic::Runner::timed(Duration::from_secs(60));
705
706        runner.start(|mut context| async move {
707            let epoch = Epoch::new(111);
708            let num_validators = 4;
709            let fixture = fixture(&mut context, TEST_NAMESPACE, num_validators);
710
711            // Setup network
712            let (mut oracle, mut registrations) =
713                initialize_simulation(context.child("simulation"), &fixture, RELIABLE_LINK).await;
714
715            let mut reporters = BTreeMap::new();
716
717            // Create validators instances that we can update later for epoch changes
718            let mut validators_providers = HashMap::new();
719            let mut monitors = HashMap::new();
720            let namespace = b"my testing namespace";
721
722            for (idx, validator) in fixture.participants.iter().enumerate() {
723                let context = context
724                    .child("validator")
725                    .with_attribute("public_key", validator);
726                let monitor = mocks::Monitor::new(epoch);
727                monitors.insert(validator.clone(), monitor.clone());
728                let sequencers = mocks::Sequencers::<PublicKey>::new(fixture.participants.clone());
729
730                // Create and store Provider so we can register new epochs later
731                let validators_provider = mocks::Provider::new();
732                assert!(validators_provider.register(epoch, fixture.schemes[idx].clone()));
733                validators_providers.insert(validator.clone(), validators_provider.clone());
734
735                let automaton = mocks::Automaton::<PublicKey>::new(|_| false);
736                let chunk_verifier = ChunkVerifier::new(namespace);
737                let (reporter, reporter_mailbox) = mocks::Reporter::new(
738                    context.child("reporter"),
739                    chunk_verifier.clone(),
740                    fixture.verifier.clone(),
741                    Some(5),
742                );
743                reporter.start();
744                reporters.insert(validator.clone(), reporter_mailbox);
745
746                let engine = Engine::new(
747                    context.child("engine"),
748                    Config {
749                        sequencer_signer: Some(ChunkSigner::new(
750                            namespace,
751                            fixture.private_keys[idx].clone(),
752                        )),
753                        chunk_verifier,
754                        sequencers_provider: sequencers,
755                        validators_provider,
756                        relay: automaton.clone(),
757                        automaton: automaton.clone(),
758                        reporter: reporters.get(validator).unwrap().clone(),
759                        monitor,
760                        epoch_bounds: (EpochDelta::new(1), EpochDelta::new(1)),
761                        height_bound: HeightDelta::new(2),
762                        rebroadcast_timeout: Duration::from_secs(1),
763                        priority_acks: false,
764                        priority_proposals: false,
765                        journal_heights_per_section: NZU64!(10),
766                        journal_replay_buffer: NZUsize!(4096),
767                        journal_write_buffer: NZUsize!(4096),
768                        journal_name_prefix: format!("ordered-broadcast-seq-{validator}-"),
769                        journal_compression: Some(3),
770                        journal_page_cache: CacheRef::from_pooler(
771                            &context,
772                            PAGE_SIZE,
773                            PAGE_CACHE_SIZE,
774                        ),
775                        strategy: Sequential,
776                    },
777                );
778
779                let ((a1, a2), (b1, b2)) = registrations.remove(validator).unwrap();
780                engine.start((a1, a2), (b1, b2));
781            }
782
783            // Perform some work
784            await_reporters(
785                context.child("reporter"),
786                reporters.keys().cloned().collect::<Vec<_>>(),
787                &reporters,
788                (Height::new(100), epoch, true),
789            )
790            .await;
791
792            // Simulate partition by removing all links.
793            link_participants(&mut oracle, &fixture.participants, Action::Unlink, None).await;
794            context.sleep(Duration::from_secs(30)).await;
795
796            // Get the maximum height from all reporters.
797            let max_height = get_max_height(&mut reporters).await;
798
799            // Update the epoch and register schemes for new epoch
800            let next_epoch = epoch.next();
801            for (validator, monitor) in monitors.iter() {
802                monitor.update(next_epoch);
803                // Register the scheme for the new epoch
804                let idx = fixture
805                    .participants
806                    .iter()
807                    .position(|v| v == validator)
808                    .unwrap();
809                let validators_provider = validators_providers.get(validator).unwrap();
810                assert!(validators_provider.register(next_epoch, fixture.schemes[idx].clone()));
811            }
812
813            // Heal the partition by re-adding links.
814            link_participants(
815                &mut oracle,
816                &fixture.participants,
817                Action::Link(RELIABLE_LINK),
818                None,
819            )
820            .await;
821            await_reporters(
822                context.child("reporter"),
823                reporters.keys().cloned().collect::<Vec<_>>(),
824                &reporters,
825                (
826                    max_height.saturating_add(HeightDelta::new(100)),
827                    next_epoch,
828                    true,
829                ),
830            )
831            .await;
832        });
833    }
834
835    test_for_all_fixtures!(updated_epoch);
836
837    fn external_sequencer<S, F>(fixture: F)
838    where
839        S: Scheme<PublicKey, Sha256Digest>,
840        F: FnOnce(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
841    {
842        let runner = deterministic::Runner::timed(Duration::from_secs(60));
843        runner.start(|mut context| async move {
844            let epoch = Epoch::new(111);
845            let num_validators = 4;
846            let fixture = fixture(&mut context, TEST_NAMESPACE, num_validators);
847
848            // Generate sequencer (external, not a validator)
849            let sequencer = PrivateKey::from_seed(u64::MAX);
850
851            // Generate network participants (validators + sequencer)
852            let mut participants = fixture.participants.clone();
853            participants.push(sequencer.public_key());
854
855            // Create network
856            let (network, mut oracle) = Network::new_with_peers(
857                context.child("network"),
858                commonware_p2p::simulated::Config {
859                    max_size: 1024 * 1024,
860                    disconnect_on_block: true,
861                    tracked_peer_sets: NZUsize!(1),
862                },
863                participants.clone(),
864            )
865            .await;
866            network.start();
867
868            // Register all participants
869            let mut registrations = register_participants(&mut oracle, &participants).await;
870            link_participants(
871                &mut oracle,
872                &participants,
873                Action::Link(RELIABLE_LINK),
874                None,
875            )
876            .await;
877
878            // Setup engines
879            let mut reporters = BTreeMap::new();
880            let namespace = b"my testing namespace";
881
882            // Spawn validator engines (no signing key, only validate)
883            for (idx, validator) in fixture.participants.iter().enumerate() {
884                let context = context
885                    .child("validator")
886                    .with_attribute("public_key", validator);
887                let monitor = mocks::Monitor::new(epoch);
888                let sequencers = mocks::Sequencers::<PublicKey>::new(vec![sequencer.public_key()]);
889
890                // Create Provider and register this validator's scheme
891                let validators_provider = mocks::Provider::new();
892                assert!(validators_provider.register(epoch, fixture.schemes[idx].clone()));
893
894                let automaton = mocks::Automaton::<PublicKey>::new(|_| false);
895
896                let chunk_verifier = ChunkVerifier::new(namespace);
897                let (reporter, reporter_mailbox) = mocks::Reporter::new(
898                    context.child("reporter"),
899                    chunk_verifier.clone(),
900                    fixture.verifier.clone(),
901                    Some(5),
902                );
903                reporter.start();
904                reporters.insert(validator.clone(), reporter_mailbox);
905
906                let engine = Engine::new(
907                    context.child("engine"),
908                    Config {
909                        sequencer_signer: None::<ChunkSigner<PrivateKey>>, // Validators don't propose in this test
910                        chunk_verifier,
911                        sequencers_provider: sequencers,
912                        validators_provider,
913                        relay: automaton.clone(),
914                        automaton: automaton.clone(),
915                        reporter: reporters.get(validator).unwrap().clone(),
916                        monitor,
917                        epoch_bounds: (EpochDelta::new(1), EpochDelta::new(1)),
918                        height_bound: HeightDelta::new(2),
919                        rebroadcast_timeout: Duration::from_secs(5),
920                        priority_acks: false,
921                        priority_proposals: false,
922                        journal_heights_per_section: NZU64!(10),
923                        journal_replay_buffer: NZUsize!(4096),
924                        journal_write_buffer: NZUsize!(4096),
925                        journal_name_prefix: format!("ordered-broadcast-seq-{validator}-"),
926                        journal_compression: Some(3),
927                        journal_page_cache: CacheRef::from_pooler(
928                            &context,
929                            PAGE_SIZE,
930                            PAGE_CACHE_SIZE,
931                        ),
932                        strategy: Sequential,
933                    },
934                );
935
936                let ((a1, a2), (b1, b2)) = registrations.remove(validator).unwrap();
937                engine.start((a1, a2), (b1, b2));
938            }
939
940            // Spawn sequencer engine
941            {
942                let context = context.child("sequencer");
943                let automaton = mocks::Automaton::<PublicKey>::new(|_| false);
944                let chunk_verifier = ChunkVerifier::new(namespace);
945                let (reporter, reporter_mailbox) = mocks::Reporter::new(
946                    context.child("reporter"),
947                    chunk_verifier.clone(),
948                    fixture.verifier.clone(),
949                    Some(5),
950                );
951                reporter.start();
952                reporters.insert(sequencer.public_key(), reporter_mailbox);
953
954                // Sequencer doesn't need a scheme (it uses ed25519 signing directly)
955                // But it needs the verifier to validate acks from validators
956                let validators_provider = mocks::Provider::new();
957                assert!(validators_provider.register(epoch, fixture.verifier.clone()));
958
959                let engine = Engine::new(
960                    context.child("engine"),
961                    Config {
962                        sequencer_signer: Some(ChunkSigner::new(namespace, sequencer.clone())),
963                        chunk_verifier,
964                        sequencers_provider: mocks::Sequencers::<PublicKey>::new(vec![
965                            sequencer.public_key()
966                        ]),
967                        validators_provider,
968                        relay: automaton.clone(),
969                        automaton,
970                        reporter: reporters.get(&sequencer.public_key()).unwrap().clone(),
971                        monitor: mocks::Monitor::new(epoch),
972                        epoch_bounds: (EpochDelta::new(1), EpochDelta::new(1)),
973                        height_bound: HeightDelta::new(2),
974                        rebroadcast_timeout: Duration::from_secs(5),
975                        priority_acks: false,
976                        priority_proposals: false,
977                        journal_heights_per_section: NZU64!(10),
978                        journal_replay_buffer: NZUsize!(4096),
979                        journal_write_buffer: NZUsize!(4096),
980                        journal_name_prefix: format!(
981                            "ordered-broadcast-seq-{}-",
982                            sequencer.public_key()
983                        ),
984                        journal_compression: Some(3),
985                        journal_page_cache: CacheRef::from_pooler(
986                            &context,
987                            PAGE_SIZE,
988                            PAGE_CACHE_SIZE,
989                        ),
990                        strategy: Sequential,
991                    },
992                );
993
994                let ((a1, a2), (b1, b2)) = registrations.remove(&sequencer.public_key()).unwrap();
995                engine.start((a1, a2), (b1, b2));
996            }
997
998            // Await reporters
999            await_reporters(
1000                context.child("reporter"),
1001                vec![sequencer.public_key()],
1002                &reporters,
1003                (Height::new(100), epoch, true),
1004            )
1005            .await;
1006        });
1007    }
1008
1009    test_for_all_fixtures!(external_sequencer);
1010
1011    fn run_1k<S, F>(fixture: F)
1012    where
1013        S: Scheme<PublicKey, Sha256Digest>,
1014        F: FnOnce(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
1015    {
1016        let cfg = deterministic::Config::new();
1017        let runner = deterministic::Runner::new(cfg);
1018
1019        runner.start(|mut context| async move {
1020            let epoch = Epoch::new(111);
1021            let num_validators = 10;
1022            let fixture = fixture(&mut context, TEST_NAMESPACE, num_validators);
1023
1024            let delayed_link = Link {
1025                latency: Duration::from_millis(80),
1026                jitter: Duration::from_millis(10),
1027                success_rate: 0.98,
1028            };
1029
1030            let (mut oracle, mut registrations) =
1031                initialize_simulation(context.child("simulation"), &fixture, RELIABLE_LINK).await;
1032
1033            // Update to delayed links
1034            link_participants(
1035                &mut oracle,
1036                &fixture.participants,
1037                Action::Update(delayed_link),
1038                None,
1039            )
1040            .await;
1041
1042            // Use first half of validators as sequencers
1043            let sequencers: Vec<PublicKey> =
1044                fixture.participants[0..num_validators as usize / 2].to_vec();
1045
1046            let reporters = spawn_validator_engines(
1047                context.child("validators"),
1048                &fixture,
1049                &sequencers,
1050                &mut registrations,
1051                Duration::from_millis(150),
1052                |_| false,
1053                None,
1054                epoch,
1055            );
1056
1057            await_reporters(
1058                context.child("reporter"),
1059                sequencers,
1060                &reporters,
1061                (Height::new(1_000), epoch, false),
1062            )
1063            .await;
1064        })
1065    }
1066
1067    #[test_group("slow")]
1068    #[test_traced]
1069    fn test_1k() {
1070        run_1k(mocks::scheme::fixture);
1071    }
1072}