1pub mod elector;
467pub mod scheme;
468pub mod types;
469
470use crate::types::{TermLength, View, ViewDelta};
471
472#[derive(Clone, Copy)]
485#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
486pub(crate) struct Lookahead {
487 pub term_length: TermLength,
489 pub optimistic_views: ViewDelta,
492}
493
494#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
495impl Lookahead {
496 pub(crate) const fn new(terms: &elector::Terms) -> Self {
498 Self {
499 term_length: terms.length(),
500 optimistic_views: terms.optimistic_views(),
501 }
502 }
503
504 pub fn in_admission_window(&self, current: View, pending: View) -> bool {
508 current < pending && pending <= self.admission_limit(current)
509 }
510
511 pub fn admits(&self, current: View, pending: View) -> bool {
520 current.admits(pending, self.term_length) || self.in_admission_window(current, pending)
521 }
522
523 pub fn admission_limit(&self, current: View) -> View {
526 current
527 .term_end(self.term_length)
528 .min(current.saturating_add(self.optimistic_views))
529 }
530
531 pub const fn issuance_floor(&self, view: View) -> Option<View> {
544 if self.optimistic_views.is_zero() || view.is_term_start(self.term_length) {
545 return None;
546 }
547 Some(
548 view.saturating_sub(self.optimistic_views)
549 .saturating_sub(ViewDelta::new(1)),
550 )
551 }
552}
553
554cfg_if::cfg_if! {
555 if #[cfg(not(target_arch = "wasm32"))] {
556 use crate::types::Round;
557 use commonware_cryptography::PublicKey;
558 use commonware_p2p::Recipients;
559
560 mod actors;
561 pub mod config;
562 pub use config::{Config, Floor, ForwardPolicy, SkipBudget, SkipPolicy};
563 mod engine;
564 pub use engine::Engine;
565 mod metrics;
566
567 #[derive(Clone, Copy)]
570 pub(crate) struct Viewport {
571 pub finalized: View,
573 pub current: View,
575 pub view_retention: ViewDelta,
577 pub lookahead: Lookahead,
579 }
580
581 impl Viewport {
582 pub const fn floor(&self) -> View {
584 self.finalized.saturating_sub(self.view_retention)
585 }
586
587 pub const fn retains(&self, view: View) -> bool {
592 !view.is_zero() && view.get() >= self.floor().get()
593 }
594
595 pub fn admits_vote(&self, pending: View) -> bool {
601 self.retains(pending) && self.lookahead.admits(self.current, pending)
602 }
603
604 pub const fn admits_certificate(&self, pending: View) -> bool {
609 self.retains(pending)
610 }
611 }
612
613 pub enum Plan<P: PublicKey> {
615 Propose {
623 round: Round,
625 },
626 Forward {
633 round: Round,
635 recipients: Recipients<P>,
637 },
638 }
639 }
640}
641
642#[cfg(any(test, feature = "mocks"))]
643pub mod mocks;
644
645#[cfg(test)]
647pub(crate) fn quorum(n: u32) -> u32 {
648 use commonware_utils::{Faults, N3f1};
649
650 N3f1::quorum(n)
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656 use crate::{
657 Monitor, Viewable,
658 simplex::{
659 elector::{self, Config as _, Elector as _, Random, RandomVersion, RoundRobin},
660 mocks::{
661 scheme as scheme_mocks,
662 twins::{self, Elector as TwinsElector},
663 wrapped,
664 },
665 scheme::{
666 Scheme, bls12381_multisig,
667 bls12381_threshold::{
668 standard as bls12381_threshold_std,
669 vrf::{self as bls12381_threshold_vrf, Seedable},
670 },
671 ed25519, secp256r1,
672 },
673 types::{
674 Certificate, Finalization as TFinalization, Finalize as TFinalize,
675 Notarization as TNotarization, Notarize as TNotarize,
676 Nullification as TNullification, Nullify as TNullify, Proposal, Vote,
677 },
678 },
679 types::{Epoch, Participant, Round, TermLength, View, ViewDelta},
680 };
681 use commonware_codec::{Decode, DecodeExt, Encode};
682 use commonware_cryptography::{
683 Hasher as _, Sha256, Signer as _,
684 bls12381::primitives::variant::{MinPk, MinSig, Variant},
685 certificate::mocks::Fixture,
686 ed25519::{PrivateKey, PublicKey},
687 sha256::{Digest as Sha256Digest, Digest as D},
688 };
689 use commonware_macros::{select, test_group, test_traced};
690 use commonware_p2p::{
691 Manager as _, Recipients, Sender as _, TrackedPeers,
692 simulated::{Config, Link, Network, Oracle, Receiver, Sender, SplitOrigin},
693 utils::mocks::inert_channel,
694 };
695 use commonware_parallel::{Sequential, Strategy};
696 use commonware_runtime::{
697 Clock, IoBuf, Metrics as _, Quota, Runner, Spawner, Strategizer as _, Supervisor as _,
698 buffer::paged::CacheRef, deterministic, telemetry::metrics::count_running_tasks,
699 };
700 use commonware_utils::{
701 Faults, N3f1, NZU16, NZU32, NZUsize, TestRng, non_empty, ordered::Set, probability,
702 sync::Mutex, test_rng,
703 };
704 use engine::Engine;
705 use futures::future::join_all;
706 use rand::{RngExt as _, SeedableRng, rngs::StdRng};
707 use rand_core::CryptoRng;
708 use std::{
709 collections::{BTreeMap, HashMap, HashSet},
710 num::{NonZeroU16, NonZeroU32, NonZeroUsize},
711 sync::Arc,
712 time::Duration,
713 };
714 use tracing::{debug, info, warn};
715 use types::Activity;
716
717 macro_rules! for_each_fixture {
720 ($cb:ident!($($args:tt)*)) => {
721 $cb!($($args)*, bls12381_threshold_vrf_min_pk, Random, bls12381_threshold_vrf::fixture::<MinPk, _>, Random::new(RandomVersion::V1));
722 $cb!($($args)*, bls12381_threshold_vrf_min_sig, Random, bls12381_threshold_vrf::fixture::<MinSig, _>, Random::new(RandomVersion::V1));
723 $cb!($($args)*, bls12381_threshold_std_min_pk, RoundRobin, bls12381_threshold_std::fixture::<MinPk, _>, RoundRobin::default());
724 $cb!($($args)*, bls12381_threshold_std_min_sig, RoundRobin, bls12381_threshold_std::fixture::<MinSig, _>, RoundRobin::default());
725 $cb!($($args)*, bls12381_multisig_min_pk, RoundRobin, bls12381_multisig::fixture::<MinPk, _>, RoundRobin::default());
726 $cb!($($args)*, bls12381_multisig_min_sig, RoundRobin, bls12381_multisig::fixture::<MinSig, _>, RoundRobin::default());
727 $cb!($($args)*, ed25519, RoundRobin, ed25519::fixture, RoundRobin::default());
728 $cb!($($args)*, secp256r1, RoundRobin, secp256r1::fixture, RoundRobin::default());
729 };
730 }
731
732 macro_rules! test_for_all_fixtures {
744 ($callee:ident) => {
745 for_each_fixture!(test_for_all_fixtures!(@emit [test_traced] $callee [] []));
746 };
747 ($callee:ident, level = $level:literal) => {
748 for_each_fixture!(test_for_all_fixtures!(@emit [test_traced($level)] $callee [] []));
749 };
750 ($callee:ident, seeds = $n:expr) => {
751 for_each_fixture!(test_for_all_fixtures!(@seeded $n, $callee));
752 };
753 ($callee:ident, $arg:expr, level = $level:literal) => {
754 for_each_fixture!(test_for_all_fixtures!(@emit [test_traced($level)] $callee [, _] [, $arg]));
755 };
756 ($callee:ident, $arg:expr) => {
757 for_each_fixture!(test_for_all_fixtures!(@emit [test_traced] $callee [, _] [, $arg]));
758 };
759 (@emit [$traced:meta] $callee:ident [$($generics:tt)*] [$($args:tt)*], $suffix:ident, $elector:ty, $fixture:expr, $elector_config:expr) => {
760 paste::paste! {
761 #[test_group("slow")]
762 #[$traced]
763 fn [<test_ $callee _ $suffix>]() {
764 $callee::<_, _, $elector $($generics)*>($fixture, $elector_config $($args)*);
765 }
766 }
767 };
768 (@seeded $n:expr, $callee:ident, $suffix:ident, $elector:ty, $fixture:expr, $elector_config:expr) => {
769 paste::paste! {
770 #[test_group("slow")]
771 #[test_traced]
772 fn [<test_ $callee _ $suffix>]() {
773 for seed in 0..$n {
774 $callee::<_, _, $elector>(seed, $fixture, $elector_config);
775 }
776 }
777 }
778 };
779 }
780
781 const PAGE_SIZE: NonZeroU16 = NZU16!(1024);
782 const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(10);
783 const TEST_QUOTA: Quota = Quota::per_second(NonZeroU32::MAX);
784
785 type TestChannel = (
786 Sender<PublicKey, deterministic::Context>,
787 Receiver<PublicKey>,
788 );
789 type TestRegistration = (TestChannel, TestChannel, TestChannel);
790 type TestRegistrations = HashMap<PublicKey, TestRegistration>;
791
792 fn test_lookahead(optimistic_views: u64) -> Lookahead {
794 Lookahead {
795 term_length: TermLength::new(NZU32!(5)),
796 optimistic_views: ViewDelta::new(optimistic_views),
797 }
798 }
799
800 #[test]
801 fn test_lookahead_admits() {
802 let current = View::new(6);
803
804 assert!(test_lookahead(0).admits(current, current.next()));
806
807 let next_term_start = current.next_term_start(test_lookahead(0).term_length);
809 assert!(test_lookahead(0).admits(current, next_term_start));
810
811 assert!(test_lookahead(2).admits(current, View::new(8)));
813 assert!(!test_lookahead(2).admits(current, View::new(9)));
814
815 assert!(!test_lookahead(10).admits(current, View::new(100)));
817 }
818
819 #[test]
820 fn test_optimistic_future_does_not_bleed_into_next_term() {
821 let lookahead = test_lookahead(100);
822 let current = View::new(9);
823 let next_term_start = current.next_term_start(lookahead.term_length);
824
825 assert!(lookahead.admits(current, View::new(10)));
827
828 assert!(lookahead.admits(current, next_term_start));
830
831 assert!(!lookahead.admits(current, next_term_start.next()));
833 }
834
835 #[test]
836 fn test_admission_limit() {
837 let current = View::new(6);
839
840 assert_eq!(test_lookahead(0).admission_limit(current), current);
842
843 assert_eq!(test_lookahead(3).admission_limit(current), View::new(9));
845
846 assert_eq!(test_lookahead(100).admission_limit(current), View::new(10));
848
849 assert_eq!(
851 test_lookahead(3).admission_limit(View::new(10)),
852 View::new(10)
853 );
854 }
855
856 #[test]
857 fn test_issuance_floor() {
858 assert!(test_lookahead(0).issuance_floor(View::new(7)).is_none());
860
861 assert!(test_lookahead(100).issuance_floor(View::new(11)).is_none());
863
864 assert_eq!(
867 test_lookahead(2).issuance_floor(View::new(9)),
868 Some(View::new(6))
869 );
870
871 assert_eq!(
873 test_lookahead(2).issuance_floor(View::new(2)),
874 Some(View::zero())
875 );
876 }
877
878 async fn register_validator(
880 oracle: &mut Oracle<PublicKey, deterministic::Context>,
881 validator: PublicKey,
882 ) -> TestRegistration {
883 let control = oracle.control(validator.clone());
884 let (vote_sender, vote_receiver) = control.register(0, TEST_QUOTA).await.unwrap();
885 let (certificate_sender, certificate_receiver) =
886 control.register(1, TEST_QUOTA).await.unwrap();
887 let (resolver_sender, resolver_receiver) = control.register(2, TEST_QUOTA).await.unwrap();
888 (
889 (vote_sender, vote_receiver),
890 (certificate_sender, certificate_receiver),
891 (resolver_sender, resolver_receiver),
892 )
893 }
894
895 async fn register_validators(
897 oracle: &mut Oracle<PublicKey, deterministic::Context>,
898 validators: &[PublicKey],
899 ) -> TestRegistrations {
900 let mut registrations = HashMap::new();
901 for validator in validators.iter() {
902 let registration = register_validator(oracle, validator.clone()).await;
903 registrations.insert(validator.clone(), registration);
904 }
905 registrations
906 }
907
908 async fn start_test_network_with_peers<I>(
909 context: deterministic::Context,
910 peers: I,
911 disconnect_on_block: bool,
912 ) -> Oracle<PublicKey, deterministic::Context>
913 where
914 I: IntoIterator<Item = PublicKey>,
915 {
916 let peers: Vec<_> = peers.into_iter().collect();
917 let (network, oracle) = Network::new_with_peers(
918 context.child("network"),
919 Config {
920 max_size: 1024 * 1024,
921 max_peers_per_set: NZUsize!(peers.len() + 1),
923 disconnect_on_block,
924 tracked_peer_sets: NZUsize!(1),
925 },
926 peers,
927 )
928 .await;
929 network.start();
930 oracle
931 }
932
933 async fn start_test_network_with_split_peers<I, J>(
934 context: deterministic::Context,
935 primary: I,
936 secondary: J,
937 disconnect_on_block: bool,
938 ) -> Oracle<PublicKey, deterministic::Context>
939 where
940 I: IntoIterator<Item = PublicKey>,
941 J: IntoIterator<Item = PublicKey>,
942 {
943 let primary: Vec<_> = primary.into_iter().collect();
944 let secondary: Vec<_> = secondary.into_iter().collect();
945 let (network, oracle) = Network::new_with_split_peers(
946 context.child("network"),
947 Config {
948 max_size: 1024 * 1024,
949 max_peers_per_set: NZUsize!(primary.len() + secondary.len()),
950 disconnect_on_block,
951 tracked_peer_sets: NZUsize!(1),
952 },
953 primary,
954 secondary,
955 )
956 .await;
957 network.start();
958 oracle
959 }
960
961 enum Action {
963 Link(Link),
964 Update(Link), Unlink,
966 }
967
968 async fn link_validators(
974 oracle: &mut Oracle<PublicKey, deterministic::Context>,
975 validators: &[PublicKey],
976 action: Action,
977 restrict_to: Option<fn(usize, usize, usize) -> bool>,
978 ) {
979 for (i1, v1) in validators.iter().enumerate() {
980 for (i2, v2) in validators.iter().enumerate() {
981 if v2 == v1 {
983 continue;
984 }
985
986 if let Some(f) = restrict_to
988 && !f(validators.len(), i1, i2)
989 {
990 continue;
991 }
992
993 match action {
995 Action::Update(_) | Action::Unlink => {
996 oracle.remove_link(v1.clone(), v2.clone()).await.unwrap();
997 }
998 _ => {}
999 }
1000
1001 match action {
1003 Action::Link(ref link) | Action::Update(ref link) => {
1004 oracle
1005 .add_link(v1.clone(), v2.clone(), link.clone())
1006 .await
1007 .unwrap();
1008 }
1009 _ => {}
1010 }
1011 }
1012 }
1013 }
1014
1015 fn count_nonzero_metric_lines(encoded: &str, patterns: &[&str]) -> u32 {
1017 encoded
1018 .lines()
1019 .filter(|line| patterns.iter().all(|p| line.contains(p)))
1020 .filter(|line| {
1021 line.split_whitespace()
1022 .last()
1023 .and_then(|s| s.parse::<u64>().ok())
1024 .is_some_and(|n| n > 0)
1025 })
1026 .count() as u32
1027 }
1028
1029 fn all_online<S, F, L, T>(
1030 mut fixture: F,
1031 elector: L,
1032 strategy: impl FnOnce(&mut deterministic::Context) -> T + Send + 'static,
1033 ) where
1034 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
1035 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
1036 L: elector::Config<S>,
1037 T: Strategy,
1038 {
1039 let n = 5;
1041 let quorum = quorum(n) as usize;
1042 let required_containers = View::new(100);
1043 let view_retention = ViewDelta::new(10);
1044 let skip_timeout = Duration::from_secs(12);
1045 let namespace = b"consensus".to_vec();
1046 let executor = deterministic::Runner::timed(Duration::from_secs(300));
1047 executor.start(|mut context| async move {
1048 let Fixture {
1050 participants,
1051 schemes,
1052 ..
1053 } = fixture(&mut context, &namespace, n);
1054 let strategy = strategy(&mut context);
1055 let mut oracle =
1056 start_test_network_with_peers(context.child("network"), participants.clone(), true)
1057 .await;
1058 let mut registrations = register_validators(&mut oracle, &participants).await;
1059
1060 let link = Link {
1066 latency: Duration::from_millis(200),
1067 jitter: Duration::from_millis(1),
1068 success_rate: probability!(1.0),
1069 };
1070 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
1071
1072 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
1074 let mut reporters = Vec::new();
1075 let mut engine_handlers = Vec::new();
1076 for (idx, validator) in participants.iter().enumerate() {
1077 let context = context
1079 .child("validator")
1080 .with_attribute("public_key", validator);
1081
1082 let reporter_config = mocks::reporter::Config {
1084 participants: participants.clone().try_into().unwrap(),
1085 scheme: schemes[idx].clone(),
1086 elector: elector.clone(),
1087 };
1088 let reporter =
1089 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
1090 reporters.push(reporter.clone());
1091 let application_cfg = mocks::application::Config::<Sha256, _> {
1092 relay: relay.clone(),
1093 me: validator.clone(),
1094 propose_latency: (10.0, 5.0),
1095 verify_latency: (10.0, 5.0),
1096 certify_latency: (10.0, 5.0),
1097 should_certify: mocks::application::Certifier::Always,
1098 };
1099 let (actor, application) = mocks::application::Application::new(
1100 context.child("application"),
1101 application_cfg,
1102 );
1103 actor.start();
1104 let blocker = oracle.control(validator.clone());
1105 let cfg = config::Config {
1106 scheme: schemes[idx].clone(),
1107 elector: elector.clone(),
1108 blocker,
1109 automaton: application.clone(),
1110 relay: application.clone(),
1111 reporter: reporter.clone(),
1112 strategy: strategy.clone(),
1113 partition: validator.to_string(),
1114 mailbox_size: NZUsize!(1024),
1115 epoch: Epoch::new(333),
1116 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
1117 Epoch::new(333),
1118 )),
1119 leader_timeout: Duration::from_secs(2),
1120 certification_timeout: Duration::from_secs(3),
1121 timeout_retry: Duration::from_secs(10),
1122 fetch_timeout: Duration::from_secs(1),
1123 view_retention,
1124 skip: SkipPolicy::Enabled {
1125 timeout: skip_timeout,
1126 budget: SkipBudget::Participants,
1127 },
1128 replay_buffer: NZUsize!(1024 * 1024),
1129 write_buffer: NZUsize!(1024 * 1024),
1130 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1131 forward: ForwardPolicy::Disabled,
1132 track_historical_votes: false,
1133 };
1134 let engine = Engine::new(context.child("engine"), cfg);
1135
1136 let (pending, recovered, resolver) = registrations
1138 .remove(validator)
1139 .expect("validator should be registered");
1140 engine_handlers.push(engine.start(pending, recovered, resolver));
1141 }
1142
1143 let mut finalizers = Vec::new();
1145 for reporter in reporters.iter_mut() {
1146 let (mut latest, mut monitor) = reporter.subscribe().await;
1147 finalizers.push(context.child("finalizer").spawn(move |_| async move {
1148 while latest < required_containers {
1149 latest = monitor.recv().await.expect("event missing");
1150 }
1151 }));
1152 }
1153 join_all(finalizers).await;
1154
1155 let latest_complete = required_containers.saturating_sub(view_retention);
1157 for reporter in reporters.iter() {
1158 reporter.assert_no_faults();
1160
1161 reporter.assert_no_invalid();
1163
1164 {
1166 let certified = reporter.certified.lock();
1167 for view in View::range(View::new(1), latest_complete) {
1168 if !certified.contains(&view) {
1170 panic!("view: {view}");
1171 }
1172 }
1173 }
1174
1175 let mut notarized = HashMap::new();
1177 let mut finalized = HashMap::new();
1178 {
1179 let notarizes = reporter.notarizes.lock();
1180 for view in View::range(View::new(1), latest_complete) {
1181 let Some(payloads) = notarizes.get(&view) else {
1183 continue;
1184 };
1185 if payloads.len() > 1 {
1186 panic!("view: {view}");
1187 }
1188 let (digest, notarizers) = payloads.iter().next().unwrap();
1189 notarized.insert(view, *digest);
1190
1191 if notarizers.len() < quorum {
1192 panic!("view: {view}");
1195 }
1196 }
1197 }
1198 {
1199 let notarizations = reporter.notarizations.lock();
1200 for view in View::range(View::new(1), latest_complete) {
1201 let Some(notarization) = notarizations.get(&view) else {
1203 continue;
1204 };
1205 let Some(digest) = notarized.get(&view) else {
1206 continue;
1207 };
1208 assert_eq!(¬arization.proposal.payload, digest);
1209 }
1210 }
1211 {
1212 let finalizes = reporter.finalizes.lock();
1213 for view in View::range(View::new(1), latest_complete) {
1214 let Some(payloads) = finalizes.get(&view) else {
1216 continue;
1217 };
1218 if payloads.len() > 1 {
1219 panic!("view: {view}");
1220 }
1221 let (digest, finalizers) = payloads.iter().next().unwrap();
1222 finalized.insert(view, *digest);
1223
1224 if view > latest_complete {
1226 continue;
1227 }
1228
1229 if finalizers.len() < quorum {
1231 panic!("view: {view}");
1234 }
1235
1236 let nullifies = reporter.nullifies.lock();
1238 let Some(nullifies) = nullifies.get(&view) else {
1239 continue;
1240 };
1241 for finalizers in payloads.values() {
1242 for finalizer in finalizers.iter() {
1243 if nullifies.contains(finalizer) {
1244 panic!("should not nullify and finalize at same view");
1245 }
1246 }
1247 }
1248 }
1249 }
1250 {
1251 let finalizations = reporter.finalizations.lock();
1252 for view in View::range(View::new(1), latest_complete) {
1253 let Some(finalization) = finalizations.get(&view) else {
1255 continue;
1256 };
1257 let Some(digest) = finalized.get(&view) else {
1258 continue;
1259 };
1260 assert_eq!(&finalization.proposal.payload, digest);
1261 }
1262 }
1263 }
1264
1265 let blocked = oracle.blocked().await.unwrap();
1267 assert!(blocked.is_empty());
1268 });
1269 }
1270
1271 test_for_all_fixtures!(all_online, |_| Sequential);
1272
1273 #[test_group("slow")]
1274 #[test_traced]
1275 fn test_all_online_rayon_bls12381_threshold_vrf_min_pk() {
1276 all_online::<_, _, Random, _>(
1277 bls12381_threshold_vrf::fixture::<MinPk, _>,
1278 Random::new(RandomVersion::V1),
1279 |context| context.strategy(NZUsize!(2)),
1280 );
1281 }
1282
1283 fn non_genesis_floor_joiner_catches_tip<S, F, L>(fixture: F, elector: L)
1284 where
1285 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
1286 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
1287 L: elector::Config<S>,
1288 {
1289 non_genesis_floor_joiner_catches_tip_with_term::<S, F, L>(elector, fixture);
1290 }
1291
1292 fn non_genesis_floor_joiner_catches_tip_with_term<S, F, L>(elector: L, mut fixture: F)
1293 where
1294 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
1295 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
1296 L: elector::Config<S>,
1297 {
1298 let n = 5;
1301 let active_count = quorum(n) as usize;
1302 let initial_tip_target = View::new(15);
1303 let view_retention = ViewDelta::new(10);
1304 let skip_timeout = Duration::from_secs(5);
1305 let timeout_retry = Duration::from_secs(1);
1306 let namespace = b"consensus".to_vec();
1307 let executor = deterministic::Runner::timed(Duration::from_secs(300));
1308 executor.start(|mut context| async move {
1309 let Fixture {
1310 participants,
1311 schemes,
1312 ..
1313 } = fixture(&mut context, &namespace, n);
1314 let mut oracle =
1315 start_test_network_with_peers(context.child("network"), participants.clone(), true)
1316 .await;
1317
1318 let active = &participants[..active_count];
1319 let joiner_idx = active_count;
1320 let joiner = participants[joiner_idx].clone();
1321
1322 let link = Link {
1323 latency: Duration::from_millis(10),
1324 jitter: Duration::from_millis(1),
1325 success_rate: probability!(1.0),
1326 };
1327 link_validators(&mut oracle, active, Action::Link(link.clone()), None).await;
1328
1329 let term_length = elector
1330 .clone()
1331 .build(schemes[0].participants())
1332 .terms()
1333 .length();
1334 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
1335 let mut reporters = Vec::new();
1336 let mut engine_handlers = Vec::new();
1337
1338 for (idx, validator) in active.iter().enumerate() {
1339 let validator_context = context
1340 .child("validator")
1341 .with_attribute("public_key", validator);
1342
1343 let reporter_config = mocks::reporter::Config {
1344 participants: participants.clone().try_into().unwrap(),
1345 scheme: schemes[idx].clone(),
1346 elector: elector.clone(),
1347 };
1348 let reporter = mocks::reporter::Reporter::new(
1349 validator_context.child("reporter"),
1350 reporter_config,
1351 );
1352 reporters.push(reporter.clone());
1353
1354 let application_cfg = mocks::application::Config::<Sha256, _> {
1355 relay: relay.clone(),
1356 me: validator.clone(),
1357 propose_latency: (10.0, 5.0),
1358 verify_latency: (10.0, 5.0),
1359 certify_latency: (10.0, 5.0),
1360 should_certify: mocks::application::Certifier::Always,
1361 };
1362 let (actor, application) = mocks::application::Application::new(
1363 validator_context.child("application"),
1364 application_cfg,
1365 );
1366 actor.start();
1367
1368 let cfg = config::Config {
1369 scheme: schemes[idx].clone(),
1370 elector: elector.clone(),
1371 blocker: oracle.control(validator.clone()),
1372 automaton: application.clone(),
1373 relay: application.clone(),
1374 reporter: reporter.clone(),
1375 strategy: Sequential,
1376 partition: format!("joiner_catches_tip_{validator}"),
1377 mailbox_size: NZUsize!(1024),
1378 epoch: Epoch::new(333),
1379 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
1380 Epoch::new(333),
1381 )),
1382 leader_timeout: Duration::from_secs(1),
1383 certification_timeout: Duration::from_secs(2),
1384 timeout_retry,
1385 fetch_timeout: Duration::from_secs(1),
1386 view_retention,
1387 skip: SkipPolicy::Enabled {
1388 timeout: skip_timeout,
1389 budget: SkipBudget::Participants,
1390 },
1391 replay_buffer: NZUsize!(1024 * 1024),
1392 write_buffer: NZUsize!(1024 * 1024),
1393 page_cache: CacheRef::from_pooler(
1394 &validator_context,
1395 PAGE_SIZE,
1396 PAGE_CACHE_SIZE,
1397 ),
1398 forward: ForwardPolicy::Disabled,
1399 track_historical_votes: false,
1400 };
1401 let engine = Engine::new(validator_context.child("engine"), cfg);
1402 let (pending, recovered, resolver) =
1403 register_validator(&mut oracle, validator.clone()).await;
1404 engine_handlers.push(engine.start(pending, recovered, resolver));
1405 }
1406
1407 let mut finalizers = Vec::new();
1408 for reporter in reporters.iter_mut() {
1409 let (mut latest, mut monitor) = reporter.subscribe().await;
1410 finalizers.push(
1411 context
1412 .child("initial_finalizer")
1413 .spawn(move |_| async move {
1414 while latest < initial_tip_target {
1415 latest = monitor.recv().await.expect("event missing");
1416 }
1417 latest
1418 }),
1419 );
1420 }
1421 let tip_at_join = join_all(finalizers)
1422 .await
1423 .into_iter()
1424 .map(|result| result.expect("initial finalizer failed"))
1425 .min()
1426 .expect("initial validators missing");
1427
1428 let (floor_view, floor_finalization) = {
1432 let finalizations = reporters[0].finalizations.lock();
1433 let mut eligible: Vec<_> = finalizations
1434 .iter()
1435 .filter(|(view, _)| **view > View::zero() && **view < tip_at_join)
1436 .collect();
1437 eligible.sort_by_key(|(view, _)| view.get());
1438 eligible
1439 .iter()
1440 .find(|(view, _)| !view.is_term_start(term_length))
1441 .or_else(|| eligible.first())
1442 .map(|(view, finalization)| (**view, (*finalization).clone()))
1443 .expect("non-genesis floor finalization missing")
1444 };
1445 assert!(floor_view > View::zero());
1446 assert!(floor_view < tip_at_join);
1447 if term_length.get() > 1 {
1448 assert!(
1449 !floor_view.is_term_start(term_length),
1450 "expected a mid-term floor at view {floor_view}"
1451 );
1452 }
1453
1454 for validator in active.iter() {
1457 oracle
1458 .add_link(joiner.clone(), validator.clone(), link.clone())
1459 .await
1460 .unwrap();
1461 oracle
1462 .add_link(validator.clone(), joiner.clone(), link.clone())
1463 .await
1464 .unwrap();
1465 }
1466
1467 let joiner_context = context
1468 .child("validator")
1469 .with_attribute("public_key", &joiner);
1470 let reporter_config = mocks::reporter::Config {
1471 participants: participants.clone().try_into().unwrap(),
1472 scheme: schemes[joiner_idx].clone(),
1473 elector: elector.clone(),
1474 };
1475 let mut joiner_reporter =
1476 mocks::reporter::Reporter::new(joiner_context.child("reporter"), reporter_config);
1477 reporters.push(joiner_reporter.clone());
1478
1479 let application_cfg = mocks::application::Config::<Sha256, _> {
1480 relay: relay.clone(),
1481 me: joiner.clone(),
1482 propose_latency: (10.0, 5.0),
1483 verify_latency: (10.0, 5.0),
1484 certify_latency: (10.0, 5.0),
1485 should_certify: mocks::application::Certifier::Always,
1486 };
1487 let (actor, application) = mocks::application::Application::new(
1488 joiner_context.child("application"),
1489 application_cfg,
1490 );
1491 actor.start();
1492
1493 let cfg = config::Config {
1494 scheme: schemes[joiner_idx].clone(),
1495 elector,
1496 blocker: oracle.control(joiner.clone()),
1497 automaton: application.clone(),
1498 relay: application.clone(),
1499 reporter: joiner_reporter.clone(),
1500 strategy: Sequential,
1501 partition: format!("joiner_catches_tip_{joiner}"),
1502 mailbox_size: NZUsize!(1024),
1503 epoch: Epoch::new(333),
1504 floor: config::Floor::Finalized(floor_finalization),
1505 leader_timeout: Duration::from_secs(1),
1506 certification_timeout: Duration::from_secs(2),
1507 timeout_retry,
1508 fetch_timeout: Duration::from_secs(1),
1509 view_retention,
1510 skip: SkipPolicy::Enabled {
1511 timeout: skip_timeout,
1512 budget: SkipBudget::Participants,
1513 },
1514 replay_buffer: NZUsize!(1024 * 1024),
1515 write_buffer: NZUsize!(1024 * 1024),
1516 page_cache: CacheRef::from_pooler(&joiner_context, PAGE_SIZE, PAGE_CACHE_SIZE),
1517 forward: ForwardPolicy::Disabled,
1518 track_historical_votes: false,
1519 };
1520 let engine = Engine::new(joiner_context.child("engine"), cfg);
1521 let (pending, recovered, resolver) = register_validator(&mut oracle, joiner).await;
1522 engine_handlers.push(engine.start(pending, recovered, resolver));
1523
1524 let (mut joiner_latest, mut joiner_monitor) = joiner_reporter.subscribe().await;
1525 while joiner_latest < tip_at_join {
1526 joiner_latest = joiner_monitor.recv().await.expect("event missing");
1527 }
1528
1529 let post_join_target = tip_at_join.saturating_add(ViewDelta::new(5));
1530 while joiner_latest < post_join_target {
1531 joiner_latest = joiner_monitor.recv().await.expect("event missing");
1532 }
1533
1534 for reporter in reporters.iter() {
1535 reporter.assert_no_faults();
1536 reporter.assert_no_invalid();
1537 }
1538
1539 let blocked = oracle.blocked().await.unwrap();
1540 assert!(blocked.is_empty());
1541 });
1542 }
1543
1544 test_for_all_fixtures!(non_genesis_floor_joiner_catches_tip);
1545
1546 #[test_group("slow")]
1547 #[test_traced]
1548 fn test_non_genesis_floor_joiner_catches_tip_stable_leader() {
1549 non_genesis_floor_joiner_catches_tip_with_term::<_, _, RoundRobin>(
1550 RoundRobin::default().with_term(
1551 TermLength::new(NZU32!(3)),
1552 Duration::from_secs(12),
1553 ViewDelta::new(0),
1554 ),
1555 scheme_mocks::fixture,
1556 );
1557 }
1558
1559 fn dishonest_leader_certification_rejected<S, F>(mut fixture: F)
1574 where
1575 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
1576 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
1577 RoundRobin: elector::Config<S>,
1578 {
1579 let n = 5;
1580 let required_containers = View::new(50);
1581 let view_retention = ViewDelta::new(10);
1582 let skip_timeout = Duration::from_secs(12);
1583 let namespace = b"consensus".to_vec();
1584 let executor = deterministic::Runner::timed(Duration::from_secs(300));
1585 executor.start(|mut context| async move {
1586 let Fixture {
1587 participants,
1588 schemes,
1589 ..
1590 } = fixture(&mut context, &namespace, n);
1591 let mut oracle =
1592 start_test_network_with_peers(context.child("network"), participants.clone(), true)
1593 .await;
1594 let mut registrations = register_validators(&mut oracle, &participants).await;
1595
1596 let link = Link {
1597 latency: Duration::from_millis(10),
1598 jitter: Duration::from_millis(1),
1599 success_rate: probability!(1.0),
1600 };
1601 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
1602
1603 let elector = RoundRobin::default();
1604 let participants_set: Set<S::PublicKey> = participants.clone().try_into().unwrap();
1605 let built_elector = elector.clone().build(&participants_set);
1606 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
1607 let mut reporters = Vec::new();
1608 let mut engine_handlers = Vec::new();
1609 let dishonest = Participant::new(0);
1610 for (idx, validator) in participants.iter().enumerate() {
1611 let context = context
1612 .child("validator")
1613 .with_attribute("public_key", validator);
1614 let reporter_config = mocks::reporter::Config {
1615 participants: participants.clone().try_into().unwrap(),
1616 scheme: schemes[idx].clone(),
1617 elector: elector.clone(),
1618 };
1619 let reporter =
1620 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
1621 reporters.push(reporter.clone());
1622
1623 let application_cfg = mocks::application::Config::<Sha256, _> {
1624 relay: relay.clone(),
1625 me: validator.clone(),
1626 propose_latency: (10.0, 5.0),
1627 verify_latency: (10.0, 5.0),
1628 certify_latency: (10.0, 5.0),
1629 should_certify: mocks::application::Certifier::Custom(Box::new({
1630 let built_elector_clone = built_elector.clone();
1631 move |round, _| built_elector_clone.elect(round, None) != dishonest
1632 })),
1633 };
1634 let (actor, application) = mocks::application::Application::new(
1635 context.child("application"),
1636 application_cfg,
1637 );
1638 actor.start();
1639
1640 let blocker = oracle.control(validator.clone());
1641 let cfg = config::Config {
1642 scheme: schemes[idx].clone(),
1643 elector: elector.clone(),
1644 blocker,
1645 automaton: application.clone(),
1646 relay: application.clone(),
1647 reporter: reporter.clone(),
1648 strategy: Sequential,
1649 partition: validator.to_string(),
1650 mailbox_size: NZUsize!(1024),
1651 epoch: Epoch::new(333),
1652 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
1653 Epoch::new(333),
1654 )),
1655 leader_timeout: Duration::from_secs(1),
1656 certification_timeout: Duration::from_secs(2),
1657 timeout_retry: Duration::from_secs(10),
1658 fetch_timeout: Duration::from_secs(1),
1659 view_retention,
1660 skip: SkipPolicy::Enabled {
1661 timeout: skip_timeout,
1662 budget: SkipBudget::Participants,
1663 },
1664 replay_buffer: NZUsize!(1024 * 1024),
1665 write_buffer: NZUsize!(1024 * 1024),
1666 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1667 forward: ForwardPolicy::Disabled,
1668 track_historical_votes: false,
1669 };
1670 let engine = Engine::new(context.child("engine"), cfg);
1671 let (pending, recovered, resolver) = registrations
1672 .remove(validator)
1673 .expect("validator should be registered");
1674 engine_handlers.push(engine.start(pending, recovered, resolver));
1675 }
1676
1677 let mut finalizers = Vec::new();
1678 for reporter in reporters.iter_mut() {
1679 let (mut latest, mut monitor) = reporter.subscribe().await;
1680 finalizers.push(context.child("finalizer").spawn(move |_| async move {
1681 while latest < required_containers {
1682 latest = monitor.recv().await.expect("event missing");
1683 }
1684 }));
1685 }
1686 join_all(finalizers).await;
1687
1688 for reporter in reporters.iter() {
1689 reporter.assert_no_faults();
1690 reporter.assert_no_invalid();
1691 }
1692 });
1693 }
1694
1695 #[test_group("slow")]
1696 #[test_traced]
1697 fn test_dishonest_leader_certification_rejected() {
1698 dishonest_leader_certification_rejected::<_, _>(
1699 bls12381_threshold_vrf::fixture::<MinPk, _>,
1700 );
1701 dishonest_leader_certification_rejected::<_, _>(
1702 bls12381_threshold_vrf::fixture::<MinSig, _>,
1703 );
1704 dishonest_leader_certification_rejected::<_, _>(
1705 bls12381_threshold_std::fixture::<MinPk, _>,
1706 );
1707 dishonest_leader_certification_rejected::<_, _>(
1708 bls12381_threshold_std::fixture::<MinSig, _>,
1709 );
1710 dishonest_leader_certification_rejected::<_, _>(bls12381_multisig::fixture::<MinPk, _>);
1711 dishonest_leader_certification_rejected::<_, _>(bls12381_multisig::fixture::<MinSig, _>);
1712 dishonest_leader_certification_rejected::<_, _>(ed25519::fixture);
1713 dishonest_leader_certification_rejected::<_, _>(secp256r1::fixture);
1714 }
1715
1716 type StableLeaderReporter = mocks::reporter::Reporter<
1718 deterministic::Context,
1719 ed25519::Scheme,
1720 RoundRobin<Sha256>,
1721 Sha256Digest,
1722 >;
1723
1724 async fn setup_stable_leader_cluster(
1736 context: &mut deterministic::Context,
1737 namespace: &[u8],
1738 link: Link,
1739 term_length: TermLength,
1740 optimistic_views: ViewDelta,
1741 propose_latency: (f64, f64),
1742 stall_timeout: Duration,
1743 ) -> (
1744 Vec<StableLeaderReporter>,
1745 usize,
1746 Oracle<PublicKey, deterministic::Context>,
1747 ) {
1748 let epoch = Epoch::new(333);
1749 let Fixture {
1750 participants,
1751 schemes,
1752 ..
1753 } = ed25519::fixture(context, namespace, 5);
1754 let mut oracle =
1755 start_test_network_with_peers(context.child("network"), participants.clone(), true)
1756 .await;
1757 let mut registrations = register_validators(&mut oracle, &participants).await;
1758 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
1759
1760 let elector =
1761 RoundRobin::<Sha256>::default().with_term(term_length, stall_timeout, optimistic_views);
1762 let relay = Arc::new(mocks::relay::Relay::new());
1763 let mut reporters = Vec::new();
1764
1765 for (idx, validator) in participants.iter().enumerate() {
1766 let context = context
1767 .child("validator")
1768 .with_attribute("public_key", validator);
1769 let reporter_config = mocks::reporter::Config {
1770 participants: participants.clone().try_into().unwrap(),
1771 scheme: schemes[idx].clone(),
1772 elector: elector.clone(),
1773 };
1774 let reporter =
1775 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
1776 reporters.push(reporter.clone());
1777
1778 let application_cfg = mocks::application::Config::<Sha256, _> {
1779 relay: relay.clone(),
1780 me: validator.clone(),
1781 propose_latency,
1782 verify_latency: (1.0, 0.0),
1783 certify_latency: (1.0, 0.0),
1784 should_certify: mocks::application::Certifier::Always,
1785 };
1786 let (actor, application) =
1787 mocks::application::Application::new(context.child("application"), application_cfg);
1788 actor.start();
1789
1790 let blocker = oracle.control(validator.clone());
1791 let cfg = config::Config {
1792 scheme: schemes[idx].clone(),
1793 elector: elector.clone(),
1794 blocker,
1795 automaton: application.clone(),
1796 relay: application.clone(),
1797 reporter: reporter.clone(),
1798 strategy: Sequential,
1799 partition: validator.to_string(),
1800 mailbox_size: NZUsize!(1024),
1801 epoch,
1802 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(epoch)),
1803 leader_timeout: Duration::from_millis(1_500),
1804 certification_timeout: Duration::from_millis(3_500),
1805 timeout_retry: Duration::from_secs(10),
1806 fetch_timeout: Duration::from_secs(1),
1807 view_retention: ViewDelta::new(10),
1808 skip: SkipPolicy::Enabled {
1809 timeout: Duration::from_secs(12),
1810 budget: SkipBudget::Participants,
1811 },
1812 replay_buffer: NZUsize!(1024 * 1024),
1813 write_buffer: NZUsize!(1024 * 1024),
1814 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
1815 forward: ForwardPolicy::Disabled,
1816 track_historical_votes: false,
1817 };
1818 let engine = Engine::new(context.child("engine"), cfg);
1819 let (pending, recovered, resolver) = registrations
1820 .remove(validator)
1821 .expect("validator should be registered");
1822 engine.start(pending, recovered, resolver);
1823 }
1824
1825 let participants_set = participants.clone().try_into().unwrap();
1826 let built_elector: elector::RoundRobinElector<ed25519::Scheme> =
1827 elector.build(&participants_set);
1828 let leader_idx = usize::from(built_elector.elect(Round::new(epoch, View::new(1)), None));
1829
1830 (reporters, leader_idx, oracle)
1831 }
1832
1833 #[test_traced]
1834 fn test_stable_leader_optimistic_blocks_faster_than_network_latency() {
1835 let required_containers = View::new(100);
1836 let link_latency = Duration::from_millis(100);
1837 let executor = deterministic::Runner::timed(Duration::from_secs(30));
1838 executor.start(|mut context| async move {
1839 let (reporters, leader_idx, _oracle) = setup_stable_leader_cluster(
1840 &mut context,
1841 b"consensus_stable_leader_high_latency",
1842 Link {
1843 latency: link_latency,
1844 jitter: Duration::from_millis(0),
1845 success_rate: probability!(1.0),
1846 },
1847 TermLength::new(NZU32!(128)),
1848 ViewDelta::new(128),
1849 (10.0, 0.0),
1850 Duration::from_secs(20),
1851 )
1852 .await;
1853
1854 let leader_reporter = reporters[leader_idx].clone();
1855 let start = context.current();
1856 while !leader_reporter
1857 .notarizes
1858 .lock()
1859 .contains_key(&required_containers)
1860 {
1861 context.sleep(Duration::from_millis(10)).await;
1862 }
1863 let elapsed = context.current().duration_since(start).unwrap_or_default();
1864 let average_block_time_s = elapsed.as_secs_f64() / required_containers.get() as f64;
1865 let network_latency_s = link_latency.as_secs_f64();
1866 assert!(
1867 average_block_time_s < network_latency_s,
1868 "expected average optimistic block time ({:.3}ms) to be below network latency ({:.3}ms); elapsed {:?} for {} views",
1869 average_block_time_s * 1000.0,
1870 network_latency_s * 1000.0,
1871 elapsed,
1872 required_containers
1873 );
1874
1875 for reporter in reporters.iter() {
1876 reporter.assert_no_invalid();
1877 }
1878 });
1879 }
1880
1881 #[test_group("slow")]
1882 #[test]
1883 fn test_stable_leader_finalizes_full_term_without_nullification() {
1884 let required_view = View::new(1000);
1885 let executor = deterministic::Runner::timed(Duration::from_secs(40));
1886 executor.start(|mut context| async move {
1887 let (reporters, leader_idx, oracle) = setup_stable_leader_cluster(
1888 &mut context,
1889 b"consensus_stable_leader_full_term_no_nullify",
1890 Link {
1894 latency: Duration::from_millis(1_000),
1895 jitter: Duration::from_millis(1),
1896 success_rate: probability!(1.0),
1897 },
1898 TermLength::new(NZU32!(1000)),
1899 ViewDelta::new(100),
1900 (1.0, 0.0),
1901 Duration::from_secs(6),
1902 )
1903 .await;
1904
1905 let start = context.current();
1906 let deadline = start + Duration::from_secs(25);
1907 while !reporters
1908 .iter()
1909 .all(|reporter| reporter.finalizations.lock().contains_key(&required_view))
1910 {
1911 if context.current() >= deadline {
1912 let progress: Vec<_> = reporters
1913 .iter()
1914 .map(|reporter| {
1915 let finalized = reporter
1916 .finalizations
1917 .lock()
1918 .keys()
1919 .copied()
1920 .max()
1921 .unwrap_or(View::zero());
1922 (finalized, reporter.nullifications.lock().len())
1923 })
1924 .collect();
1925 panic!(
1926 "expected all validators to finalize view {required_view} before {deadline:?}; (max finalized, nullifications) per reporter: {progress:?}",
1927 );
1928 }
1929 context.sleep(Duration::from_millis(10)).await;
1930 }
1931
1932 let leader_reporter = &reporters[leader_idx];
1933 assert!(
1934 leader_reporter.notarizes.lock().contains_key(&required_view),
1935 "stable leader must notarize through full term ending at view {}",
1936 required_view
1937 );
1938 let leader_tip_notarization = leader_reporter
1939 .notarizations
1940 .lock()
1941 .get(&required_view)
1942 .cloned()
1943 .expect("leader reporter missing tip notarization");
1944 assert_eq!(
1945 leader_tip_notarization.proposal.parent,
1946 required_view.previous().unwrap_or(View::zero()),
1947 "unexpected parent for leader tip notarization"
1948 );
1949
1950 for (idx, reporter) in reporters.iter().enumerate() {
1951 reporter.assert_no_invalid();
1952 reporter.assert_no_faults();
1953
1954 assert!(
1955 reporter.nullifies.lock().is_empty(),
1956 "reporter {} observed unexpected nullify votes",
1957 idx
1958 );
1959 assert!(
1960 reporter.nullifications.lock().is_empty(),
1961 "reporter {} observed unexpected nullification certificates",
1962 idx
1963 );
1964
1965 let finalization = reporter
1966 .finalizations
1967 .lock()
1968 .get(&required_view)
1969 .cloned()
1970 .unwrap_or_else(|| panic!("reporter {idx} missing tip finalization"));
1971 assert_eq!(
1972 finalization.proposal.round.view(),
1973 required_view,
1974 "reporter {idx} has mismatched tip finalization round"
1975 );
1976 assert_eq!(
1977 finalization.proposal.parent,
1978 required_view.previous().unwrap_or(View::zero()),
1979 "reporter {idx} has non-chain tip finalization parent"
1980 );
1981 }
1982
1983 let blocked = oracle.blocked().await.unwrap();
1984 assert!(blocked.is_empty());
1985 });
1986 }
1987
1988 fn observer<S, F, L>(mut fixture: F, elector: L)
1989 where
1990 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
1991 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
1992 L: elector::Config<S>,
1993 {
1994 let n_active = 5;
1996 let required_containers = View::new(100);
1997 let view_retention = ViewDelta::new(10);
1998 let skip_timeout = Duration::from_secs(12);
1999 let namespace = b"consensus".to_vec();
2000 let executor = deterministic::Runner::timed(Duration::from_secs(300));
2001 executor.start(|mut context| async move {
2002 let Fixture {
2004 participants,
2005 schemes,
2006 verifier,
2007 ..
2008 } = fixture(&mut context, &namespace, n_active);
2009
2010 let private_key_observer = PrivateKey::from_seed(n_active as u64);
2012 let public_key_observer = private_key_observer.public_key();
2013
2014 let mut oracle = start_test_network_with_split_peers(
2015 context.child("network"),
2016 participants.clone(),
2017 [public_key_observer.clone()],
2018 true,
2019 )
2020 .await;
2021
2022 let mut all_validators = participants.clone();
2024 all_validators.push(public_key_observer.clone());
2025 all_validators.sort();
2026 let mut registrations = register_validators(&mut oracle, &all_validators).await;
2027
2028 let link = Link {
2030 latency: Duration::from_millis(10),
2031 jitter: Duration::from_millis(1),
2032 success_rate: probability!(1.0),
2033 };
2034 link_validators(&mut oracle, &all_validators, Action::Link(link), None).await;
2035
2036 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
2038 let mut reporters = Vec::new();
2039
2040 for (idx, validator) in participants.iter().enumerate() {
2041 let is_observer = *validator == public_key_observer;
2042
2043 let context = context
2045 .child("validator")
2046 .with_attribute("public_key", validator);
2047
2048 let signing = if is_observer {
2050 verifier.clone()
2051 } else {
2052 schemes[idx].clone()
2053 };
2054 let reporter_config = mocks::reporter::Config {
2055 participants: participants.clone().try_into().unwrap(),
2056 scheme: signing.clone(),
2057 elector: elector.clone(),
2058 };
2059 let reporter =
2060 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2061 reporters.push(reporter.clone());
2062 let application_cfg = mocks::application::Config::<Sha256, _> {
2063 relay: relay.clone(),
2064 me: validator.clone(),
2065 propose_latency: (10.0, 5.0),
2066 verify_latency: (10.0, 5.0),
2067 certify_latency: (10.0, 5.0),
2068 should_certify: mocks::application::Certifier::Always,
2069 };
2070 let (actor, application) = mocks::application::Application::new(
2071 context.child("application"),
2072 application_cfg,
2073 );
2074 actor.start();
2075 let blocker = oracle.control(validator.clone());
2076 let cfg = config::Config {
2077 scheme: signing.clone(),
2078 elector: elector.clone(),
2079 blocker,
2080 automaton: application.clone(),
2081 relay: application.clone(),
2082 reporter: reporter.clone(),
2083 strategy: Sequential,
2084 partition: validator.to_string(),
2085 mailbox_size: NZUsize!(1024),
2086 epoch: Epoch::new(333),
2087 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2088 Epoch::new(333),
2089 )),
2090 leader_timeout: Duration::from_secs(1),
2091 certification_timeout: Duration::from_secs(2),
2092 timeout_retry: Duration::from_secs(10),
2093 fetch_timeout: Duration::from_secs(1),
2094 view_retention,
2095 skip: SkipPolicy::Enabled {
2096 timeout: skip_timeout,
2097 budget: SkipBudget::Participants,
2098 },
2099 replay_buffer: NZUsize!(1024 * 1024),
2100 write_buffer: NZUsize!(1024 * 1024),
2101 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2102 forward: ForwardPolicy::Disabled,
2103 track_historical_votes: false,
2104 };
2105 let engine = Engine::new(context.child("engine"), cfg);
2106
2107 let (pending, recovered, resolver) = registrations
2109 .remove(validator)
2110 .expect("validator should be registered");
2111 engine.start(pending, recovered, resolver);
2112 }
2113
2114 let mut finalizers = Vec::new();
2116 for reporter in reporters.iter_mut() {
2117 let (mut latest, mut monitor) = reporter.subscribe().await;
2118 finalizers.push(context.child("finalizer").spawn(move |_| async move {
2119 while latest < required_containers {
2120 latest = monitor.recv().await.expect("event missing");
2121 }
2122 }));
2123 }
2124 join_all(finalizers).await;
2125
2126 for reporter in reporters.iter() {
2129 reporter.assert_no_faults();
2131 reporter.assert_no_invalid();
2132
2133 let blocked = oracle.blocked().await.unwrap();
2135 assert!(blocked.is_empty());
2136 }
2137 });
2138 }
2139
2140 test_for_all_fixtures!(observer);
2141
2142 fn unclean_shutdown<S, F, L>(fixture: F, elector: L)
2143 where
2144 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2145 F: FnMut(&mut TestRng, &[u8], u32) -> Fixture<S>,
2146 L: elector::Config<S>,
2147 {
2148 unclean_shutdown_with_term::<S, F, L>(elector, fixture);
2149 }
2150
2151 fn unclean_shutdown_with_term<S, F, L>(elector: L, mut fixture: F)
2152 where
2153 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2154 F: FnMut(&mut TestRng, &[u8], u32) -> Fixture<S>,
2155 L: elector::Config<S>,
2156 {
2157 let n = 5;
2159 let required_containers = View::new(100);
2160 let view_retention = ViewDelta::new(10);
2161 let skip_timeout = Duration::from_secs(12);
2162 let namespace = b"consensus".to_vec();
2163
2164 let shutdowns: Arc<Mutex<u64>> = Arc::new(Mutex::new(0));
2166 let supervised = Arc::new(Mutex::new(Vec::new()));
2167 let mut prev_checkpoint = None;
2168
2169 let mut rng = test_rng();
2171 let Fixture {
2172 participants,
2173 schemes,
2174 ..
2175 } = fixture(&mut rng, &namespace, n);
2176 let reporter_seed: [u8; 32] = rng.random();
2177
2178 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, S::PublicKey>::new());
2180
2181 loop {
2182 let participants = participants.clone();
2183 let schemes = schemes.clone();
2184 let shutdowns = shutdowns.clone();
2185 let supervised = supervised.clone();
2186 let relay = relay.clone();
2187 let elector = elector.clone();
2188 relay.deregister_all(); let f = |mut context: deterministic::Context| async move {
2191 let mut oracle = start_test_network_with_peers(
2193 context.child("network"),
2194 participants.clone(),
2195 true,
2196 )
2197 .await;
2198 let mut registrations = register_validators(&mut oracle, &participants).await;
2199
2200 let link = Link {
2202 latency: Duration::from_millis(50),
2203 jitter: Duration::from_millis(50),
2204 success_rate: probability!(1.0),
2205 };
2206 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
2207
2208 let elector = elector.clone();
2210 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
2211 let mut reporters = HashMap::new();
2212 let mut engine_handlers = Vec::new();
2213 for (idx, validator) in participants.iter().enumerate() {
2214 let context = context
2216 .child("validator")
2217 .with_attribute("public_key", validator);
2218
2219 let reporter_config = mocks::reporter::Config {
2221 participants: participants.clone().try_into().unwrap(),
2222 scheme: schemes[idx].clone(),
2223 elector: elector.clone(),
2224 };
2225 let reporter_rng = StdRng::from_seed(reporter_seed);
2226 let reporter = mocks::reporter::Reporter::new(reporter_rng, reporter_config);
2227 reporters.insert(validator.clone(), reporter.clone());
2228 let application_cfg = mocks::application::Config::<Sha256, _> {
2229 relay: relay.clone(),
2230 me: validator.clone(),
2231 propose_latency: (10.0, 5.0),
2232 verify_latency: (10.0, 5.0),
2233 certify_latency: (10.0, 5.0),
2234 should_certify: mocks::application::Certifier::Always,
2235 };
2236 let (actor, application) = mocks::application::Application::new(
2237 context.child("application"),
2238 application_cfg,
2239 );
2240 actor.start();
2241 let blocker = oracle.control(validator.clone());
2242 let cfg = config::Config {
2243 scheme: schemes[idx].clone(),
2244 elector: elector.clone(),
2245 blocker,
2246 automaton: application.clone(),
2247 relay: application.clone(),
2248 reporter: reporter.clone(),
2249 strategy: Sequential,
2250 partition: validator.to_string(),
2251 mailbox_size: NZUsize!(1024),
2252 epoch: Epoch::new(333),
2253 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2254 Epoch::new(333),
2255 )),
2256 leader_timeout: Duration::from_millis(500),
2259 certification_timeout: Duration::from_secs(1),
2260 timeout_retry: Duration::from_millis(500),
2261 fetch_timeout: Duration::from_secs(1),
2262 view_retention,
2263 skip: SkipPolicy::Enabled {
2264 timeout: skip_timeout,
2265 budget: SkipBudget::Participants,
2266 },
2267 replay_buffer: NZUsize!(1024 * 1024),
2268 write_buffer: NZUsize!(1024 * 1024),
2269 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2270 forward: ForwardPolicy::Disabled,
2271 track_historical_votes: false,
2272 };
2273 let engine = Engine::new(context.child("engine"), cfg);
2274
2275 let (pending, recovered, resolver) = registrations
2277 .remove(validator)
2278 .expect("validator should be registered");
2279 engine_handlers.push(engine.start(pending, recovered, resolver));
2280 }
2281
2282 let mut finalizers = Vec::new();
2284 for reporter in reporters.values_mut() {
2285 let (mut latest, mut monitor) = reporter.subscribe().await;
2286 finalizers.push(context.child("finalizer").spawn(move |_| async move {
2287 while latest < required_containers {
2288 latest = monitor.recv().await.expect("event missing");
2289 }
2290 }));
2291 }
2292
2293 let wait =
2295 context.random_range(Duration::from_millis(100)..Duration::from_millis(2_000));
2296 let result = select! {
2297 _ = context.sleep(wait) => {
2298 {
2300 let mut shutdowns = shutdowns.lock();
2301 debug!(shutdowns = *shutdowns, elapsed = ?wait, "restarting");
2302 *shutdowns += 1;
2303 }
2304 supervised.lock().push(reporters);
2305 false
2306 },
2307 _ = join_all(finalizers) => {
2308 let supervised = supervised.lock();
2310 for reporters in supervised.iter() {
2311 for reporter in reporters.values() {
2312 reporter.assert_no_faults();
2313 }
2314 }
2315 true
2316 },
2317 };
2318
2319 let blocked = oracle.blocked().await.unwrap();
2321 assert!(blocked.is_empty());
2322
2323 result
2324 };
2325
2326 let (complete, checkpoint) = prev_checkpoint
2327 .map_or_else(
2328 || deterministic::Runner::timed(Duration::from_secs(180)),
2329 deterministic::Runner::from,
2330 )
2331 .start_and_recover(f);
2332
2333 if complete {
2335 break;
2336 }
2337
2338 prev_checkpoint = Some(checkpoint);
2339 }
2340 }
2341
2342 test_for_all_fixtures!(unclean_shutdown);
2343
2344 #[test_group("slow")]
2352 #[test_traced]
2353 fn test_unclean_shutdown_stable_leader_optimistic() {
2354 unclean_shutdown_with_term::<_, _, RoundRobin>(
2355 RoundRobin::default().with_term(
2356 TermLength::new(NZU32!(5)),
2357 Duration::from_secs(13),
2358 ViewDelta::new(2),
2359 ),
2360 ed25519::fixture,
2361 );
2362 }
2363
2364 fn backfill<S, F, L>(mut fixture: F, elector: L)
2365 where
2366 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2367 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
2368 L: elector::Config<S>,
2369 {
2370 let n = 4;
2372 let required_containers = View::new(100);
2373 let view_retention = ViewDelta::new(10);
2374 let skip_timeout = Duration::from_secs(11);
2375 let namespace = b"consensus".to_vec();
2376 let executor = deterministic::Runner::timed(Duration::from_secs(240));
2377 executor.start(|mut context| async move {
2378 let Fixture {
2380 participants,
2381 schemes,
2382 ..
2383 } = fixture(&mut context, &namespace, n);
2384 let mut oracle =
2385 start_test_network_with_peers(context.child("network"), participants.clone(), true)
2386 .await;
2387 let mut registrations = register_validators(&mut oracle, &participants).await;
2388
2389 let link = Link {
2391 latency: Duration::from_millis(10),
2392 jitter: Duration::from_millis(1),
2393 success_rate: probability!(1.0),
2394 };
2395 link_validators(
2396 &mut oracle,
2397 &participants,
2398 Action::Link(link),
2399 Some(|_, i, j| ![i, j].contains(&0usize)),
2400 )
2401 .await;
2402
2403 let elector = elector.clone();
2405 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
2406 let mut reporters = Vec::new();
2407 let mut engine_handlers = Vec::new();
2408 for (idx_scheme, validator) in participants.iter().enumerate() {
2409 if idx_scheme == 0 {
2411 continue;
2412 }
2413
2414 let context = context
2416 .child("validator")
2417 .with_attribute("public_key", validator);
2418
2419 let reporter_config = mocks::reporter::Config {
2421 participants: participants.clone().try_into().unwrap(),
2422 scheme: schemes[idx_scheme].clone(),
2423 elector: elector.clone(),
2424 };
2425 let reporter =
2426 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2427 reporters.push(reporter.clone());
2428 let application_cfg = mocks::application::Config::<Sha256, _> {
2429 relay: relay.clone(),
2430 me: validator.clone(),
2431 propose_latency: (10.0, 5.0),
2432 verify_latency: (10.0, 5.0),
2433 certify_latency: (10.0, 5.0),
2434 should_certify: mocks::application::Certifier::Always,
2435 };
2436 let (actor, application) = mocks::application::Application::new(
2437 context.child("application"),
2438 application_cfg,
2439 );
2440 actor.start();
2441 let blocker = oracle.control(validator.clone());
2442 let cfg = config::Config {
2443 scheme: schemes[idx_scheme].clone(),
2444 elector: elector.clone(),
2445 blocker,
2446 automaton: application.clone(),
2447 relay: application.clone(),
2448 reporter: reporter.clone(),
2449 strategy: Sequential,
2450 partition: validator.to_string(),
2451 mailbox_size: NZUsize!(1024),
2452 epoch: Epoch::new(333),
2453 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2454 Epoch::new(333),
2455 )),
2456 leader_timeout: Duration::from_secs(1),
2457 certification_timeout: Duration::from_secs(2),
2458 timeout_retry: Duration::from_secs(10),
2459 fetch_timeout: Duration::from_secs(1),
2460 view_retention,
2461 skip: SkipPolicy::Enabled {
2462 timeout: skip_timeout,
2463 budget: SkipBudget::Participants,
2464 },
2465 replay_buffer: NZUsize!(1024 * 1024),
2466 write_buffer: NZUsize!(1024 * 1024),
2467 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2468 forward: ForwardPolicy::Disabled,
2469 track_historical_votes: false,
2470 };
2471 let engine = Engine::new(context.child("engine"), cfg);
2472
2473 let (pending, recovered, resolver) = registrations
2475 .remove(validator)
2476 .expect("validator should be registered");
2477 engine_handlers.push(engine.start(pending, recovered, resolver));
2478 }
2479
2480 let mut finalizers = Vec::new();
2482 for reporter in reporters.iter_mut() {
2483 let (mut latest, mut monitor) = reporter.subscribe().await;
2484 finalizers.push(context.child("finalizer").spawn(move |_| async move {
2485 while latest < required_containers {
2486 latest = monitor.recv().await.expect("event missing");
2487 }
2488 }));
2489 }
2490 join_all(finalizers).await;
2491
2492 let link = Link {
2494 latency: Duration::from_secs(3),
2495 jitter: Duration::from_millis(0),
2496 success_rate: probability!(1.0),
2497 };
2498 link_validators(
2499 &mut oracle,
2500 &participants,
2501 Action::Update(link.clone()),
2502 Some(|_, i, j| ![i, j].contains(&0usize)),
2503 )
2504 .await;
2505
2506 context.sleep(Duration::from_secs(60)).await;
2508
2509 link_validators(
2511 &mut oracle,
2512 &participants,
2513 Action::Unlink,
2514 Some(|_, i, j| [i, j].contains(&1usize) && ![i, j].contains(&0usize)),
2515 )
2516 .await;
2517
2518 let me = participants[0].clone();
2520 let context = context.child("validator").with_attribute("public_key", &me);
2521
2522 link_validators(
2524 &mut oracle,
2525 &participants,
2526 Action::Link(link),
2527 Some(|_, i, j| [i, j].contains(&0usize) && ![i, j].contains(&1usize)),
2528 )
2529 .await;
2530
2531 let link = Link {
2533 latency: Duration::from_millis(10),
2534 jitter: Duration::from_millis(3),
2535 success_rate: probability!(1.0),
2536 };
2537 link_validators(
2538 &mut oracle,
2539 &participants,
2540 Action::Update(link),
2541 Some(|_, i, j| ![i, j].contains(&1usize)),
2542 )
2543 .await;
2544
2545 let reporter_config = mocks::reporter::Config {
2547 participants: participants.clone().try_into().unwrap(),
2548 scheme: schemes[0].clone(),
2549 elector: elector.clone(),
2550 };
2551 let mut reporter =
2552 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2553 reporters.push(reporter.clone());
2554 let application_cfg = mocks::application::Config::<Sha256, _> {
2555 relay: relay.clone(),
2556 me: me.clone(),
2557 propose_latency: (10.0, 5.0),
2558 verify_latency: (10.0, 5.0),
2559 certify_latency: (10.0, 5.0),
2560 should_certify: mocks::application::Certifier::Always,
2561 };
2562 let (actor, application) =
2563 mocks::application::Application::new(context.child("application"), application_cfg);
2564 actor.start();
2565 let blocker = oracle.control(me.clone());
2566 let cfg = config::Config {
2567 scheme: schemes[0].clone(),
2568 elector: elector.clone(),
2569 blocker,
2570 automaton: application.clone(),
2571 relay: application.clone(),
2572 reporter: reporter.clone(),
2573 strategy: Sequential,
2574 partition: me.to_string(),
2575 mailbox_size: NZUsize!(1024),
2576 epoch: Epoch::new(333),
2577 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(Epoch::new(
2578 333,
2579 ))),
2580 leader_timeout: Duration::from_secs(1),
2581 certification_timeout: Duration::from_secs(2),
2582 timeout_retry: Duration::from_secs(10),
2583 fetch_timeout: Duration::from_secs(1),
2584 view_retention,
2585 skip: SkipPolicy::Enabled {
2586 timeout: skip_timeout,
2587 budget: SkipBudget::Participants,
2588 },
2589 replay_buffer: NZUsize!(1024 * 1024),
2590 write_buffer: NZUsize!(1024 * 1024),
2591 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2592 forward: ForwardPolicy::Disabled,
2593 track_historical_votes: false,
2594 };
2595 let engine = Engine::new(context.child("engine"), cfg);
2596
2597 let (pending, recovered, resolver) = registrations
2599 .remove(&me)
2600 .expect("validator should be registered");
2601 engine_handlers.push(engine.start(pending, recovered, resolver));
2602
2603 let (mut latest, mut monitor) = reporter.subscribe().await;
2605 while latest < required_containers {
2606 latest = monitor.recv().await.expect("event missing");
2607 }
2608
2609 let blocked = oracle.blocked().await.unwrap();
2611 assert!(blocked.is_empty());
2612 });
2613 }
2614
2615 test_for_all_fixtures!(backfill);
2616
2617 #[test_group("slow")]
2618 #[test_traced]
2619 fn test_backfill_stable_leader_optimistic() {
2620 backfill::<_, _, RoundRobin>(
2621 ed25519::fixture,
2622 RoundRobin::default().with_term(
2625 TermLength::new(NZU32!(5)),
2626 Duration::from_secs(51),
2627 ViewDelta::new(2),
2628 ),
2629 );
2630 }
2631
2632 fn one_offline<S, F, L>(fixture: F, elector: L)
2633 where
2634 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2635 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
2636 L: elector::Config<S>,
2637 {
2638 one_offline_with_term::<S, F, L>(elector, fixture);
2639 }
2640
2641 fn one_offline_with_term<S, F, L>(elector: L, mut fixture: F)
2642 where
2643 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2644 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
2645 L: elector::Config<S>,
2646 {
2647 let n = 5;
2649 let quorum = quorum(n) as usize;
2650 let required_containers = View::new(100);
2651 let view_retention = ViewDelta::new(10);
2652 let skip_timeout = Duration::from_secs(11);
2653 let max_exceptions = 10;
2654 let namespace = b"consensus".to_vec();
2655 let executor = deterministic::Runner::timed(Duration::from_secs(300));
2656 executor.start(|mut context| async move {
2657 let Fixture {
2659 participants,
2660 schemes,
2661 ..
2662 } = fixture(&mut context, &namespace, n);
2663 let mut oracle =
2664 start_test_network_with_peers(context.child("network"), participants.clone(), true)
2665 .await;
2666 let mut registrations = register_validators(&mut oracle, &participants).await;
2667
2668 let link = Link {
2670 latency: Duration::from_millis(10),
2671 jitter: Duration::from_millis(1),
2672 success_rate: probability!(1.0),
2673 };
2674 link_validators(
2675 &mut oracle,
2676 &participants,
2677 Action::Link(link),
2678 Some(|_, i, j| ![i, j].contains(&0usize)),
2679 )
2680 .await;
2681
2682 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
2684 let mut reporters = Vec::new();
2685 let mut engine_handlers = Vec::new();
2686 for (idx_scheme, validator) in participants.iter().enumerate() {
2687 if idx_scheme == 0 {
2689 continue;
2690 }
2691
2692 let context = context
2694 .child("validator")
2695 .with_attribute("public_key", validator);
2696
2697 let reporter_config = mocks::reporter::Config {
2699 participants: participants.clone().try_into().unwrap(),
2700 scheme: schemes[idx_scheme].clone(),
2701 elector: elector.clone(),
2702 };
2703 let reporter =
2704 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2705 reporters.push(reporter.clone());
2706 let application_cfg = mocks::application::Config::<Sha256, _> {
2707 relay: relay.clone(),
2708 me: validator.clone(),
2709 propose_latency: (10.0, 5.0),
2710 verify_latency: (10.0, 5.0),
2711 certify_latency: (10.0, 5.0),
2712 should_certify: mocks::application::Certifier::Always,
2713 };
2714 let (actor, application) = mocks::application::Application::new(
2715 context.child("application"),
2716 application_cfg,
2717 );
2718 actor.start();
2719 let blocker = oracle.control(validator.clone());
2720 let cfg = config::Config {
2721 scheme: schemes[idx_scheme].clone(),
2722 elector: elector.clone(),
2723 blocker,
2724 automaton: application.clone(),
2725 relay: application.clone(),
2726 reporter: reporter.clone(),
2727 strategy: Sequential,
2728 partition: validator.to_string(),
2729 mailbox_size: NZUsize!(1024),
2730 epoch: Epoch::new(333),
2731 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2732 Epoch::new(333),
2733 )),
2734 leader_timeout: Duration::from_secs(1),
2735 certification_timeout: Duration::from_secs(2),
2736 timeout_retry: Duration::from_secs(10),
2737 fetch_timeout: Duration::from_secs(1),
2738 view_retention,
2739 skip: SkipPolicy::Enabled {
2740 timeout: skip_timeout,
2741 budget: SkipBudget::Participants,
2742 },
2743 replay_buffer: NZUsize!(1024 * 1024),
2744 write_buffer: NZUsize!(1024 * 1024),
2745 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2746 forward: ForwardPolicy::Disabled,
2747 track_historical_votes: false,
2748 };
2749 let engine = Engine::new(context.child("engine"), cfg);
2750
2751 let (pending, recovered, resolver) = registrations
2753 .remove(validator)
2754 .expect("validator should be registered");
2755 engine_handlers.push(engine.start(pending, recovered, resolver));
2756 }
2757
2758 let mut finalizers = Vec::new();
2760 for reporter in reporters.iter_mut() {
2761 let (mut latest, mut monitor) = reporter.subscribe().await;
2762 finalizers.push(context.child("finalizer").spawn(move |_| async move {
2763 while latest < required_containers {
2764 latest = monitor.recv().await.expect("event missing");
2765 }
2766 }));
2767 }
2768 join_all(finalizers).await;
2769
2770 let exceptions = 0;
2772 let offline = &participants[0];
2773 for reporter in reporters.iter() {
2774 reporter.assert_no_faults();
2776
2777 reporter.assert_no_invalid();
2779
2780 let mut exceptions = 0;
2782 {
2783 let notarizes = reporter.notarizes.lock();
2784 for (view, payloads) in notarizes.iter() {
2785 for participants in payloads.values() {
2786 if participants.contains(offline) {
2787 panic!("view: {view}");
2788 }
2789 }
2790 }
2791 }
2792 {
2793 let nullifies = reporter.nullifies.lock();
2794 for (view, participants) in nullifies.iter() {
2795 if participants.contains(offline) {
2796 panic!("view: {view}");
2797 }
2798 }
2799 }
2800 {
2801 let finalizes = reporter.finalizes.lock();
2802 for (view, payloads) in finalizes.iter() {
2803 for finalizers in payloads.values() {
2804 if finalizers.contains(offline) {
2805 panic!("view: {view}");
2806 }
2807 }
2808 }
2809 }
2810
2811 let mut offline_views = Vec::new();
2813 {
2814 let leaders = reporter.leaders.lock();
2815 for (view, leader) in leaders.iter() {
2816 if leader == offline {
2817 offline_views.push(*view);
2818 }
2819 }
2820 }
2821 assert!(!offline_views.is_empty());
2822
2823 {
2825 let nullifies = reporter.nullifies.lock();
2826 for view in offline_views.iter() {
2827 let nullifies = nullifies.get(view).map_or(0, |n| n.len());
2828 if nullifies < quorum {
2829 warn!("missing expected view nullifies: {}", view);
2830 exceptions += 1;
2831 }
2832 }
2833 }
2834 {
2835 let nullifications = reporter.nullifications.lock();
2836 for view in offline_views.iter() {
2837 if !nullifications.contains_key(view) {
2838 warn!("missing expected view nullifies: {}", view);
2839 exceptions += 1;
2840 }
2841 }
2842 }
2843
2844 assert!(exceptions <= max_exceptions);
2846 }
2847 assert!(exceptions <= max_exceptions);
2848
2849 let blocked = oracle.blocked().await.unwrap();
2851 assert!(blocked.is_empty());
2852
2853 let encoded = context.encode();
2855 let leader_label = format!("leader=\"{}\"", offline);
2856 assert!(
2857 count_nonzero_metric_lines(&encoded, &["_timeouts", &leader_label]) >= n - 1,
2858 "expected timeout metrics for offline leader"
2859 );
2860 assert_eq!(
2861 count_nonzero_metric_lines(&encoded, &["_nullifications", &leader_label]),
2862 n - 1,
2863 "expected all online nodes to record _nullifications for offline leader"
2864 );
2865 });
2866 }
2867
2868 test_for_all_fixtures!(one_offline);
2869
2870 #[test_group("slow")]
2871 #[test_traced]
2872 fn test_one_offline_stable_leader() {
2873 one_offline_with_term::<_, _, RoundRobin>(
2874 RoundRobin::default().with_term(
2875 TermLength::new(NZU32!(3)),
2876 Duration::from_secs(12),
2877 ViewDelta::new(0),
2878 ),
2879 scheme_mocks::fixture,
2880 );
2881 }
2882
2883 fn slow_validator<S, F, L>(mut fixture: F, elector: L)
2884 where
2885 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
2886 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
2887 L: elector::Config<S>,
2888 {
2889 let n = 5;
2891 let required_containers = View::new(50);
2892 let view_retention = ViewDelta::new(10);
2893 let skip_timeout = Duration::from_secs(11);
2894 let namespace = b"consensus".to_vec();
2895 let executor = deterministic::Runner::timed(Duration::from_secs(300));
2896 executor.start(|mut context| async move {
2897 let Fixture {
2899 participants,
2900 schemes,
2901 ..
2902 } = fixture(&mut context, &namespace, n);
2903 let mut oracle =
2904 start_test_network_with_peers(context.child("network"), participants.clone(), true)
2905 .await;
2906 let mut registrations = register_validators(&mut oracle, &participants).await;
2907
2908 let link = Link {
2910 latency: Duration::from_millis(10),
2911 jitter: Duration::from_millis(1),
2912 success_rate: probability!(1.0),
2913 };
2914 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
2915
2916 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
2918 let mut reporters = Vec::new();
2919 let mut engine_handlers = Vec::new();
2920 for (idx_scheme, validator) in participants.iter().enumerate() {
2921 let context = context
2923 .child("validator")
2924 .with_attribute("public_key", validator);
2925
2926 let reporter_config = mocks::reporter::Config {
2928 participants: participants.clone().try_into().unwrap(),
2929 scheme: schemes[idx_scheme].clone(),
2930 elector: elector.clone(),
2931 };
2932 let reporter =
2933 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
2934 reporters.push(reporter.clone());
2935 let application_cfg = if idx_scheme == 0 {
2936 mocks::application::Config::<Sha256, _> {
2937 relay: relay.clone(),
2938 me: validator.clone(),
2939 propose_latency: (10_000.0, 0.0),
2940 verify_latency: (10_000.0, 5.0),
2941 certify_latency: (10_000.0, 5.0),
2942 should_certify: mocks::application::Certifier::Always,
2943 }
2944 } else {
2945 mocks::application::Config::<Sha256, _> {
2946 relay: relay.clone(),
2947 me: validator.clone(),
2948 propose_latency: (10.0, 5.0),
2949 verify_latency: (10.0, 5.0),
2950 certify_latency: (10.0, 5.0),
2951 should_certify: mocks::application::Certifier::Always,
2952 }
2953 };
2954 let (actor, application) = mocks::application::Application::new(
2955 context.child("application"),
2956 application_cfg,
2957 );
2958 actor.start();
2959 let blocker = oracle.control(validator.clone());
2960 let cfg = config::Config {
2961 scheme: schemes[idx_scheme].clone(),
2962 elector: elector.clone(),
2963 blocker,
2964 automaton: application.clone(),
2965 relay: application.clone(),
2966 reporter: reporter.clone(),
2967 strategy: Sequential,
2968 partition: validator.to_string(),
2969 mailbox_size: NZUsize!(1024),
2970 epoch: Epoch::new(333),
2971 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
2972 Epoch::new(333),
2973 )),
2974 leader_timeout: Duration::from_secs(1),
2975 certification_timeout: Duration::from_secs(2),
2976 timeout_retry: Duration::from_secs(10),
2977 fetch_timeout: Duration::from_secs(1),
2978 view_retention,
2979 skip: SkipPolicy::Enabled {
2980 timeout: skip_timeout,
2981 budget: SkipBudget::Participants,
2982 },
2983 replay_buffer: NZUsize!(1024 * 1024),
2984 write_buffer: NZUsize!(1024 * 1024),
2985 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
2986 forward: ForwardPolicy::Disabled,
2987 track_historical_votes: false,
2988 };
2989 let engine = Engine::new(context.child("engine"), cfg);
2990
2991 let (pending, recovered, resolver) = registrations
2993 .remove(validator)
2994 .expect("validator should be registered");
2995 engine_handlers.push(engine.start(pending, recovered, resolver));
2996 }
2997
2998 let mut finalizers = Vec::new();
3000 for reporter in reporters.iter_mut() {
3001 let (mut latest, mut monitor) = reporter.subscribe().await;
3002 finalizers.push(context.child("finalizer").spawn(move |_| async move {
3003 while latest < required_containers {
3004 latest = monitor.recv().await.expect("event missing");
3005 }
3006 }));
3007 }
3008 join_all(finalizers).await;
3009
3010 let slow = &participants[0];
3012 for reporter in reporters.iter() {
3013 reporter.assert_no_faults();
3015
3016 reporter.assert_no_invalid();
3018
3019 {
3022 let notarizes = reporter.notarizes.lock();
3023 assert!(notarizes.values().all(|payloads| {
3024 payloads
3025 .values()
3026 .all(|participants| !participants.contains(slow))
3027 }));
3028 }
3029 {
3030 let finalizes = reporter.finalizes.lock();
3031 assert!(finalizes.values().all(|payloads| {
3032 payloads
3033 .values()
3034 .all(|participants| !participants.contains(slow))
3035 }));
3036 }
3037
3038 {
3040 let finalizations = reporter.finalizations.lock();
3041 assert!(
3042 finalizations
3043 .keys()
3044 .any(|view| *view >= required_containers)
3045 );
3046 }
3047 }
3048
3049 let blocked = oracle.blocked().await.unwrap();
3051 assert!(blocked.is_empty());
3052 });
3053 }
3054
3055 test_for_all_fixtures!(slow_validator);
3056
3057 fn all_recovery<S, F, L>(mut fixture: F, elector: L)
3058 where
3059 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3060 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3061 L: elector::Config<S>,
3062 {
3063 let n = 5;
3065 let required_containers = View::new(100);
3066 let view_retention = ViewDelta::new(10);
3067 let skip_timeout = Duration::from_secs(11);
3068 let namespace = b"consensus".to_vec();
3069 let executor = deterministic::Runner::timed(Duration::from_secs(1800));
3070 executor.start(|mut context| async move {
3071 let Fixture {
3073 participants,
3074 schemes,
3075 ..
3076 } = fixture(&mut context, &namespace, n);
3077 let mut oracle =
3078 start_test_network_with_peers(context.child("network"), participants.clone(), true)
3079 .await;
3080 let mut registrations = register_validators(&mut oracle, &participants).await;
3081
3082 let link = Link {
3084 latency: Duration::from_secs(3),
3085 jitter: Duration::from_millis(0),
3086 success_rate: probability!(1.0),
3087 };
3088 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
3089
3090 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
3092 let mut reporters = Vec::new();
3093 let mut engine_handlers = Vec::new();
3094 for (idx, validator) in participants.iter().enumerate() {
3095 let context = context
3097 .child("validator")
3098 .with_attribute("public_key", validator);
3099
3100 let reporter_config = mocks::reporter::Config {
3102 participants: participants.clone().try_into().unwrap(),
3103 scheme: schemes[idx].clone(),
3104 elector: elector.clone(),
3105 };
3106 let reporter =
3107 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3108 reporters.push(reporter.clone());
3109 let application_cfg = mocks::application::Config::<Sha256, _> {
3110 relay: relay.clone(),
3111 me: validator.clone(),
3112 propose_latency: (10.0, 5.0),
3113 verify_latency: (10.0, 5.0),
3114 certify_latency: (10.0, 5.0),
3115 should_certify: mocks::application::Certifier::Always,
3116 };
3117 let (actor, application) = mocks::application::Application::new(
3118 context.child("application"),
3119 application_cfg,
3120 );
3121 actor.start();
3122 let blocker = oracle.control(validator.clone());
3123 let cfg = config::Config {
3124 scheme: schemes[idx].clone(),
3125 elector: elector.clone(),
3126 blocker,
3127 automaton: application.clone(),
3128 relay: application.clone(),
3129 reporter: reporter.clone(),
3130 strategy: Sequential,
3131 partition: validator.to_string(),
3132 mailbox_size: NZUsize!(1024),
3133 epoch: Epoch::new(333),
3134 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3135 Epoch::new(333),
3136 )),
3137 leader_timeout: Duration::from_secs(1),
3138 certification_timeout: Duration::from_secs(2),
3139 timeout_retry: Duration::from_secs(10),
3140 fetch_timeout: Duration::from_secs(1),
3141 view_retention,
3142 skip: SkipPolicy::Enabled {
3143 timeout: skip_timeout,
3144 budget: SkipBudget::Participants,
3145 },
3146 replay_buffer: NZUsize!(1024 * 1024),
3147 write_buffer: NZUsize!(1024 * 1024),
3148 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3149 forward: ForwardPolicy::Disabled,
3150 track_historical_votes: false,
3151 };
3152 let engine = Engine::new(context.child("engine"), cfg);
3153
3154 let (pending, recovered, resolver) = registrations
3156 .remove(validator)
3157 .expect("validator should be registered");
3158 engine_handlers.push(engine.start(pending, recovered, resolver));
3159 }
3160
3161 let mut finalizers = Vec::new();
3163 for reporter in reporters.iter_mut() {
3164 let (_, mut monitor) = reporter.subscribe().await;
3165 finalizers.push(context.child("finalizer").spawn(move |context| async move {
3166 select! {
3167 _timeout = context.sleep(Duration::from_secs(60)) => {},
3168 _done = monitor.recv() => {
3169 panic!("engine should not notarize or finalize anything");
3170 },
3171 }
3172 }));
3173 }
3174 join_all(finalizers).await;
3175
3176 link_validators(&mut oracle, &participants, Action::Unlink, None).await;
3178
3179 context.sleep(Duration::from_secs(60)).await;
3181
3182 let mut latest = View::zero();
3184 for reporter in reporters.iter() {
3185 let nullifies = reporter.nullifies.lock();
3186 let max = nullifies.keys().max().unwrap();
3187 if *max > latest {
3188 latest = *max;
3189 }
3190 }
3191
3192 let link = Link {
3194 latency: Duration::from_millis(10),
3195 jitter: Duration::from_millis(1),
3196 success_rate: probability!(1.0),
3197 };
3198 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
3199
3200 let mut finalizers = Vec::new();
3202 for reporter in reporters.iter_mut() {
3203 let (mut latest, mut monitor) = reporter.subscribe().await;
3204 finalizers.push(context.child("finalizer").spawn(move |_| async move {
3205 while latest < required_containers {
3206 latest = monitor.recv().await.expect("event missing");
3207 }
3208 }));
3209 }
3210 join_all(finalizers).await;
3211
3212 for reporter in reporters.iter() {
3214 reporter.assert_no_faults();
3216
3217 reporter.assert_no_invalid();
3219
3220 {
3225 let mut found = 0;
3229 let notarizations = reporter.notarizations.lock();
3230 for view in View::range(latest, latest.saturating_add(view_retention)) {
3231 if notarizations.contains_key(&view) {
3232 found += 1;
3233 }
3234 }
3235 let tolerated_missing = 3;
3239 assert!(
3240 found >= view_retention.get().saturating_sub(tolerated_missing),
3241 "found: {found}"
3242 );
3243 }
3244 }
3245
3246 let blocked = oracle.blocked().await.unwrap();
3248 assert!(blocked.is_empty());
3249 });
3250 }
3251
3252 test_for_all_fixtures!(all_recovery);
3253
3254 fn all_crash_after_nullify<S, F, L>(mut fixture: F, elector: L)
3255 where
3256 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3257 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3258 L: elector::Config<S>,
3259 {
3260 let n = 4;
3262 let required_containers = View::new(10);
3263 let view_retention = ViewDelta::new(10);
3264 let skip_timeout = Duration::from_secs(11);
3265 let namespace = b"consensus".to_vec();
3266 let executor = deterministic::Runner::timed(Duration::from_secs(3600));
3267 executor.start(|mut context| async move {
3268 let Fixture {
3270 participants,
3271 schemes,
3272 ..
3273 } = fixture(&mut context, &namespace, n);
3274 let mut oracle =
3275 start_test_network_with_peers(context.child("network"), participants.clone(), true)
3276 .await;
3277 let mut registrations = register_validators(&mut oracle, &participants).await;
3278
3279 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
3282 let mut reporters = Vec::new();
3283 let mut engine_handlers = Vec::new();
3284 for (idx_scheme, validator) in participants.iter().enumerate() {
3285 if idx_scheme == 0 {
3287 continue;
3288 }
3289
3290 let context = context
3292 .child("validator")
3293 .with_attribute("public_key", validator);
3294
3295 let reporter_config = mocks::reporter::Config {
3297 participants: participants.clone().try_into().unwrap(),
3298 scheme: schemes[idx_scheme].clone(),
3299 elector: elector.clone(),
3300 };
3301 let reporter =
3302 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3303 reporters.push(reporter.clone());
3304 let application_cfg = mocks::application::Config::<Sha256, _> {
3305 relay: relay.clone(),
3306 me: validator.clone(),
3307 propose_latency: (10.0, 5.0),
3308 verify_latency: (10.0, 5.0),
3309 certify_latency: (10.0, 5.0),
3310 should_certify: mocks::application::Certifier::Always,
3311 };
3312 let (actor, application) = mocks::application::Application::new(
3313 context.child("application"),
3314 application_cfg,
3315 );
3316 actor.start();
3317 let blocker = oracle.control(validator.clone());
3318 let cfg = config::Config {
3319 scheme: schemes[idx_scheme].clone(),
3320 elector: elector.clone(),
3321 blocker,
3322 automaton: application.clone(),
3323 relay: application.clone(),
3324 reporter: reporter.clone(),
3325 strategy: Sequential,
3326 partition: validator.to_string(),
3327 mailbox_size: NZUsize!(1024),
3328 epoch: Epoch::new(333),
3329 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3330 Epoch::new(333),
3331 )),
3332 leader_timeout: Duration::from_secs(1),
3333 certification_timeout: Duration::from_secs(2),
3334 timeout_retry: Duration::from_secs(10),
3335 fetch_timeout: Duration::from_secs(1),
3336 view_retention,
3337 skip: SkipPolicy::Enabled {
3338 timeout: skip_timeout,
3339 budget: SkipBudget::Participants,
3340 },
3341 replay_buffer: NZUsize!(1024 * 1024),
3342 write_buffer: NZUsize!(1024 * 1024),
3343 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3344 forward: ForwardPolicy::Disabled,
3345 track_historical_votes: false,
3346 };
3347 let engine = Engine::new(context.child("engine"), cfg);
3348
3349 let (pending, recovered, resolver) = registrations
3351 .remove(validator)
3352 .expect("validator should be registered");
3353 engine_handlers.push(engine.start(pending, recovered, resolver));
3354 }
3355
3356 let stalled = View::new(1);
3359 loop {
3360 let nullified = reporters.iter().zip(participants.iter().skip(1)).all(
3361 |(reporter, validator)| {
3362 reporter
3363 .nullifies
3364 .lock()
3365 .get(&stalled)
3366 .is_some_and(|nullifiers| nullifiers.contains(validator))
3367 },
3368 );
3369 if nullified {
3370 break;
3371 }
3372 context.sleep(Duration::from_millis(100)).await;
3373 }
3374
3375 context.sleep(Duration::from_secs(1)).await;
3379
3380 for handle in engine_handlers.drain(..) {
3383 handle.abort();
3384 let _ = handle.await;
3385 }
3386 relay.deregister_all();
3387
3388 let link = Link {
3390 latency: Duration::from_millis(10),
3391 jitter: Duration::from_millis(1),
3392 success_rate: probability!(1.0),
3393 };
3394 link_validators(
3395 &mut oracle,
3396 &participants,
3397 Action::Link(link),
3398 Some(|_, i, j| ![i, j].contains(&0usize)),
3399 )
3400 .await;
3401
3402 let mut reporters = Vec::new();
3404 for (idx_scheme, validator) in participants.iter().enumerate() {
3405 if idx_scheme == 0 {
3407 continue;
3408 }
3409
3410 let context = context
3412 .child("validator_restarted")
3413 .with_attribute("public_key", validator);
3414
3415 let reporter_config = mocks::reporter::Config {
3417 participants: participants.clone().try_into().unwrap(),
3418 scheme: schemes[idx_scheme].clone(),
3419 elector: elector.clone(),
3420 };
3421 let reporter =
3422 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3423 reporters.push(reporter.clone());
3424 let application_cfg = mocks::application::Config::<Sha256, _> {
3425 relay: relay.clone(),
3426 me: validator.clone(),
3427 propose_latency: (10.0, 5.0),
3428 verify_latency: (10.0, 5.0),
3429 certify_latency: (10.0, 5.0),
3430 should_certify: mocks::application::Certifier::Always,
3431 };
3432 let (actor, application) = mocks::application::Application::new(
3433 context.child("application"),
3434 application_cfg,
3435 );
3436 actor.start();
3437 let blocker = oracle.control(validator.clone());
3438 let cfg = config::Config {
3439 scheme: schemes[idx_scheme].clone(),
3440 elector: elector.clone(),
3441 blocker,
3442 automaton: application.clone(),
3443 relay: application.clone(),
3444 reporter: reporter.clone(),
3445 strategy: Sequential,
3446 partition: validator.to_string(),
3447 mailbox_size: NZUsize!(1024),
3448 epoch: Epoch::new(333),
3449 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3450 Epoch::new(333),
3451 )),
3452 leader_timeout: Duration::from_secs(1),
3453 certification_timeout: Duration::from_secs(2),
3454 timeout_retry: Duration::from_secs(10),
3455 fetch_timeout: Duration::from_secs(1),
3456 view_retention,
3457 skip: SkipPolicy::Enabled {
3458 timeout: skip_timeout,
3459 budget: SkipBudget::Participants,
3460 },
3461 replay_buffer: NZUsize!(1024 * 1024),
3462 write_buffer: NZUsize!(1024 * 1024),
3463 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3464 forward: ForwardPolicy::Disabled,
3465 track_historical_votes: false,
3466 };
3467 let engine = Engine::new(context.child("engine"), cfg);
3468
3469 let (pending, recovered, resolver) =
3471 register_validator(&mut oracle, validator.clone()).await;
3472 engine.start(pending, recovered, resolver);
3473 }
3474
3475 let mut finalizers = Vec::new();
3479 for reporter in reporters.iter_mut() {
3480 let (mut latest, mut monitor) = reporter.subscribe().await;
3481 finalizers.push(context.child("finalizer").spawn(move |_| async move {
3482 while latest < required_containers {
3483 latest = monitor.recv().await.expect("event missing");
3484 }
3485 }));
3486 }
3487 join_all(finalizers).await;
3488 });
3489 }
3490
3491 test_for_all_fixtures!(all_crash_after_nullify);
3492
3493 fn partition<S, F, L>(fixture: F, elector: L)
3494 where
3495 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3496 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3497 L: elector::Config<S>,
3498 {
3499 partition_with_term::<S, F, L>(elector, fixture);
3500 }
3501
3502 fn partition_with_term<S, F, L>(elector: L, mut fixture: F)
3503 where
3504 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3505 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3506 L: elector::Config<S>,
3507 {
3508 let n = 10;
3510 let required_containers = View::new(50);
3511 let view_retention = ViewDelta::new(10);
3512 let skip_timeout = Duration::from_secs(11);
3513 let namespace = b"consensus".to_vec();
3514 let executor = deterministic::Runner::timed(Duration::from_secs(900));
3515 executor.start(|mut context| async move {
3516 let Fixture {
3518 participants,
3519 schemes,
3520 ..
3521 } = fixture(&mut context, &namespace, n);
3522 let mut oracle =
3523 start_test_network_with_peers(context.child("network"), participants.clone(), true)
3524 .await;
3525 let mut registrations = register_validators(&mut oracle, &participants).await;
3526
3527 let link = Link {
3529 latency: Duration::from_millis(10),
3530 jitter: Duration::from_millis(1),
3531 success_rate: probability!(1.0),
3532 };
3533 link_validators(&mut oracle, &participants, Action::Link(link.clone()), None).await;
3534
3535 let elector = elector.clone();
3537 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
3538 let mut reporters = Vec::new();
3539 let mut engine_handlers = Vec::new();
3540 for (idx, validator) in participants.iter().enumerate() {
3541 let context = context
3543 .child("validator")
3544 .with_attribute("public_key", validator);
3545
3546 let reporter_config = mocks::reporter::Config {
3548 participants: participants.clone().try_into().unwrap(),
3549 scheme: schemes[idx].clone(),
3550 elector: elector.clone(),
3551 };
3552 let reporter =
3553 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3554 reporters.push(reporter.clone());
3555 let application_cfg = mocks::application::Config::<Sha256, _> {
3556 relay: relay.clone(),
3557 me: validator.clone(),
3558 propose_latency: (10.0, 5.0),
3559 verify_latency: (10.0, 5.0),
3560 certify_latency: (10.0, 5.0),
3561 should_certify: mocks::application::Certifier::Always,
3562 };
3563 let (actor, application) = mocks::application::Application::new(
3564 context.child("application"),
3565 application_cfg,
3566 );
3567 actor.start();
3568 let blocker = oracle.control(validator.clone());
3569 let cfg = config::Config {
3570 scheme: schemes[idx].clone(),
3571 elector: elector.clone(),
3572 blocker,
3573 automaton: application.clone(),
3574 relay: application.clone(),
3575 reporter: reporter.clone(),
3576 strategy: Sequential,
3577 partition: validator.to_string(),
3578 mailbox_size: NZUsize!(1024),
3579 epoch: Epoch::new(333),
3580 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3581 Epoch::new(333),
3582 )),
3583 leader_timeout: Duration::from_secs(1),
3584 certification_timeout: Duration::from_secs(2),
3585 timeout_retry: Duration::from_secs(10),
3586 fetch_timeout: Duration::from_secs(1),
3587 view_retention,
3588 skip: SkipPolicy::Enabled {
3589 timeout: skip_timeout,
3590 budget: SkipBudget::Participants,
3591 },
3592 replay_buffer: NZUsize!(1024 * 1024),
3593 write_buffer: NZUsize!(1024 * 1024),
3594 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3595 forward: ForwardPolicy::Disabled,
3596 track_historical_votes: false,
3597 };
3598 let engine = Engine::new(context.child("engine"), cfg);
3599
3600 let (pending, recovered, resolver) = registrations
3602 .remove(validator)
3603 .expect("validator should be registered");
3604 engine_handlers.push(engine.start(pending, recovered, resolver));
3605 }
3606
3607 let mut finalizers = Vec::new();
3609 for reporter in reporters.iter_mut() {
3610 let (mut latest, mut monitor) = reporter.subscribe().await;
3611 finalizers.push(context.child("finalizer").spawn(move |_| async move {
3612 while latest < required_containers {
3613 latest = monitor.recv().await.expect("event missing");
3614 }
3615 }));
3616 }
3617 join_all(finalizers).await;
3618
3619 fn separated(n: usize, a: usize, b: usize) -> bool {
3621 let m = n / 2;
3622 (a < m && b >= m) || (a >= m && b < m)
3623 }
3624 link_validators(&mut oracle, &participants, Action::Unlink, Some(separated)).await;
3625
3626 context.sleep(Duration::from_secs(10)).await;
3628
3629 let mut finalizers = Vec::new();
3631 for reporter in reporters.iter_mut() {
3632 let (_, mut monitor) = reporter.subscribe().await;
3633 finalizers.push(context.child("finalizer").spawn(move |context| async move {
3634 select! {
3635 _timeout = context.sleep(Duration::from_secs(60)) => {},
3636 _done = monitor.recv() => {
3637 panic!("engine should not notarize or finalize anything");
3638 },
3639 }
3640 }));
3641 }
3642 join_all(finalizers).await;
3643
3644 link_validators(
3646 &mut oracle,
3647 &participants,
3648 Action::Link(link),
3649 Some(separated),
3650 )
3651 .await;
3652
3653 let mut finalizers = Vec::new();
3655 for reporter in reporters.iter_mut() {
3656 let (mut latest, mut monitor) = reporter.subscribe().await;
3657 let required = latest.saturating_add(ViewDelta::new(required_containers.get()));
3658 finalizers.push(context.child("finalizer").spawn(move |_| async move {
3659 while latest < required {
3660 latest = monitor.recv().await.expect("event missing");
3661 }
3662 }));
3663 }
3664 join_all(finalizers).await;
3665
3666 for reporter in reporters.iter() {
3668 reporter.assert_no_faults();
3670
3671 reporter.assert_no_invalid();
3673 }
3674
3675 let blocked = oracle.blocked().await.unwrap();
3677 assert!(blocked.is_empty());
3678 });
3679 }
3680
3681 test_for_all_fixtures!(partition);
3682
3683 #[test_group("slow")]
3684 #[test_traced]
3685 fn test_partition_stable_leader_optimistic() {
3686 partition_with_term::<_, _, RoundRobin>(
3687 RoundRobin::default().with_term(
3688 TermLength::new(NZU32!(5)),
3689 Duration::from_secs(13),
3690 ViewDelta::new(2),
3691 ),
3692 ed25519::fixture,
3693 );
3694 }
3695
3696 fn slow_and_lossy_links_seeded<S, F, L>(seed: u64, fixture: F, elector: L) -> String
3697 where
3698 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3699 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3700 L: elector::Config<S>,
3701 {
3702 slow_and_lossy_links_seeded_with_term::<S, F, L>(elector, seed, fixture)
3703 }
3704
3705 fn slow_and_lossy_links_seeded_with_term<S, F, L>(
3706 elector: L,
3707 seed: u64,
3708 mut fixture: F,
3709 ) -> String
3710 where
3711 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3712 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3713 L: elector::Config<S>,
3714 {
3715 let n = 5;
3717 let required_containers = View::new(50);
3718 let view_retention = ViewDelta::new(10);
3719 let skip_timeout = Duration::from_secs(11);
3720 let namespace = b"consensus".to_vec();
3721 let cfg = deterministic::Config::new()
3722 .with_seed(seed)
3723 .with_timeout(Some(Duration::from_secs(5_000)));
3724 let executor = deterministic::Runner::new(cfg);
3725 executor.start(|mut context| async move {
3726 let Fixture {
3728 participants,
3729 schemes,
3730 ..
3731 } = fixture(&mut context, &namespace, n);
3732 let mut oracle =
3733 start_test_network_with_peers(context.child("network"), participants.clone(), true)
3734 .await;
3735 let mut registrations = register_validators(&mut oracle, &participants).await;
3736
3737 let degraded_link = Link {
3739 latency: Duration::from_millis(200),
3740 jitter: Duration::from_millis(150),
3741 success_rate: probability!(0.5),
3742 };
3743 link_validators(
3744 &mut oracle,
3745 &participants,
3746 Action::Link(degraded_link),
3747 None,
3748 )
3749 .await;
3750
3751 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
3753 let mut reporters = Vec::new();
3754 let mut engine_handlers = Vec::new();
3755 for (idx, validator) in participants.iter().enumerate() {
3756 let context = context
3758 .child("validator")
3759 .with_attribute("public_key", validator);
3760
3761 let reporter_config = mocks::reporter::Config {
3763 participants: participants.clone().try_into().unwrap(),
3764 scheme: schemes[idx].clone(),
3765 elector: elector.clone(),
3766 };
3767 let reporter =
3768 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3769 reporters.push(reporter.clone());
3770 let application_cfg = mocks::application::Config::<Sha256, _> {
3771 relay: relay.clone(),
3772 me: validator.clone(),
3773 propose_latency: (10.0, 5.0),
3774 verify_latency: (10.0, 5.0),
3775 certify_latency: (10.0, 5.0),
3776 should_certify: mocks::application::Certifier::Always,
3777 };
3778 let (actor, application) = mocks::application::Application::new(
3779 context.child("application"),
3780 application_cfg,
3781 );
3782 actor.start();
3783 let blocker = oracle.control(validator.clone());
3784 let cfg = config::Config {
3785 scheme: schemes[idx].clone(),
3786 elector: elector.clone(),
3787 blocker,
3788 automaton: application.clone(),
3789 relay: application.clone(),
3790 reporter: reporter.clone(),
3791 strategy: Sequential,
3792 partition: validator.to_string(),
3793 mailbox_size: NZUsize!(1024),
3794 epoch: Epoch::new(333),
3795 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
3796 Epoch::new(333),
3797 )),
3798 leader_timeout: Duration::from_secs(1),
3799 certification_timeout: Duration::from_secs(2),
3800 timeout_retry: Duration::from_secs(10),
3801 fetch_timeout: Duration::from_secs(1),
3802 view_retention,
3803 skip: SkipPolicy::Enabled {
3804 timeout: skip_timeout,
3805 budget: SkipBudget::Participants,
3806 },
3807 replay_buffer: NZUsize!(1024 * 1024),
3808 write_buffer: NZUsize!(1024 * 1024),
3809 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
3810 forward: ForwardPolicy::Disabled,
3811 track_historical_votes: false,
3812 };
3813 let engine = Engine::new(context.child("engine"), cfg);
3814
3815 let (pending, recovered, resolver) = registrations
3817 .remove(validator)
3818 .expect("validator should be registered");
3819 engine_handlers.push(engine.start(pending, recovered, resolver));
3820 }
3821
3822 let mut finalizers = Vec::new();
3824 for reporter in reporters.iter_mut() {
3825 let (mut latest, mut monitor) = reporter.subscribe().await;
3826 finalizers.push(context.child("finalizer").spawn(move |_| async move {
3827 while latest < required_containers {
3828 latest = monitor.recv().await.expect("event missing");
3829 }
3830 }));
3831 }
3832 join_all(finalizers).await;
3833
3834 for reporter in reporters.iter() {
3836 reporter.assert_no_faults();
3838
3839 reporter.assert_no_invalid();
3841 }
3842
3843 let blocked = oracle.blocked().await.unwrap();
3845 assert!(blocked.is_empty());
3846
3847 context.auditor().state()
3848 })
3849 }
3850
3851 fn slow_and_lossy_links<S, F, L>(fixture: F, elector: L) -> String
3852 where
3853 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3854 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3855 L: elector::Config<S>,
3856 {
3857 slow_and_lossy_links_seeded::<_, _, L>(6, fixture, elector)
3858 }
3859
3860 test_for_all_fixtures!(slow_and_lossy_links);
3861
3862 #[test_group("slow")]
3863 #[test_traced]
3864 fn test_slow_and_lossy_links_stable_leader_optimistic() {
3865 slow_and_lossy_links_seeded_with_term::<_, _, RoundRobin>(
3866 RoundRobin::default().with_term(
3867 TermLength::new(NZU32!(5)),
3868 Duration::from_secs(13),
3869 ViewDelta::new(2),
3870 ),
3871 6,
3872 ed25519::fixture,
3873 );
3874 }
3875
3876 fn determinism<S, F, L>(seed: u64, fixture: F, elector: L)
3877 where
3878 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3879 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S> + Copy,
3880 L: elector::Config<S>,
3881 {
3882 assert_eq!(
3885 slow_and_lossy_links_seeded::<_, _, L>(seed, fixture, elector.clone()),
3886 slow_and_lossy_links_seeded::<_, _, L>(seed, fixture, elector),
3887 );
3888 }
3889
3890 test_for_all_fixtures!(determinism, seeds = 5);
3891
3892 #[test_group("slow")]
3893 #[test_traced]
3894 fn test_distinct_states() {
3895 macro_rules! collect {
3897 ($vec:ident, $suffix:ident, $elector:ty, $fixture:expr, $elector_config:expr) => {
3898 $vec.push((
3899 stringify!($suffix),
3900 slow_and_lossy_links_seeded::<_, _, $elector>(7, $fixture, $elector_config),
3901 ));
3902 };
3903 }
3904 let mut states = Vec::new();
3905 for_each_fixture!(collect!(states));
3906 for pair in states.windows(2) {
3907 assert_ne!(
3908 pair[0].1, pair[1].1,
3909 "state {} equals state {}",
3910 pair[0].0, pair[1].0
3911 );
3912 }
3913 }
3914
3915 fn conflicter<S, F, L>(seed: u64, mut fixture: F, elector: L)
3916 where
3917 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
3918 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
3919 L: elector::Config<S>,
3920 {
3921 let n = 4;
3923 let required_containers = View::new(50);
3924 let view_retention = ViewDelta::new(10);
3925 let skip_timeout = Duration::from_secs(11);
3926 let namespace = b"consensus".to_vec();
3927 let cfg = deterministic::Config::new()
3928 .with_seed(seed)
3929 .with_timeout(Some(Duration::from_secs(30)));
3930 let executor = deterministic::Runner::new(cfg);
3931 executor.start(|mut context| async move {
3932 let Fixture {
3934 participants,
3935 schemes,
3936 ..
3937 } = fixture(&mut context, &namespace, n);
3938 let mut oracle =
3939 start_test_network_with_peers(context.child("network"), participants.clone(), true)
3940 .await;
3941 let mut registrations = register_validators(&mut oracle, &participants).await;
3942
3943 let link = Link {
3945 latency: Duration::from_millis(10),
3946 jitter: Duration::from_millis(1),
3947 success_rate: probability!(1.0),
3948 };
3949 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
3950
3951 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
3953 let mut reporters = Vec::new();
3954 for (idx_scheme, validator) in participants.iter().enumerate() {
3955 let context = context
3957 .child("validator")
3958 .with_attribute("public_key", validator);
3959
3960 let reporter_config = mocks::reporter::Config {
3962 participants: participants.clone().try_into().unwrap(),
3963 scheme: schemes[idx_scheme].clone(),
3964 elector: elector.clone(),
3965 };
3966 let reporter =
3967 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
3968 let (pending, recovered, resolver) = registrations
3969 .remove(validator)
3970 .expect("validator should be registered");
3971 if idx_scheme == 0 {
3972 let cfg = mocks::conflicter::Config {
3973 scheme: schemes[idx_scheme].clone(),
3974 };
3975
3976 let engine: mocks::conflicter::Conflicter<_, _, Sha256> =
3977 mocks::conflicter::Conflicter::new(context.child("byzantine_engine"), cfg);
3978 engine.start(pending);
3979 } else {
3980 reporters.push(reporter.clone());
3981 let application_cfg = mocks::application::Config::<Sha256, _> {
3982 relay: relay.clone(),
3983 me: validator.clone(),
3984 propose_latency: (10.0, 5.0),
3985 verify_latency: (10.0, 5.0),
3986 certify_latency: (10.0, 5.0),
3987 should_certify: mocks::application::Certifier::Always,
3988 };
3989 let (actor, application) = mocks::application::Application::new(
3990 context.child("application"),
3991 application_cfg,
3992 );
3993 actor.start();
3994 let blocker = oracle.control(validator.clone());
3995 let cfg = config::Config {
3996 scheme: schemes[idx_scheme].clone(),
3997 elector: elector.clone(),
3998 blocker,
3999 automaton: application.clone(),
4000 relay: application.clone(),
4001 reporter: reporter.clone(),
4002 strategy: Sequential,
4003 partition: validator.to_string(),
4004 mailbox_size: NZUsize!(1024),
4005 epoch: Epoch::new(333),
4006 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4007 Epoch::new(333),
4008 )),
4009 leader_timeout: Duration::from_secs(1),
4010 certification_timeout: Duration::from_secs(2),
4011 timeout_retry: Duration::from_secs(10),
4012 fetch_timeout: Duration::from_secs(1),
4013 view_retention,
4014 skip: SkipPolicy::Enabled {
4015 timeout: skip_timeout,
4016 budget: SkipBudget::Participants,
4017 },
4018 replay_buffer: NZUsize!(1024 * 1024),
4019 write_buffer: NZUsize!(1024 * 1024),
4020 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4021 forward: ForwardPolicy::Disabled,
4022 track_historical_votes: true,
4023 };
4024 let engine = Engine::new(context.child("engine"), cfg);
4025 engine.start(pending, recovered, resolver);
4026 }
4027 }
4028
4029 let mut finalizers = Vec::new();
4031 for reporter in reporters.iter_mut() {
4032 let (mut latest, mut monitor) = reporter.subscribe().await;
4033 finalizers.push(context.child("finalizer").spawn(move |_| async move {
4034 while latest < required_containers {
4035 latest = monitor.recv().await.expect("event missing");
4036 }
4037 }));
4038 }
4039 join_all(finalizers).await;
4040
4041 let byz = &participants[0];
4043 let mut count_conflicting = 0;
4044 for reporter in reporters.iter() {
4045 {
4047 let faults = reporter.faults.lock();
4048 assert_eq!(faults.len(), 1);
4049 let faulter = faults.get(byz).expect("byzantine party is not faulter");
4050 for faults in faulter.values() {
4051 for fault in faults.iter() {
4052 match fault {
4053 Activity::ConflictingNotarize(_) => {
4054 count_conflicting += 1;
4055 }
4056 Activity::ConflictingFinalize(_) => {
4057 count_conflicting += 1;
4058 }
4059 _ => panic!("unexpected fault: {fault:?}"),
4060 }
4061 }
4062 }
4063 }
4064
4065 reporter.assert_no_invalid();
4067 }
4068 assert!(count_conflicting > 0);
4069
4070 let blocked = oracle.blocked().await.unwrap();
4072 assert!(!blocked.is_empty());
4073 for (a, b) in blocked {
4074 assert_ne!(&a, byz);
4075 assert_eq!(&b, byz);
4076 }
4077 });
4078 }
4079
4080 test_for_all_fixtures!(conflicter, seeds = 5);
4081
4082 fn invalid<S, F, L>(seed: u64, mut fixture: F, elector: L)
4083 where
4084 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4085 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4086 L: elector::Config<S>,
4087 {
4088 let n = 4;
4090 let required_containers = View::new(50);
4091 let view_retention = ViewDelta::new(10);
4092 let skip_timeout = Duration::from_secs(11);
4093 let namespace = b"consensus".to_vec();
4094 let cfg = deterministic::Config::new()
4095 .with_seed(seed)
4096 .with_timeout(Some(Duration::from_secs(30)));
4097 let executor = deterministic::Runner::new(cfg);
4098 executor.start(|mut context| async move {
4099 let Fixture {
4101 participants,
4102 schemes,
4103 ..
4104 } = fixture(&mut context, &namespace, n);
4105
4106 let schemes: Vec<_> = schemes
4107 .into_iter()
4108 .enumerate()
4109 .map(|(idx, scheme)| {
4110 let is_byzantine = idx == 0;
4111 let behavior = if is_byzantine {
4112 wrapped::Behavior::CorruptSignature
4113 } else {
4114 wrapped::Behavior::Honest
4115 };
4116 wrapped::Scheme::new(scheme, behavior)
4117 })
4118 .collect();
4119
4120 let mut oracle =
4121 start_test_network_with_peers(context.child("network"), participants.clone(), true)
4122 .await;
4123 let mut registrations = register_validators(&mut oracle, &participants).await;
4124
4125 let link = Link {
4127 latency: Duration::from_millis(10),
4128 jitter: Duration::from_millis(1),
4129 success_rate: probability!(1.0),
4130 };
4131 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4132
4133 let elector = wrapped::Config(elector);
4135 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
4136 let mut reporters = Vec::new();
4137 for (idx_scheme, validator) in participants.iter().enumerate() {
4138 let context = context
4140 .child("validator")
4141 .with_attribute("public_key", validator);
4142
4143 let reporter_config = mocks::reporter::Config {
4144 participants: participants.clone().try_into().unwrap(),
4145 scheme: schemes[idx_scheme].clone(),
4146 elector: elector.clone(),
4147 };
4148 let reporter =
4149 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4150 reporters.push(reporter.clone());
4151
4152 let application_cfg = mocks::application::Config::<Sha256, _> {
4153 relay: relay.clone(),
4154 me: validator.clone(),
4155 propose_latency: (10.0, 5.0),
4156 verify_latency: (10.0, 5.0),
4157 certify_latency: (10.0, 5.0),
4158 should_certify: mocks::application::Certifier::Always,
4159 };
4160 let (actor, application) = mocks::application::Application::new(
4161 context.child("application"),
4162 application_cfg,
4163 );
4164 actor.start();
4165 let blocker = oracle.control(validator.clone());
4166 let cfg = config::Config {
4167 scheme: schemes[idx_scheme].clone(),
4168 elector: elector.clone(),
4169 blocker,
4170 automaton: application.clone(),
4171 relay: application.clone(),
4172 reporter: reporter.clone(),
4173 strategy: Sequential,
4174 partition: validator.clone().to_string(),
4175 mailbox_size: NZUsize!(1024),
4176 epoch: Epoch::new(333),
4177 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4178 Epoch::new(333),
4179 )),
4180 leader_timeout: Duration::from_secs(1),
4181 certification_timeout: Duration::from_secs(2),
4182 timeout_retry: Duration::from_secs(10),
4183 fetch_timeout: Duration::from_secs(1),
4184 view_retention,
4185 skip: SkipPolicy::Enabled {
4186 timeout: skip_timeout,
4187 budget: SkipBudget::Participants,
4188 },
4189 replay_buffer: NZUsize!(1024 * 1024),
4190 write_buffer: NZUsize!(1024 * 1024),
4191 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4192 forward: ForwardPolicy::Disabled,
4193 track_historical_votes: false,
4194 };
4195 let engine = Engine::new(context.child("engine"), cfg);
4196 let (pending, recovered, resolver) = registrations
4197 .remove(validator)
4198 .expect("validator should be registered");
4199 engine.start(pending, recovered, resolver);
4200 }
4201
4202 let mut finalizers = Vec::new();
4206 for reporter in reporters.iter_mut().skip(1) {
4207 let (mut latest, mut monitor) = reporter.subscribe().await;
4208 finalizers.push(context.child("finalizer").spawn(move |_| async move {
4209 while latest < required_containers {
4210 latest = monitor.recv().await.expect("event missing");
4211 }
4212 }));
4213 }
4214 join_all(finalizers).await;
4215
4216 for (i, reporter) in reporters.iter().enumerate() {
4218 reporter.assert_no_faults();
4220
4221 assert!(*reporter.invalid_votes.lock() > 0);
4224
4225 let is_byzantine = i == 0;
4228 if is_byzantine {
4229 assert!(*reporter.invalid_certificates.lock() > 0);
4230 } else {
4231 assert_eq!(*reporter.invalid_certificates.lock(), 0);
4232 }
4233 }
4234
4235 let blocked = oracle.blocked().await.unwrap();
4238 assert!(blocked.len() >= participants.len() - 1);
4239 let byz = &participants[0];
4240 for (_, b) in blocked {
4241 assert_eq!(&b, byz);
4243 }
4244 });
4245 }
4246
4247 test_for_all_fixtures!(invalid, seeds = 5);
4248
4249 fn received_certificates_are_reported<S, F, L>(mut fixture: F, elector: L)
4251 where
4252 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4253 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4254 L: elector::Config<S>,
4255 {
4256 let n = 4;
4257 let required_containers = View::new(10);
4258 let view_retention = ViewDelta::new(10);
4259 let skip_timeout = Duration::from_secs(11);
4260 let namespace = b"consensus".to_vec();
4261 let cfg = deterministic::Config::new()
4262 .with_seed(0)
4263 .with_timeout(Some(Duration::from_secs(30)));
4264 let executor = deterministic::Runner::new(cfg);
4265 executor.start(|mut context| async move {
4266 let Fixture {
4267 participants,
4268 schemes,
4269 ..
4270 } = fixture(&mut context, &namespace, n);
4271
4272 let mut oracle = start_test_network_with_peers(
4273 context.child("network"),
4274 participants.clone(),
4275 false,
4276 )
4277 .await;
4278 let mut registrations = register_validators(&mut oracle, &participants).await;
4279
4280 let link = Link {
4285 latency: Duration::from_millis(100),
4286 jitter: Duration::from_millis(1),
4287 success_rate: probability!(1.0),
4288 };
4289 fn link_graph(_: usize, i: usize, j: usize) -> bool {
4290 if i == 0 || j == 0 {
4291 return i == 1 || j == 1;
4292 }
4293 true
4294 }
4295 link_validators(
4296 &mut oracle,
4297 &participants,
4298 Action::Link(link),
4299 Some(link_graph),
4300 )
4301 .await;
4302
4303 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
4304 let mut reporters = Vec::new();
4305 for (idx_scheme, validator) in participants.iter().enumerate() {
4306 let context = context
4307 .child("validator")
4308 .with_attribute("public_key", validator);
4309 let reporter_config = mocks::reporter::Config {
4310 participants: participants.clone().try_into().unwrap(),
4311 scheme: schemes[idx_scheme].clone(),
4312 elector: elector.clone(),
4313 };
4314 let reporter =
4315 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4316 reporters.push(reporter.clone());
4317
4318 let application_cfg = mocks::application::Config::<Sha256, _> {
4319 relay: relay.clone(),
4320 me: validator.clone(),
4321 propose_latency: (10.0, 5.0),
4322 verify_latency: (10.0, 5.0),
4323 certify_latency: (10.0, 5.0),
4324 should_certify: mocks::application::Certifier::Always,
4325 };
4326 let (actor, application) = mocks::application::Application::new(
4327 context.child("application"),
4328 application_cfg,
4329 );
4330 actor.start();
4331 let blocker = oracle.control(validator.clone());
4332 let cfg = config::Config {
4333 scheme: schemes[idx_scheme].clone(),
4334 elector: elector.clone(),
4335 blocker,
4336 automaton: application.clone(),
4337 relay: application.clone(),
4338 reporter: reporter.clone(),
4339 strategy: Sequential,
4340 partition: validator.clone().to_string(),
4341 mailbox_size: NZUsize!(1024),
4342 epoch: Epoch::new(333),
4343 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4344 Epoch::new(333),
4345 )),
4346 leader_timeout: Duration::from_secs(1),
4347 certification_timeout: Duration::from_secs(2),
4348 timeout_retry: Duration::from_secs(10),
4349 fetch_timeout: Duration::from_secs(1),
4350 view_retention,
4351 skip: SkipPolicy::Enabled {
4352 timeout: skip_timeout,
4353 budget: SkipBudget::Participants,
4354 },
4355 replay_buffer: NZUsize!(1024 * 1024),
4356 write_buffer: NZUsize!(1024 * 1024),
4357 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4358 forward: ForwardPolicy::Disabled,
4359 track_historical_votes: false,
4360 };
4361 let engine = Engine::new(context.child("engine"), cfg);
4362 let (pending, recovered, resolver) = registrations
4363 .remove(validator)
4364 .expect("validator should be registered");
4365 engine.start(pending, recovered, resolver);
4366 }
4367 let excluded_reporter = reporters[0].clone();
4369 let mut honest_reporter = reporters[1].clone();
4370 let (mut honest_latest, mut honest_monitor) = honest_reporter.subscribe().await;
4371 while honest_latest < required_containers {
4372 honest_latest = honest_monitor.recv().await.expect("event missing");
4373 }
4374
4375 context.sleep(Duration::from_secs(1)).await;
4377
4378 let honest_notarized = {
4381 let notarizations = honest_reporter.notarizations.lock();
4382 View::range(View::new(1), required_containers.next())
4383 .filter(|view| notarizations.contains_key(view))
4384 .count()
4385 };
4386 let excluded_notarized = {
4387 let notarizations = excluded_reporter.notarizations.lock();
4388 View::range(View::new(1), required_containers.next())
4389 .filter(|view| notarizations.contains_key(view))
4390 .count()
4391 };
4392 assert!(
4393 excluded_notarized >= honest_notarized.saturating_sub(2),
4394 "honest_notarized: {honest_notarized}, excluded_notarized: {excluded_notarized}"
4395 );
4396
4397 let honest_finalized = {
4398 let finalizations = honest_reporter.finalizations.lock();
4399 View::range(View::new(1), required_containers.next())
4400 .filter(|view| finalizations.contains_key(view))
4401 .count()
4402 };
4403 let excluded_finalized = {
4404 let finalizations = excluded_reporter.finalizations.lock();
4405 View::range(View::new(1), required_containers.next())
4406 .filter(|view| finalizations.contains_key(view))
4407 .count()
4408 };
4409 assert!(
4410 excluded_finalized >= honest_finalized.saturating_sub(2),
4411 "honest_finalized: {honest_finalized}, excluded_finalized: {excluded_finalized}"
4412 );
4413 });
4414 }
4415
4416 test_for_all_fixtures!(received_certificates_are_reported);
4417
4418 fn survives_burst<S, F, L>(mut fixture: F, elector: L)
4419 where
4420 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4421 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4422 L: elector::Config<S>,
4423 {
4424 let n = 4;
4425 let epoch = Epoch::new(333);
4426 let namespace = b"mailbox_size_one_certificate_burst".to_vec();
4427 let executor = deterministic::Runner::default();
4428 executor.start(|mut context| async move {
4429 let Fixture {
4430 participants,
4431 schemes,
4432 ..
4433 } = fixture(&mut context, &namespace, n);
4434 let me = participants[0].clone();
4435 let mut oracle =
4436 start_test_network_with_peers(context.child("network"), participants.clone(), true)
4437 .await;
4438 let (pending, recovered, resolver) = register_validator(&mut oracle, me.clone()).await;
4439
4440 let injector_pk = PrivateKey::from_seed(9_000_000).public_key();
4441 let (mut injector_sender, _injector_receiver) = oracle
4442 .control(injector_pk.clone())
4443 .register(1, TEST_QUOTA)
4444 .await
4445 .unwrap();
4446 let link = Link {
4447 latency: Duration::from_millis(0),
4448 jitter: Duration::from_millis(0),
4449 success_rate: probability!(1.0),
4450 };
4451 oracle
4452 .add_link(injector_pk.clone(), me.clone(), link)
4453 .await
4454 .unwrap();
4455 oracle.manager().track(
4456 1,
4457 TrackedPeers::new(
4458 Set::from_iter_dedup(std::iter::once(me.clone())),
4459 Set::from_iter_dedup(std::iter::once(injector_pk.clone())),
4460 ),
4461 );
4462 context.sleep(Duration::from_millis(1)).await;
4463
4464 let quorum = quorum(n) as usize;
4465 let notarization = |view: View, parent: View, payload: &[u8]| {
4466 let proposal =
4467 Proposal::new(Round::new(epoch, view), parent, Sha256::hash(&[payload]));
4468 let votes: Vec<_> = schemes
4469 .iter()
4470 .take(quorum)
4471 .map(|scheme| TNotarize::sign(scheme, proposal.clone()).unwrap())
4472 .collect();
4473 TNotarization::from_notarizes(&schemes[0], non_empty![@votes.iter()], &Sequential)
4474 .expect("notarization requires quorum")
4475 };
4476 let finalization = |view: View, parent: View, payload: &[u8]| {
4477 let proposal =
4478 Proposal::new(Round::new(epoch, view), parent, Sha256::hash(&[payload]));
4479 let votes: Vec<_> = schemes
4480 .iter()
4481 .take(quorum)
4482 .map(|scheme| TFinalize::sign(scheme, proposal.clone()).unwrap())
4483 .collect();
4484 TFinalization::from_finalizes(&schemes[0], non_empty![@votes.iter()], &Sequential)
4485 .expect("finalization requires quorum")
4486 };
4487
4488 for certificate in [
4490 Certificate::Notarization(notarization(View::new(1), View::zero(), b"payload-1")),
4491 Certificate::Notarization(notarization(View::new(2), View::new(1), b"payload-2")),
4492 Certificate::Notarization(notarization(View::new(3), View::new(2), b"payload-3")),
4493 Certificate::Finalization(finalization(View::new(3), View::new(2), b"payload-3")),
4494 ] {
4495 injector_sender.send(Recipients::One(me.clone()), certificate.encode(), true);
4496 }
4497
4498 let reporter_config = mocks::reporter::Config {
4499 participants: participants.clone().try_into().unwrap(),
4500 scheme: schemes[0].clone(),
4501 elector: elector.clone(),
4502 };
4503 let reporter =
4504 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4505 let mut monitor_reporter = reporter.clone();
4506 let (mut latest, mut monitor) = monitor_reporter.subscribe().await;
4507
4508 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
4509 let application_cfg = mocks::application::Config::<Sha256, _> {
4510 relay: relay.clone(),
4511 me: me.clone(),
4512 propose_latency: (0.0, 0.0),
4513 verify_latency: (0.0, 0.0),
4514 certify_latency: (0.0, 0.0),
4515 should_certify: mocks::application::Certifier::Always,
4516 };
4517 let (mut application_actor, application) =
4518 mocks::application::Application::new(context.child("application"), application_cfg);
4519 application_actor.set_stall_proposals(true);
4520 application_actor.start();
4521
4522 let cfg = config::Config {
4523 scheme: schemes[0].clone(),
4524 elector,
4525 blocker: oracle.control(me.clone()),
4526 automaton: application.clone(),
4527 relay: application,
4528 reporter,
4529 strategy: Sequential,
4530 partition: me.to_string(),
4531 mailbox_size: NZUsize!(1),
4532 epoch,
4533 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(epoch)),
4534 leader_timeout: Duration::from_secs(1),
4535 certification_timeout: Duration::from_secs(2),
4536 timeout_retry: Duration::from_secs(10),
4537 fetch_timeout: Duration::from_secs(1),
4538 view_retention: ViewDelta::new(10),
4539 skip: SkipPolicy::Enabled {
4540 timeout: Duration::from_secs(11),
4541 budget: SkipBudget::Participants,
4542 },
4543 replay_buffer: NZUsize!(1024 * 1024),
4544 write_buffer: NZUsize!(1024 * 1024),
4545 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4546 forward: ForwardPolicy::Disabled,
4547 track_historical_votes: false,
4548 };
4549 let engine = Engine::new(context.child("engine"), cfg);
4550 engine.start(pending, recovered, resolver);
4551
4552 while latest < View::new(3) {
4553 latest = monitor.recv().await.expect("finalization event missing");
4554 }
4555 });
4556 }
4557
4558 test_for_all_fixtures!(survives_burst);
4559
4560 fn impersonator<S, F, L>(seed: u64, mut fixture: F, elector: L)
4561 where
4562 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4563 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4564 L: elector::Config<S>,
4565 {
4566 let n = 4;
4568 let required_containers = View::new(50);
4569 let view_retention = ViewDelta::new(10);
4570 let skip_timeout = Duration::from_secs(11);
4571 let namespace = b"consensus".to_vec();
4572 let cfg = deterministic::Config::new()
4573 .with_seed(seed)
4574 .with_timeout(Some(Duration::from_secs(30)));
4575 let executor = deterministic::Runner::new(cfg);
4576 executor.start(|mut context| async move {
4577 let Fixture {
4579 participants,
4580 schemes,
4581 ..
4582 } = fixture(&mut context, &namespace, n);
4583 let mut oracle =
4584 start_test_network_with_peers(context.child("network"), participants.clone(), true)
4585 .await;
4586 let mut registrations = register_validators(&mut oracle, &participants).await;
4587
4588 let link = Link {
4590 latency: Duration::from_millis(10),
4591 jitter: Duration::from_millis(1),
4592 success_rate: probability!(1.0),
4593 };
4594 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4595
4596 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
4598 let mut reporters = Vec::new();
4599 for (idx_scheme, validator) in participants.iter().enumerate() {
4600 let context = context
4602 .child("validator")
4603 .with_attribute("public_key", validator);
4604
4605 let reporter_config = mocks::reporter::Config {
4607 participants: participants.clone().try_into().unwrap(),
4608 scheme: schemes[idx_scheme].clone(),
4609 elector: elector.clone(),
4610 };
4611 let reporter =
4612 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4613 let (pending, recovered, resolver) = registrations
4614 .remove(validator)
4615 .expect("validator should be registered");
4616 if idx_scheme == 0 {
4617 let cfg = mocks::impersonator::Config {
4618 scheme: schemes[idx_scheme].clone(),
4619 };
4620
4621 let engine: mocks::impersonator::Impersonator<_, _, Sha256> =
4622 mocks::impersonator::Impersonator::new(
4623 context.child("byzantine_engine"),
4624 cfg,
4625 );
4626 engine.start(pending);
4627 } else {
4628 reporters.push(reporter.clone());
4629 let application_cfg = mocks::application::Config::<Sha256, _> {
4630 relay: relay.clone(),
4631 me: validator.clone(),
4632 propose_latency: (10.0, 5.0),
4633 verify_latency: (10.0, 5.0),
4634 certify_latency: (10.0, 5.0),
4635 should_certify: mocks::application::Certifier::Always,
4636 };
4637 let (actor, application) = mocks::application::Application::new(
4638 context.child("application"),
4639 application_cfg,
4640 );
4641 actor.start();
4642 let blocker = oracle.control(validator.clone());
4643 let cfg = config::Config {
4644 scheme: schemes[idx_scheme].clone(),
4645 elector: elector.clone(),
4646 blocker,
4647 automaton: application.clone(),
4648 relay: application.clone(),
4649 reporter: reporter.clone(),
4650 strategy: Sequential,
4651 partition: validator.clone().to_string(),
4652 mailbox_size: NZUsize!(1024),
4653 epoch: Epoch::new(333),
4654 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4655 Epoch::new(333),
4656 )),
4657 leader_timeout: Duration::from_secs(1),
4658 certification_timeout: Duration::from_secs(2),
4659 timeout_retry: Duration::from_secs(10),
4660 fetch_timeout: Duration::from_secs(1),
4661 view_retention,
4662 skip: SkipPolicy::Enabled {
4663 timeout: skip_timeout,
4664 budget: SkipBudget::Participants,
4665 },
4666 replay_buffer: NZUsize!(1024 * 1024),
4667 write_buffer: NZUsize!(1024 * 1024),
4668 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4669 forward: ForwardPolicy::Disabled,
4670 track_historical_votes: false,
4671 };
4672 let engine = Engine::new(context.child("engine"), cfg);
4673 engine.start(pending, recovered, resolver);
4674 }
4675 }
4676
4677 let mut finalizers = Vec::new();
4679 for reporter in reporters.iter_mut() {
4680 let (mut latest, mut monitor) = reporter.subscribe().await;
4681 finalizers.push(context.child("finalizer").spawn(move |_| async move {
4682 while latest < required_containers {
4683 latest = monitor.recv().await.expect("event missing");
4684 }
4685 }));
4686 }
4687 join_all(finalizers).await;
4688
4689 let byz = &participants[0];
4691 for reporter in reporters.iter() {
4692 reporter.assert_no_faults();
4694
4695 reporter.assert_no_invalid();
4697 }
4698
4699 let blocked = oracle.blocked().await.unwrap();
4701 assert!(!blocked.is_empty());
4702 for (a, b) in blocked {
4703 assert_ne!(&a, byz);
4704 assert_eq!(&b, byz);
4705 }
4706 });
4707 }
4708
4709 test_for_all_fixtures!(impersonator, seeds = 5);
4710
4711 fn equivocator_seeded<S, F, L>(seed: u64, fixture: F, elector: L) -> bool
4712 where
4713 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4714 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4715 L: elector::Config<S>,
4716 {
4717 equivocator_seeded_with_term::<S, F, L>(seed, elector, fixture)
4718 }
4719
4720 fn equivocator_seeded_with_term<S, F, L>(seed: u64, elector: L, mut fixture: F) -> bool
4721 where
4722 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4723 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
4724 L: elector::Config<S>,
4725 {
4726 let n = 7;
4728 let required_containers = View::new(50);
4729 let view_retention = ViewDelta::new(10);
4730 let skip_timeout = Duration::from_secs(11);
4731 let namespace = b"consensus".to_vec();
4732 let cfg = deterministic::Config::new()
4733 .with_seed(seed)
4734 .with_timeout(Some(Duration::from_secs(60)));
4735 let executor = deterministic::Runner::new(cfg);
4736 executor.start(|mut context| async move {
4737 let Fixture {
4739 participants,
4740 schemes,
4741 ..
4742 } = fixture(&mut context, &namespace, n);
4743 let mut oracle =
4744 start_test_network_with_peers(context.child("network"), participants.clone(), true)
4745 .await;
4746 let mut registrations = register_validators(&mut oracle, &participants).await;
4747
4748 let link = Link {
4750 latency: Duration::from_millis(10),
4751 jitter: Duration::from_millis(1),
4752 success_rate: probability!(1.0),
4753 };
4754 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
4755
4756 let elector = elector.clone();
4758 let mut engines = Vec::new();
4759 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
4760 let mut reporters = Vec::new();
4761 for (idx_scheme, validator) in participants.iter().enumerate() {
4762 let context = context
4764 .child("validator")
4765 .with_attribute("public_key", validator);
4766
4767 let reporter_config = mocks::reporter::Config {
4769 participants: participants.clone().try_into().unwrap(),
4770 scheme: schemes[idx_scheme].clone(),
4771 elector: elector.clone(),
4772 };
4773 let reporter =
4774 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4775 reporters.push(reporter.clone());
4776 let (pending, recovered, resolver) = registrations
4777 .remove(validator)
4778 .expect("validator should be registered");
4779 if idx_scheme == 0 {
4780 let cfg = mocks::equivocator::Config::<_, _, Sha256> {
4781 scheme: schemes[idx_scheme].clone(),
4782 epoch: Epoch::new(333),
4783 relay: relay.clone(),
4784 elector: elector.clone(),
4785 };
4786
4787 let engine = mocks::equivocator::Equivocator::new(
4788 context.child("byzantine_engine"),
4789 cfg,
4790 );
4791 engines.push(engine.start(pending, recovered));
4792 } else {
4793 let application_cfg = mocks::application::Config::<Sha256, _> {
4794 relay: relay.clone(),
4795 me: validator.clone(),
4796 propose_latency: (10.0, 5.0),
4797 verify_latency: (10.0, 5.0),
4798 certify_latency: (10.0, 5.0),
4799 should_certify: mocks::application::Certifier::Always,
4800 };
4801 let (actor, application) = mocks::application::Application::new(
4802 context.child("application"),
4803 application_cfg,
4804 );
4805 actor.start();
4806 let blocker = oracle.control(validator.clone());
4807 let cfg = config::Config {
4808 scheme: schemes[idx_scheme].clone(),
4809 elector: elector.clone(),
4810 blocker,
4811 automaton: application.clone(),
4812 relay: application.clone(),
4813 reporter: reporter.clone(),
4814 strategy: Sequential,
4815 partition: validator.to_string(),
4816 mailbox_size: NZUsize!(1024),
4817 epoch: Epoch::new(333),
4818 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
4819 Epoch::new(333),
4820 )),
4821 leader_timeout: Duration::from_secs(1),
4822 certification_timeout: Duration::from_secs(2),
4823 timeout_retry: Duration::from_secs(10),
4824 fetch_timeout: Duration::from_secs(1),
4825 view_retention,
4826 skip: SkipPolicy::Enabled {
4827 timeout: skip_timeout,
4828 budget: SkipBudget::Participants,
4829 },
4830 replay_buffer: NZUsize!(1024 * 1024),
4831 write_buffer: NZUsize!(1024 * 1024),
4832 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4833 forward: ForwardPolicy::Disabled,
4834 track_historical_votes: false,
4835 };
4836 let engine = Engine::new(context.child("engine"), cfg);
4837 engines.push(engine.start(pending, recovered, resolver));
4838 }
4839 }
4840
4841 let mut finalizers = Vec::new();
4843 for reporter in reporters.iter_mut().skip(1) {
4844 let (mut latest, mut monitor) = reporter.subscribe().await;
4845 finalizers.push(context.child("finalizer").spawn(move |_| async move {
4846 while latest < required_containers {
4847 latest = monitor.recv().await.expect("event missing");
4848 }
4849 }));
4850 }
4851 join_all(finalizers).await;
4852
4853 let idx = context.random_range(1..engines.len()); let validator = &participants[idx];
4856 let handle = engines.remove(idx);
4857 handle.abort();
4858 let _ = handle.await;
4859 reporters.remove(idx);
4860 info!(idx, ?validator, "aborted validator");
4861
4862 let mut finalizers = Vec::new();
4864 for reporter in reporters.iter_mut().skip(1) {
4865 let (mut latest, mut monitor) = reporter.subscribe().await;
4866 finalizers.push(context.child("finalizer").spawn(move |_| async move {
4867 while latest < View::new(required_containers.get() * 2) {
4868 latest = monitor.recv().await.expect("event missing");
4869 }
4870 }));
4871 }
4872 join_all(finalizers).await;
4873
4874 info!(idx, ?validator, "restarting validator");
4876 let context = context
4877 .child("validator_restarted")
4878 .with_attribute("public_key", validator);
4879
4880 let reporter_config = mocks::reporter::Config {
4882 participants: participants.clone().try_into().unwrap(),
4883 scheme: schemes[idx].clone(),
4884 elector: elector.clone(),
4885 };
4886 let reporter =
4887 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
4888 let (pending, recovered, resolver) =
4889 register_validator(&mut oracle, validator.clone()).await;
4890 reporters.push(reporter.clone());
4891 let application_cfg = mocks::application::Config::<Sha256, _> {
4892 relay: relay.clone(),
4893 me: validator.clone(),
4894 propose_latency: (10.0, 5.0),
4895 verify_latency: (10.0, 5.0),
4896 certify_latency: (10.0, 5.0),
4897 should_certify: mocks::application::Certifier::Always,
4898 };
4899 let (actor, application) =
4900 mocks::application::Application::new(context.child("application"), application_cfg);
4901 actor.start();
4902 let blocker = oracle.control(validator.clone());
4903 let cfg = config::Config {
4904 scheme: schemes[idx].clone(),
4905 elector: elector.clone(),
4906 blocker,
4907 automaton: application.clone(),
4908 relay: application.clone(),
4909 reporter: reporter.clone(),
4910 strategy: Sequential,
4911 partition: validator.to_string(),
4912 mailbox_size: NZUsize!(1024),
4913 epoch: Epoch::new(333),
4914 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(Epoch::new(
4915 333,
4916 ))),
4917 leader_timeout: Duration::from_secs(1),
4918 certification_timeout: Duration::from_secs(2),
4919 timeout_retry: Duration::from_secs(10),
4920 fetch_timeout: Duration::from_secs(1),
4921 view_retention,
4922 skip: SkipPolicy::Enabled {
4923 timeout: skip_timeout,
4924 budget: SkipBudget::Participants,
4925 },
4926 replay_buffer: NZUsize!(1024 * 1024),
4927 write_buffer: NZUsize!(1024 * 1024),
4928 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
4929 forward: ForwardPolicy::Disabled,
4930 track_historical_votes: false,
4931 };
4932 let engine = Engine::new(context.child("engine"), cfg);
4933 engine.start(pending, recovered, resolver);
4934
4935 let mut finalizers = Vec::new();
4937 for reporter in reporters.iter_mut().skip(1) {
4938 let (mut latest, mut monitor) = reporter.subscribe().await;
4939 finalizers.push(context.child("finalizer").spawn(move |_| async move {
4940 while latest < View::new(required_containers.get() * 3) {
4941 latest = monitor.recv().await.expect("event missing");
4942 }
4943 }));
4944 }
4945 join_all(finalizers).await;
4946
4947 let byz = &participants[0];
4951 let blocked = oracle.blocked().await.unwrap();
4952 for (a, b) in &blocked {
4953 assert_ne!(a, byz);
4954 assert_eq!(b, byz);
4955 }
4956 !blocked.is_empty()
4957 })
4958 }
4959
4960 fn equivocator<S, F, L>(fixture: F, elector: L)
4961 where
4962 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4963 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S> + Copy,
4964 L: elector::Config<S>,
4965 {
4966 let detected =
4967 (0..5).any(|seed| equivocator_seeded::<_, _, L>(seed, fixture, elector.clone()));
4968 assert!(
4969 detected,
4970 "expected at least one seed to detect equivocation"
4971 );
4972 }
4973
4974 test_for_all_fixtures!(equivocator);
4975
4976 #[test_group("slow")]
4977 #[test_traced]
4978 fn test_equivocator_stable_leader_optimistic() {
4979 let detected = (0..5).any(|seed| {
4980 equivocator_seeded_with_term::<_, _, RoundRobin>(
4981 seed,
4982 RoundRobin::default().with_term(
4983 TermLength::new(NZU32!(5)),
4984 Duration::from_secs(13),
4985 ViewDelta::new(2),
4986 ),
4987 ed25519::fixture,
4988 )
4989 });
4990 assert!(
4991 detected,
4992 "expected at least one seed to detect equivocation"
4993 );
4994 }
4995
4996 fn reconfigurer<S, F, L>(seed: u64, mut fixture: F, elector: L)
4997 where
4998 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
4999 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5000 L: elector::Config<S>,
5001 {
5002 let n = 4;
5004 let required_containers = View::new(50);
5005 let view_retention = ViewDelta::new(10);
5006 let skip_timeout = Duration::from_secs(11);
5007 let namespace = b"consensus".to_vec();
5008 let cfg = deterministic::Config::new()
5009 .with_seed(seed)
5010 .with_timeout(Some(Duration::from_secs(30)));
5011 let executor = deterministic::Runner::new(cfg);
5012 executor.start(|mut context| async move {
5013 let Fixture {
5015 participants,
5016 schemes,
5017 ..
5018 } = fixture(&mut context, &namespace, n);
5019 let mut oracle =
5020 start_test_network_with_peers(context.child("network"), participants.clone(), true)
5021 .await;
5022 let mut registrations = register_validators(&mut oracle, &participants).await;
5023
5024 let link = Link {
5026 latency: Duration::from_millis(10),
5027 jitter: Duration::from_millis(1),
5028 success_rate: probability!(1.0),
5029 };
5030 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5031
5032 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
5034 let mut reporters = Vec::new();
5035 for (idx_scheme, validator) in participants.iter().enumerate() {
5036 let context = context
5038 .child("validator")
5039 .with_attribute("public_key", validator);
5040
5041 let reporter_config = mocks::reporter::Config {
5043 participants: participants.clone().try_into().unwrap(),
5044 scheme: schemes[idx_scheme].clone(),
5045 elector: elector.clone(),
5046 };
5047 let reporter =
5048 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
5049 let (pending, recovered, resolver) = registrations
5050 .remove(validator)
5051 .expect("validator should be registered");
5052 if idx_scheme == 0 {
5053 let cfg = mocks::reconfigurer::Config {
5054 scheme: schemes[idx_scheme].clone(),
5055 };
5056 let engine: mocks::reconfigurer::Reconfigurer<_, _, Sha256> =
5057 mocks::reconfigurer::Reconfigurer::new(
5058 context.child("byzantine_engine"),
5059 cfg,
5060 );
5061 engine.start(pending);
5062 } else {
5063 reporters.push(reporter.clone());
5064 let application_cfg = mocks::application::Config::<Sha256, _> {
5065 relay: relay.clone(),
5066 me: validator.clone(),
5067 propose_latency: (10.0, 5.0),
5068 verify_latency: (10.0, 5.0),
5069 certify_latency: (10.0, 5.0),
5070 should_certify: mocks::application::Certifier::Always,
5071 };
5072 let (actor, application) = mocks::application::Application::new(
5073 context.child("application"),
5074 application_cfg,
5075 );
5076 actor.start();
5077 let blocker = oracle.control(validator.clone());
5078 let cfg = config::Config {
5079 scheme: schemes[idx_scheme].clone(),
5080 elector: elector.clone(),
5081 blocker,
5082 automaton: application.clone(),
5083 relay: application.clone(),
5084 reporter: reporter.clone(),
5085 strategy: Sequential,
5086 partition: validator.to_string(),
5087 mailbox_size: NZUsize!(1024),
5088 epoch: Epoch::new(333),
5089 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5090 Epoch::new(333),
5091 )),
5092 leader_timeout: Duration::from_secs(1),
5093 certification_timeout: Duration::from_secs(2),
5094 timeout_retry: Duration::from_secs(10),
5095 fetch_timeout: Duration::from_secs(1),
5096 view_retention,
5097 skip: SkipPolicy::Enabled {
5098 timeout: skip_timeout,
5099 budget: SkipBudget::Participants,
5100 },
5101 replay_buffer: NZUsize!(1024 * 1024),
5102 write_buffer: NZUsize!(1024 * 1024),
5103 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5104 forward: ForwardPolicy::Disabled,
5105 track_historical_votes: false,
5106 };
5107 let engine = Engine::new(context.child("engine"), cfg);
5108 engine.start(pending, recovered, resolver);
5109 }
5110 }
5111
5112 let mut finalizers = Vec::new();
5114 for reporter in reporters.iter_mut() {
5115 let (mut latest, mut monitor) = reporter.subscribe().await;
5116 finalizers.push(context.child("finalizer").spawn(move |_| async move {
5117 while latest < required_containers {
5118 latest = monitor.recv().await.expect("event missing");
5119 }
5120 }));
5121 }
5122 join_all(finalizers).await;
5123
5124 let byz = &participants[0];
5126 for reporter in reporters.iter() {
5127 reporter.assert_no_faults();
5129
5130 reporter.assert_no_invalid();
5132 }
5133
5134 let blocked = oracle.blocked().await.unwrap();
5136 assert!(!blocked.is_empty());
5137 for (a, b) in blocked {
5138 assert_ne!(&a, byz);
5139 assert_eq!(&b, byz);
5140 }
5141 });
5142 }
5143
5144 test_for_all_fixtures!(reconfigurer, seeds = 5);
5145
5146 fn nuller<S, F, L>(seed: u64, mut fixture: F, elector: L)
5147 where
5148 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5149 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5150 L: elector::Config<S>,
5151 {
5152 let n = 4;
5154 let required_containers = View::new(50);
5155 let view_retention = ViewDelta::new(10);
5156 let skip_timeout = Duration::from_secs(11);
5157 let namespace = b"consensus".to_vec();
5158 let cfg = deterministic::Config::new()
5159 .with_seed(seed)
5160 .with_timeout(Some(Duration::from_secs(30)));
5161 let executor = deterministic::Runner::new(cfg);
5162 executor.start(|mut context| async move {
5163 let Fixture {
5165 participants,
5166 schemes,
5167 ..
5168 } = fixture(&mut context, &namespace, n);
5169 let mut oracle =
5170 start_test_network_with_peers(context.child("network"), participants.clone(), true)
5171 .await;
5172 let mut registrations = register_validators(&mut oracle, &participants).await;
5173
5174 let link = Link {
5176 latency: Duration::from_millis(10),
5177 jitter: Duration::from_millis(1),
5178 success_rate: probability!(1.0),
5179 };
5180 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5181
5182 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
5184 let mut reporters = Vec::new();
5185 for (idx_scheme, validator) in participants.iter().enumerate() {
5186 let context = context
5188 .child("validator")
5189 .with_attribute("public_key", validator);
5190
5191 let reporter_config = mocks::reporter::Config {
5193 participants: participants.clone().try_into().unwrap(),
5194 scheme: schemes[idx_scheme].clone(),
5195 elector: elector.clone(),
5196 };
5197 let reporter =
5198 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
5199 let (pending, recovered, resolver) = registrations
5200 .remove(validator)
5201 .expect("validator should be registered");
5202 if idx_scheme == 0 {
5203 let cfg = mocks::nuller::Config {
5204 scheme: schemes[idx_scheme].clone(),
5205 };
5206 let engine: mocks::nuller::Nuller<_, _, Sha256> =
5207 mocks::nuller::Nuller::new(context.child("byzantine_engine"), cfg);
5208 engine.start(pending);
5209 } else {
5210 reporters.push(reporter.clone());
5211 let application_cfg = mocks::application::Config::<Sha256, _> {
5212 relay: relay.clone(),
5213 me: validator.clone(),
5214 propose_latency: (10.0, 5.0),
5215 verify_latency: (10.0, 5.0),
5216 certify_latency: (10.0, 5.0),
5217 should_certify: mocks::application::Certifier::Always,
5218 };
5219 let (actor, application) = mocks::application::Application::new(
5220 context.child("application"),
5221 application_cfg,
5222 );
5223 actor.start();
5224 let blocker = oracle.control(validator.clone());
5225 let cfg = config::Config {
5226 scheme: schemes[idx_scheme].clone(),
5227 elector: elector.clone(),
5228 blocker,
5229 automaton: application.clone(),
5230 relay: application.clone(),
5231 reporter: reporter.clone(),
5232 strategy: Sequential,
5233 partition: validator.clone().to_string(),
5234 mailbox_size: NZUsize!(1024),
5235 epoch: Epoch::new(333),
5236 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5237 Epoch::new(333),
5238 )),
5239 leader_timeout: Duration::from_secs(1),
5240 certification_timeout: Duration::from_secs(2),
5241 timeout_retry: Duration::from_secs(10),
5242 fetch_timeout: Duration::from_secs(1),
5243 view_retention,
5244 skip: SkipPolicy::Enabled {
5245 timeout: skip_timeout,
5246 budget: SkipBudget::Participants,
5247 },
5248 replay_buffer: NZUsize!(1024 * 1024),
5249 write_buffer: NZUsize!(1024 * 1024),
5250 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5251 forward: ForwardPolicy::Disabled,
5252 track_historical_votes: true,
5253 };
5254 let engine = Engine::new(context.child("engine"), cfg);
5255 engine.start(pending, recovered, resolver);
5256 }
5257 }
5258
5259 let mut finalizers = Vec::new();
5261 for reporter in reporters.iter_mut() {
5262 let (mut latest, mut monitor) = reporter.subscribe().await;
5263 finalizers.push(context.child("finalizer").spawn(move |_| async move {
5264 while latest < required_containers {
5265 latest = monitor.recv().await.expect("event missing");
5266 }
5267 }));
5268 }
5269 join_all(finalizers).await;
5270
5271 let byz = &participants[0];
5273 let mut count_nullify_and_finalize = 0;
5274 for reporter in reporters.iter() {
5275 {
5277 let faults = reporter.faults.lock();
5278 assert_eq!(faults.len(), 1);
5279 let faulter = faults.get(byz).expect("byzantine party is not faulter");
5280 for faults in faulter.values() {
5281 for fault in faults.iter() {
5282 match fault {
5283 Activity::NullifyFinalize(_) => {
5284 count_nullify_and_finalize += 1;
5285 }
5286 _ => panic!("unexpected fault: {fault:?}"),
5287 }
5288 }
5289 }
5290 }
5291
5292 reporter.assert_no_invalid();
5294 }
5295 assert!(count_nullify_and_finalize > 0);
5296
5297 let blocked = oracle.blocked().await.unwrap();
5299 assert!(!blocked.is_empty());
5300 for (a, b) in blocked {
5301 assert_ne!(&a, byz);
5302 assert_eq!(&b, byz);
5303 }
5304 });
5305 }
5306
5307 test_for_all_fixtures!(nuller, seeds = 5);
5308
5309 fn outdated<S, F, L>(seed: u64, mut fixture: F, elector: L)
5310 where
5311 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5312 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5313 L: elector::Config<S>,
5314 {
5315 let n = 4;
5317 let required_containers = View::new(100);
5318 let view_retention = ViewDelta::new(10);
5319 let skip_timeout = Duration::from_secs(11);
5320 let namespace = b"consensus".to_vec();
5321 let cfg = deterministic::Config::new()
5322 .with_seed(seed)
5323 .with_timeout(Some(Duration::from_secs(60)));
5324 let executor = deterministic::Runner::new(cfg);
5325 executor.start(|mut context| async move {
5326 let Fixture {
5328 participants,
5329 schemes,
5330 ..
5331 } = fixture(&mut context, &namespace, n);
5332 let mut oracle =
5333 start_test_network_with_peers(context.child("network"), participants.clone(), true)
5334 .await;
5335 let mut registrations = register_validators(&mut oracle, &participants).await;
5336
5337 let link = Link {
5339 latency: Duration::from_millis(10),
5340 jitter: Duration::from_millis(1),
5341 success_rate: probability!(1.0),
5342 };
5343 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5344
5345 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
5347 let mut reporters = Vec::new();
5348 for (idx_scheme, validator) in participants.iter().enumerate() {
5349 let context = context
5351 .child("validator")
5352 .with_attribute("public_key", validator);
5353
5354 let reporter_config = mocks::reporter::Config {
5356 participants: participants.clone().try_into().unwrap(),
5357 scheme: schemes[idx_scheme].clone(),
5358 elector: elector.clone(),
5359 };
5360 let reporter =
5361 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
5362 let (pending, recovered, resolver) = registrations
5363 .remove(validator)
5364 .expect("validator should be registered");
5365 if idx_scheme == 0 {
5366 let cfg = mocks::outdated::Config {
5367 scheme: schemes[idx_scheme].clone(),
5368 view_delta: ViewDelta::new(view_retention.get().saturating_mul(4)),
5369 };
5370 let engine: mocks::outdated::Outdated<_, _, Sha256> =
5371 mocks::outdated::Outdated::new(context.child("byzantine_engine"), cfg);
5372 engine.start(pending);
5373 } else {
5374 reporters.push(reporter.clone());
5375 let application_cfg = mocks::application::Config::<Sha256, _> {
5376 relay: relay.clone(),
5377 me: validator.clone(),
5378 propose_latency: (10.0, 5.0),
5379 verify_latency: (10.0, 5.0),
5380 certify_latency: (10.0, 5.0),
5381 should_certify: mocks::application::Certifier::Always,
5382 };
5383 let (actor, application) = mocks::application::Application::new(
5384 context.child("application"),
5385 application_cfg,
5386 );
5387 actor.start();
5388 let blocker = oracle.control(validator.clone());
5389 let cfg = config::Config {
5390 scheme: schemes[idx_scheme].clone(),
5391 elector: elector.clone(),
5392 blocker,
5393 automaton: application.clone(),
5394 relay: application.clone(),
5395 reporter: reporter.clone(),
5396 strategy: Sequential,
5397 partition: validator.clone().to_string(),
5398 mailbox_size: NZUsize!(1024),
5399 epoch: Epoch::new(333),
5400 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5401 Epoch::new(333),
5402 )),
5403 leader_timeout: Duration::from_secs(1),
5404 certification_timeout: Duration::from_secs(2),
5405 timeout_retry: Duration::from_secs(10),
5406 fetch_timeout: Duration::from_secs(1),
5407 view_retention,
5408 skip: SkipPolicy::Enabled {
5409 timeout: skip_timeout,
5410 budget: SkipBudget::Participants,
5411 },
5412 replay_buffer: NZUsize!(1024 * 1024),
5413 write_buffer: NZUsize!(1024 * 1024),
5414 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5415 forward: ForwardPolicy::Disabled,
5416 track_historical_votes: false,
5417 };
5418 let engine = Engine::new(context.child("engine"), cfg);
5419 engine.start(pending, recovered, resolver);
5420 }
5421 }
5422
5423 let mut finalizers = Vec::new();
5425 for reporter in reporters.iter_mut() {
5426 let (mut latest, mut monitor) = reporter.subscribe().await;
5427 finalizers.push(context.child("finalizer").spawn(move |_| async move {
5428 while latest < required_containers {
5429 latest = monitor.recv().await.expect("event missing");
5430 }
5431 }));
5432 }
5433 join_all(finalizers).await;
5434
5435 for reporter in reporters.iter() {
5437 reporter.assert_no_faults();
5439
5440 reporter.assert_no_invalid();
5442 }
5443
5444 let blocked = oracle.blocked().await.unwrap();
5446 assert!(blocked.is_empty());
5447 });
5448 }
5449
5450 test_for_all_fixtures!(outdated, seeds = 5);
5451
5452 fn run_1k<S, F, L>(mut fixture: F, elector: L)
5453 where
5454 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5455 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5456 L: elector::Config<S>,
5457 {
5458 let n = 10;
5460 let required_containers = View::new(1_000);
5461 let view_retention = ViewDelta::new(10);
5462 let skip_timeout = Duration::from_secs(11);
5463 let namespace = b"consensus".to_vec();
5464 let cfg = deterministic::Config::new();
5465 let executor = deterministic::Runner::new(cfg);
5466 executor.start(|mut context| async move {
5467 let Fixture {
5469 participants,
5470 schemes,
5471 ..
5472 } = fixture(&mut context, &namespace, n);
5473 let mut oracle =
5474 start_test_network_with_peers(context.child("network"), participants.clone(), true)
5475 .await;
5476 let mut registrations = register_validators(&mut oracle, &participants).await;
5477
5478 let link = Link {
5480 latency: Duration::from_millis(80),
5481 jitter: Duration::from_millis(10),
5482 success_rate: probability!(0.98),
5483 };
5484 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5485
5486 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
5488 let mut reporters = Vec::new();
5489 let mut engine_handlers = Vec::new();
5490 for (idx, validator) in participants.iter().enumerate() {
5491 let context = context
5493 .child("validator")
5494 .with_attribute("public_key", validator);
5495
5496 let reporter_config = mocks::reporter::Config {
5498 participants: participants.clone().try_into().unwrap(),
5499 scheme: schemes[idx].clone(),
5500 elector: elector.clone(),
5501 };
5502 let reporter =
5503 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
5504 reporters.push(reporter.clone());
5505 let application_cfg = mocks::application::Config::<Sha256, _> {
5506 relay: relay.clone(),
5507 me: validator.clone(),
5508 propose_latency: (100.0, 50.0),
5509 verify_latency: (50.0, 40.0),
5510 certify_latency: (50.0, 40.0),
5511 should_certify: mocks::application::Certifier::Always,
5512 };
5513 let (actor, application) = mocks::application::Application::new(
5514 context.child("application"),
5515 application_cfg,
5516 );
5517 actor.start();
5518 let blocker = oracle.control(validator.clone());
5519 let cfg = config::Config {
5520 scheme: schemes[idx].clone(),
5521 elector: elector.clone(),
5522 blocker,
5523 automaton: application.clone(),
5524 relay: application.clone(),
5525 reporter: reporter.clone(),
5526 strategy: Sequential,
5527 partition: validator.to_string(),
5528 mailbox_size: NZUsize!(1024),
5529 epoch: Epoch::new(333),
5530 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5531 Epoch::new(333),
5532 )),
5533 leader_timeout: Duration::from_secs(1),
5534 certification_timeout: Duration::from_secs(2),
5535 timeout_retry: Duration::from_secs(10),
5536 fetch_timeout: Duration::from_secs(1),
5537 view_retention,
5538 skip: SkipPolicy::Enabled {
5539 timeout: skip_timeout,
5540 budget: SkipBudget::Participants,
5541 },
5542 replay_buffer: NZUsize!(1024 * 1024),
5543 write_buffer: NZUsize!(1024 * 1024),
5544 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5545 forward: ForwardPolicy::Disabled,
5546 track_historical_votes: false,
5547 };
5548 let engine = Engine::new(context.child("engine"), cfg);
5549
5550 let (pending, recovered, resolver) = registrations
5552 .remove(validator)
5553 .expect("validator should be registered");
5554 engine_handlers.push(engine.start(pending, recovered, resolver));
5555 }
5556
5557 let mut finalizers = Vec::new();
5559 for reporter in reporters.iter_mut() {
5560 let (mut latest, mut monitor) = reporter.subscribe().await;
5561 finalizers.push(context.child("finalizer").spawn(move |_| async move {
5562 while latest < required_containers {
5563 latest = monitor.recv().await.expect("event missing");
5564 }
5565 }));
5566 }
5567 join_all(finalizers).await;
5568
5569 for reporter in reporters.iter() {
5571 reporter.assert_no_faults();
5573
5574 reporter.assert_no_invalid();
5576 }
5577
5578 let blocked = oracle.blocked().await.unwrap();
5580 assert!(blocked.is_empty());
5581 })
5582 }
5583
5584 #[test_group("slow")]
5585 #[test_traced]
5586 fn test_1k() {
5587 run_1k::<_, _, RoundRobin>(scheme_mocks::fixture, RoundRobin::default());
5588 }
5589
5590 fn engine_shutdown<S, F, L>(seed: u64, mut fixture: F, elector: L, graceful: bool)
5591 where
5592 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5593 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5594 L: elector::Config<S>,
5595 {
5596 let n = 1;
5597 let namespace = b"consensus".to_vec();
5598 let cfg = deterministic::Config::default()
5599 .with_seed(seed)
5600 .with_timeout(Some(Duration::from_secs(10)));
5601 let executor = deterministic::Runner::new(cfg);
5602 executor.start(|mut context| async move {
5603 let Fixture {
5605 participants,
5606 schemes,
5607 ..
5608 } = fixture(&mut context, &namespace, n);
5609 let mut oracle =
5610 start_test_network_with_peers(context.child("network"), participants.clone(), true)
5611 .await;
5612 let mut registrations = register_validators(&mut oracle, &participants).await;
5613
5614 let link = Link {
5616 latency: Duration::from_millis(1),
5617 jitter: Duration::from_millis(0),
5618 success_rate: probability!(1.0),
5619 };
5620 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5621
5622 let reporter_config = mocks::reporter::Config {
5624 participants: participants.clone().try_into().unwrap(),
5625 scheme: schemes[0].clone(),
5626 elector: elector.clone(),
5627 };
5628 let reporter =
5629 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
5630 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
5631 let application_cfg = mocks::application::Config::<Sha256, _> {
5632 relay: relay.clone(),
5633 me: participants[0].clone(),
5634 propose_latency: (1.0, 0.0),
5635 verify_latency: (1.0, 0.0),
5636 certify_latency: (1.0, 0.0),
5637 should_certify: mocks::application::Certifier::Always,
5638 };
5639 let (actor, application) =
5640 mocks::application::Application::new(context.child("application"), application_cfg);
5641 actor.start();
5642 let blocker = oracle.control(participants[0].clone());
5643 let cfg = config::Config {
5644 scheme: schemes[0].clone(),
5645 elector: elector.clone(),
5646 blocker,
5647 automaton: application.clone(),
5648 relay: application.clone(),
5649 reporter: reporter.clone(),
5650 strategy: Sequential,
5651 partition: participants[0].clone().to_string(),
5652 mailbox_size: NZUsize!(64),
5653 epoch: Epoch::new(333),
5654 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(Epoch::new(
5655 333,
5656 ))),
5657 leader_timeout: Duration::from_millis(50),
5658 certification_timeout: Duration::from_millis(100),
5659 timeout_retry: Duration::from_millis(250),
5660 fetch_timeout: Duration::from_millis(50),
5661 view_retention: ViewDelta::new(4),
5662 skip: SkipPolicy::Enabled {
5663 timeout: Duration::from_secs(2),
5664 budget: SkipBudget::Participants,
5665 },
5666 replay_buffer: NZUsize!(1024 * 16),
5667 write_buffer: NZUsize!(1024 * 16),
5668 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5669 forward: ForwardPolicy::Disabled,
5670 track_historical_votes: false,
5671 };
5672 let engine = Engine::new(context.child("engine"), cfg);
5673
5674 let (pending, recovered, resolver) = registrations
5676 .remove(&participants[0])
5677 .expect("validator should be registered");
5678 let handle = engine.start(pending, recovered, resolver);
5679
5680 context.sleep(Duration::from_millis(1000)).await;
5682
5683 let running_before = count_running_tasks(&context, "engine");
5685 assert!(
5686 running_before > 0,
5687 "at least one engine task should be running"
5688 );
5689
5690 context.sleep(Duration::from_millis(1500)).await;
5692 assert!(
5693 count_running_tasks(&context, "engine") > 0,
5694 "engine tasks should still be running"
5695 );
5696
5697 let running_after = if graceful {
5699 let result = context
5700 .child("stop")
5701 .stop(0, Some(Duration::from_secs(5)))
5702 .await;
5703 assert!(
5704 result.is_ok(),
5705 "graceful shutdown should complete: {result:?}"
5706 );
5707 count_running_tasks(&context, "engine")
5708 } else {
5709 handle.abort();
5710 let _ = handle.await; context.sleep(Duration::from_millis(1000)).await;
5714 count_running_tasks(&context, "engine")
5715 };
5716 assert_eq!(
5717 running_after, 0,
5718 "all engine tasks should be stopped, but {running_after} still running"
5719 );
5720 });
5721 }
5722
5723 fn children_shutdown_on_engine_abort<S, F, L>(seed: u64, fixture: F, elector: L)
5724 where
5725 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5726 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5727 L: elector::Config<S>,
5728 {
5729 engine_shutdown::<S, F, L>(seed, fixture, elector, false);
5730 }
5731
5732 test_for_all_fixtures!(children_shutdown_on_engine_abort, seeds = 10);
5733
5734 fn graceful_shutdown<S, F, L>(seed: u64, fixture: F, elector: L)
5735 where
5736 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5737 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5738 L: elector::Config<S>,
5739 {
5740 engine_shutdown::<S, F, L>(seed, fixture, elector, true);
5741 }
5742
5743 test_for_all_fixtures!(graceful_shutdown, seeds = 10);
5744
5745 fn attributable_reporter_filtering<S, F, L>(mut fixture: F, elector: L)
5746 where
5747 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5748 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5749 L: elector::Config<S>,
5750 {
5751 let n = 3;
5752 let required_containers = View::new(10);
5753 let view_retention = ViewDelta::new(10);
5754 let skip_timeout = Duration::from_secs(11);
5755 let namespace = b"consensus".to_vec();
5756 let executor = deterministic::Runner::timed(Duration::from_secs(30));
5757 executor.start(|mut context| async move {
5758 let Fixture {
5760 participants,
5761 schemes,
5762 ..
5763 } = fixture(&mut context, &namespace, n);
5764 let mut oracle = start_test_network_with_peers(
5765 context.child("network"),
5766 participants.clone(),
5767 false,
5768 )
5769 .await;
5770 let mut registrations = register_validators(&mut oracle, &participants).await;
5771
5772 let link = Link {
5774 latency: Duration::from_millis(10),
5775 jitter: Duration::from_millis(1),
5776 success_rate: probability!(1.0),
5777 };
5778 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
5779
5780 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
5782 let mut reporters = Vec::new();
5783 for (idx, validator) in participants.iter().enumerate() {
5784 let context = context
5785 .child("validator")
5786 .with_attribute("public_key", validator);
5787
5788 let reporter_config = mocks::reporter::Config {
5789 participants: participants.clone().try_into().unwrap(),
5790 scheme: schemes[idx].clone(),
5791 elector: elector.clone(),
5792 };
5793 let mock_reporter =
5794 mocks::reporter::Reporter::new(context.child("mock_reporter"), reporter_config);
5795
5796 let attributable_reporter = scheme::reporter::AttributableReporter::new(
5798 context.child("rng"),
5799 schemes[idx].clone(),
5800 mock_reporter.clone(),
5801 Sequential,
5802 true, );
5804 reporters.push(mock_reporter.clone());
5805
5806 let application_cfg = mocks::application::Config::<Sha256, _> {
5807 relay: relay.clone(),
5808 me: validator.clone(),
5809 propose_latency: (10.0, 5.0),
5810 verify_latency: (10.0, 5.0),
5811 certify_latency: (10.0, 5.0),
5812 should_certify: mocks::application::Certifier::Always,
5813 };
5814 let (actor, application) = mocks::application::Application::new(
5815 context.child("application"),
5816 application_cfg,
5817 );
5818 actor.start();
5819 let blocker = oracle.control(validator.clone());
5820 let cfg = config::Config {
5821 scheme: schemes[idx].clone(),
5822 elector: elector.clone(),
5823 blocker,
5824 automaton: application.clone(),
5825 relay: application.clone(),
5826 reporter: attributable_reporter,
5827 strategy: Sequential,
5828 partition: validator.to_string(),
5829 mailbox_size: NZUsize!(1024),
5830 epoch: Epoch::new(333),
5831 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
5832 Epoch::new(333),
5833 )),
5834 leader_timeout: Duration::from_secs(1),
5835 certification_timeout: Duration::from_secs(2),
5836 timeout_retry: Duration::from_secs(10),
5837 fetch_timeout: Duration::from_secs(1),
5838 view_retention,
5839 skip: SkipPolicy::Enabled {
5840 timeout: skip_timeout,
5841 budget: SkipBudget::Participants,
5842 },
5843 replay_buffer: NZUsize!(1024 * 1024),
5844 write_buffer: NZUsize!(1024 * 1024),
5845 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
5846 forward: ForwardPolicy::Disabled,
5847 track_historical_votes: false,
5848 };
5849 let engine = Engine::new(context.child("engine"), cfg);
5850
5851 let (pending, recovered, resolver) = registrations
5853 .remove(validator)
5854 .expect("validator should be registered");
5855 engine.start(pending, recovered, resolver);
5856 }
5857
5858 let mut finalizers = Vec::new();
5860 for reporter in reporters.iter_mut() {
5861 let (mut latest, mut monitor) = reporter.subscribe().await;
5862 finalizers.push(context.child("finalizer").spawn(move |_| async move {
5863 while latest < required_containers {
5864 latest = monitor.recv().await.expect("event missing");
5865 }
5866 }));
5867 }
5868 join_all(finalizers).await;
5869
5870 for reporter in reporters.iter() {
5872 reporter.assert_no_faults();
5874
5875 reporter.assert_no_invalid();
5877
5878 {
5880 let notarizations = reporter.notarizations.lock();
5881 let finalizations = reporter.finalizations.lock();
5882 assert!(
5883 !notarizations.is_empty() || !finalizations.is_empty(),
5884 "Certificates should be reported"
5885 );
5886 }
5887
5888 let notarizes = reporter.notarizes.lock();
5890 let last_view = notarizes.keys().max().cloned().unwrap_or_default();
5891 for (view, payloads) in notarizes.iter() {
5892 if *view == last_view {
5893 continue; }
5895
5896 let signers: usize = payloads.values().map(|signers| signers.len()).sum();
5897
5898 if S::is_attributable() {
5900 assert!(signers > 1, "view {view}: {signers}");
5901 } else {
5902 assert_eq!(signers, 0);
5904 }
5905 }
5906
5907 let finalizes = reporter.finalizes.lock();
5909 for payloads in finalizes.values() {
5910 let signers: usize = payloads.values().map(|signers| signers.len()).sum();
5911
5912 if S::is_attributable() {
5914 assert!(signers > 1);
5915 } else {
5916 assert_eq!(signers, 0);
5918 }
5919 }
5920 }
5921
5922 let blocked = oracle.blocked().await.unwrap();
5924 assert!(blocked.is_empty());
5925 });
5926 }
5927
5928 test_for_all_fixtures!(attributable_reporter_filtering);
5929
5930 fn split_views_no_lockup<S, F, L>(mut fixture: F, elector: L)
5931 where
5932 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
5933 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
5934 L: elector::Config<S>,
5935 {
5936 enum ParticipantType {
5949 Group1, Group2, Ignorant, Byzantine, }
5954 let get_type = |idx: usize| -> ParticipantType {
5955 match idx {
5956 0..3 => ParticipantType::Group1,
5957 3..6 => ParticipantType::Group2,
5958 6 => ParticipantType::Ignorant,
5959 7..10 => ParticipantType::Byzantine,
5960 _ => unreachable!(),
5961 }
5962 };
5963
5964 let n = 10;
5966 let quorum = quorum(n) as usize;
5967 assert_eq!(quorum, 7);
5968 let view_retention = ViewDelta::new(10);
5969 let skip_timeout = Duration::from_secs(12);
5970 let namespace = b"consensus".to_vec();
5971 let executor = deterministic::Runner::timed(Duration::from_secs(300));
5972 executor.start(|mut context| async move {
5973 let Fixture {
5975 participants,
5976 schemes,
5977 ..
5978 } = fixture(&mut context, &namespace, n);
5979 let mut oracle = start_test_network_with_peers(
5980 context.child("network"),
5981 participants.clone(),
5982 false,
5983 )
5984 .await;
5985 let mut registrations = register_validators(&mut oracle, &participants).await;
5986
5987 let build_finalization = |proposal: &Proposal<D>| -> TFinalization<_, D> {
5991 let votes: Vec<_> = (0..=quorum)
5992 .map(|i| TFinalize::sign(&schemes[i], proposal.clone()).unwrap())
5993 .collect();
5994 TFinalization::from_finalizes(&schemes[0], non_empty![@&votes], &Sequential)
5995 .expect("finalization quorum")
5996 };
5997 let build_notarization = |proposal: &Proposal<D>| -> TNotarization<_, D> {
5999 let votes: Vec<_> = (0..=quorum)
6000 .map(|i| TNotarize::sign(&schemes[i], proposal.clone()).unwrap())
6001 .collect();
6002 TNotarization::from_notarizes(&schemes[0], non_empty![@&votes], &Sequential)
6003 .expect("notarization quorum")
6004 };
6005 let build_nullification = |round: Round| -> TNullification<_> {
6006 let votes: Vec<_> = (0..=quorum)
6007 .map(|i| TNullify::sign::<D>(&schemes[i], round).unwrap())
6008 .collect();
6009 TNullification::from_nullifies(&schemes[0], non_empty![@&votes], &Sequential)
6010 .expect("nullification quorum")
6011 };
6012 let f_view = 1;
6014 let round_f = Round::new(Epoch::new(333), View::new(f_view));
6015 let payload_b0 = Sha256::hash(&[b"B_F"]);
6016 let proposal_b0 = Proposal::new(round_f, View::new(f_view - 1), payload_b0);
6017 let payload_b1a = Sha256::hash(&[b"B_G1"]);
6018 let proposal_b1a = Proposal::new(
6019 Round::new(Epoch::new(333), View::new(f_view + 1)),
6020 View::new(f_view),
6021 payload_b1a,
6022 );
6023 let payload_b1b = Sha256::hash(&[b"B_G2"]);
6024 let proposal_b1b = Proposal::new(
6025 Round::new(Epoch::new(333), View::new(f_view + 2)),
6026 View::new(f_view),
6027 payload_b1b,
6028 );
6029
6030 let b0_notarization = build_notarization(&proposal_b0);
6032 let b0_finalization = build_finalization(&proposal_b0);
6033 let b1a_notarization = build_notarization(&proposal_b1a);
6035 let b1b_notarization = build_notarization(&proposal_b1b);
6036 let null_a = build_nullification(Round::new(Epoch::new(333), View::new(f_view + 1)));
6038 let null_b = build_nullification(Round::new(Epoch::new(333), View::new(f_view + 2)));
6039
6040 let link = Link {
6042 latency: Duration::from_millis(10),
6043 jitter: Duration::from_millis(0),
6044 success_rate: probability!(1.0),
6045 };
6046 let mut injector_sender =
6047 start_certificate_injector(&context, &mut oracle, &participants, &link).await;
6048
6049 let msg = Certificate::<_, D>::Notarization(b0_notarization).encode();
6053 injector_sender.send(Recipients::All, msg, true);
6054 let msg = Certificate::<_, D>::Finalization(b0_finalization).encode();
6055 injector_sender.send(Recipients::All, msg, true);
6056 let notarization_msg = Certificate::<_, D>::Notarization(b1a_notarization);
6058 let nullification_msg = Certificate::<_, D>::Nullification(null_a.clone());
6059 for (i, participant) in participants.iter().enumerate() {
6060 let recipient = Recipients::One(participant.clone());
6061 let msg = match get_type(i) {
6062 ParticipantType::Group1 => notarization_msg.encode(),
6063 _ => nullification_msg.encode(),
6064 };
6065 injector_sender.send(recipient, msg, true);
6066 }
6067 let notarization_msg = Certificate::<_, D>::Notarization(b1b_notarization);
6069 let nullification_msg = Certificate::<_, D>::Nullification(null_b.clone());
6070 for (i, participant) in participants.iter().enumerate() {
6071 let recipient = Recipients::One(participant.clone());
6072 let msg = match get_type(i) {
6073 ParticipantType::Group2 => notarization_msg.encode(),
6074 _ => nullification_msg.encode(),
6075 };
6076 injector_sender.send(recipient, msg, true);
6077 }
6078
6079 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
6085 let mut honest_reporters = Vec::new();
6086 for (idx, validator) in participants.iter().enumerate() {
6087 let (pending, recovered, resolver) = registrations
6088 .remove(validator)
6089 .expect("validator should be registered");
6090 let participant_type = get_type(idx);
6091 if matches!(participant_type, ParticipantType::Byzantine) {
6092 let cfg = mocks::nullify_only::Config {
6094 scheme: schemes[idx].clone(),
6095 };
6096 let engine: mocks::nullify_only::NullifyOnly<_, _, Sha256> =
6097 mocks::nullify_only::NullifyOnly::new(
6098 context
6099 .child("byzantine")
6100 .with_attribute("public_key", validator),
6101 cfg,
6102 );
6103 engine.start(pending);
6104 drop(recovered);
6106 drop(resolver);
6107 } else {
6108 let reporter_config = mocks::reporter::Config {
6110 participants: participants.clone().try_into().unwrap(),
6111 scheme: schemes[idx].clone(),
6112 elector: elector.clone(),
6113 };
6114 let reporter = mocks::reporter::Reporter::new(
6115 context
6116 .child("reporter")
6117 .with_attribute("public_key", validator),
6118 reporter_config,
6119 );
6120 honest_reporters.push(reporter.clone());
6121
6122 let application_cfg = mocks::application::Config::<Sha256, _> {
6123 relay: relay.clone(),
6124 me: validator.clone(),
6125 propose_latency: (250.0, 50.0), verify_latency: (10.0, 5.0),
6127 certify_latency: (10.0, 5.0),
6128 should_certify: mocks::application::Certifier::Always,
6129 };
6130 let (actor, application) = mocks::application::Application::new(
6131 context
6132 .child("application")
6133 .with_attribute("public_key", validator),
6134 application_cfg,
6135 );
6136 actor.start();
6137 let blocker = oracle.control(validator.clone());
6138 let cfg = config::Config {
6139 scheme: schemes[idx].clone(),
6140 elector: elector.clone(),
6141 blocker,
6142 automaton: application.clone(),
6143 relay: application.clone(),
6144 reporter: reporter.clone(),
6145 strategy: Sequential,
6146 partition: validator.to_string(),
6147 mailbox_size: NZUsize!(1024),
6148 epoch: Epoch::new(333),
6149 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
6150 Epoch::new(333),
6151 )),
6152 leader_timeout: Duration::from_secs(10),
6153 certification_timeout: Duration::from_secs(11),
6154 timeout_retry: Duration::from_secs(10),
6155 fetch_timeout: Duration::from_secs(1),
6156 view_retention,
6157 skip: SkipPolicy::Enabled {
6158 timeout: skip_timeout,
6159 budget: SkipBudget::Participants,
6160 },
6161 replay_buffer: NZUsize!(1024 * 1024),
6162 write_buffer: NZUsize!(1024 * 1024),
6163 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
6164 forward: ForwardPolicy::Disabled,
6165 track_historical_votes: false,
6166 };
6167 let engine = Engine::new(
6168 context
6169 .child("engine")
6170 .with_attribute("public_key", validator),
6171 cfg,
6172 );
6173 engine.start(pending, recovered, resolver);
6174 }
6175 }
6176
6177 context.sleep(Duration::from_secs(2)).await;
6179
6180 let view = View::new(f_view);
6185 for reporter in honest_reporters.iter() {
6186 let finalizations = reporter.finalizations.lock();
6187 assert!(finalizations.contains_key(&view));
6188 }
6189
6190 let view = View::new(f_view + 1);
6194 for (i, reporter) in honest_reporters.iter().enumerate() {
6195 let finalizations = reporter.finalizations.lock();
6196 assert!(!finalizations.contains_key(&view));
6197 let nullifications = reporter.nullifications.lock();
6198 let notarizations = reporter.notarizations.lock();
6199 match get_type(i) {
6200 ParticipantType::Group1 => {
6201 assert!(notarizations.contains_key(&view));
6202 assert!(!nullifications.contains_key(&view));
6203 }
6204 _ => {
6205 assert!(nullifications.contains_key(&view));
6206 assert!(!notarizations.contains_key(&view));
6207 }
6208 }
6209 }
6210
6211 let view = View::new(f_view + 2);
6215 for (i, reporter) in honest_reporters.iter().enumerate() {
6216 let finalizations = reporter.finalizations.lock();
6217 assert!(!finalizations.contains_key(&view));
6218 let nullifications = reporter.nullifications.lock();
6219 let notarizations = reporter.notarizations.lock();
6220 match get_type(i) {
6221 ParticipantType::Group2 => {
6222 assert!(notarizations.contains_key(&view));
6223 assert!(!nullifications.contains_key(&view));
6224 }
6225 _ => {
6226 assert!(nullifications.contains_key(&view));
6227 assert!(!notarizations.contains_key(&view));
6228 }
6229 }
6230 }
6231
6232 let next_view = View::new(f_view + 3);
6234 for (i, reporter) in honest_reporters.iter().enumerate() {
6235 let nullifies = reporter.nullifies.lock();
6236 assert!(!nullifies.contains_key(&next_view), "reporter {i}");
6237 }
6238
6239 link_validators(&mut oracle, &participants, Action::Link(link.clone()), None).await;
6243
6244 {
6246 let target = View::new(f_view + 3);
6247 let mut finalizers = Vec::new();
6248 for reporter in honest_reporters.iter_mut() {
6249 let (mut latest, mut monitor) = reporter.subscribe().await;
6250 finalizers.push(
6251 context
6252 .child("resume_finalizer")
6253 .spawn(move |_| async move {
6254 while latest < target {
6255 latest = monitor.recv().await.expect("event missing");
6256 }
6257 }),
6258 );
6259 }
6260 join_all(finalizers).await;
6261 }
6262
6263 for reporter in honest_reporters.iter() {
6265 reporter.assert_no_faults();
6266 reporter.assert_no_invalid();
6267 }
6268 let blocked = oracle.blocked().await.unwrap();
6269 assert!(blocked.is_empty(), "blocked peers: {blocked:?}");
6270 });
6271 }
6272
6273 test_for_all_fixtures!(split_views_no_lockup);
6274
6275 type CertifiedSplitReporter<S, L> =
6276 mocks::reporter::Reporter<deterministic::Context, S, L, Sha256Digest>;
6277
6278 struct CertifiedSplitEngineConfig<'a, S, L> {
6279 oracle: &'a Oracle<PublicKey, deterministic::Context>,
6280 participants: &'a [PublicKey],
6281 schemes: &'a [S],
6282 registrations: &'a mut TestRegistrations,
6283 silent: usize,
6284 elector: &'a L,
6285 epoch: Epoch,
6286 view_retention: ViewDelta,
6287 skip_timeout: Duration,
6288 }
6289
6290 async fn start_certificate_injector(
6293 context: &deterministic::Context,
6294 oracle: &mut Oracle<PublicKey, deterministic::Context>,
6295 participants: &[PublicKey],
6296 link: &Link,
6297 ) -> Sender<PublicKey, deterministic::Context> {
6298 let injector = PrivateKey::from_seed(1_000_000).public_key();
6299 let (sender, _receiver) = oracle
6300 .control(injector.clone())
6301 .register(1, TEST_QUOTA)
6302 .await
6303 .unwrap();
6304 for participant in participants {
6305 oracle
6306 .add_link(injector.clone(), participant.clone(), link.clone())
6307 .await
6308 .unwrap();
6309 }
6310 oracle.manager().track(
6311 1,
6312 TrackedPeers::new(
6313 Set::from_iter_dedup(participants.iter().cloned()),
6314 Set::from_iter_dedup(std::iter::once(injector)),
6315 ),
6316 );
6317 context.sleep(Duration::from_millis(10)).await;
6318 sender
6319 }
6320
6321 fn start_certified_split_engines<S, L>(
6324 context: &deterministic::Context,
6325 cfg: CertifiedSplitEngineConfig<'_, S, L>,
6326 ) -> HashMap<usize, CertifiedSplitReporter<S, L>>
6327 where
6328 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
6329 L: elector::Config<S>,
6330 {
6331 let CertifiedSplitEngineConfig {
6332 oracle,
6333 participants,
6334 schemes,
6335 registrations,
6336 silent,
6337 elector,
6338 epoch,
6339 view_retention,
6340 skip_timeout,
6341 } = cfg;
6342 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
6343 let mut reporters = HashMap::new();
6344
6345 for (idx, validator) in participants.iter().enumerate() {
6346 let registration = registrations
6347 .remove(validator)
6348 .expect("validator should be registered");
6349 if idx == silent {
6350 drop(registration);
6351 continue;
6352 }
6353
6354 let reporter_config = mocks::reporter::Config {
6355 participants: participants.to_vec().try_into().unwrap(),
6356 scheme: schemes[idx].clone(),
6357 elector: elector.clone(),
6358 };
6359 let reporter = mocks::reporter::Reporter::new(
6360 context
6361 .child("reporter")
6362 .with_attribute("public_key", validator),
6363 reporter_config,
6364 );
6365 reporters.insert(idx, reporter.clone());
6366
6367 let application_cfg = mocks::application::Config::<Sha256, _> {
6368 relay: relay.clone(),
6369 me: validator.clone(),
6370 propose_latency: (250.0, 50.0), verify_latency: (10.0, 5.0),
6372 certify_latency: (10.0, 5.0),
6373 should_certify: mocks::application::Certifier::Always,
6374 };
6375 let (actor, application) = mocks::application::Application::new(
6376 context
6377 .child("application")
6378 .with_attribute("public_key", validator),
6379 application_cfg,
6380 );
6381 actor.start();
6382
6383 let cfg = config::Config {
6384 scheme: schemes[idx].clone(),
6385 elector: elector.clone(),
6386 blocker: oracle.control(validator.clone()),
6387 automaton: application.clone(),
6388 relay: application.clone(),
6389 reporter: reporter.clone(),
6390 strategy: Sequential,
6391 partition: validator.to_string(),
6392 mailbox_size: NZUsize!(1024),
6393 epoch,
6394 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(epoch)),
6395 leader_timeout: Duration::from_secs(10),
6396 certification_timeout: Duration::from_secs(11),
6397 timeout_retry: Duration::from_secs(10),
6398 fetch_timeout: Duration::from_secs(1),
6399 view_retention,
6400 skip: SkipPolicy::Enabled {
6401 timeout: skip_timeout,
6402 budget: SkipBudget::Participants,
6403 },
6404 replay_buffer: NZUsize!(1024 * 1024),
6405 write_buffer: NZUsize!(1024 * 1024),
6406 page_cache: CacheRef::from_pooler(context, PAGE_SIZE, PAGE_CACHE_SIZE),
6407 forward: ForwardPolicy::Disabled,
6408 track_historical_votes: false,
6409 };
6410 let engine = Engine::new(
6411 context
6412 .child("engine")
6413 .with_attribute("public_key", validator),
6414 cfg,
6415 );
6416 let (pending, recovered, resolver) = registration;
6417 engine.start(pending, recovered, resolver);
6418 }
6419
6420 reporters
6421 }
6422
6423 fn certified_split_heals_in_group_led_view<S, F, L>(mut fixture: F, elector: L)
6430 where
6431 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
6432 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
6433 L: elector::Config<S>,
6434 {
6435 let n = 4;
6436 let quorum = quorum(n) as usize;
6437 assert_eq!(quorum, 3);
6438 let view_retention = ViewDelta::new(10);
6439 let skip_timeout = Duration::from_secs(12);
6440 let namespace = b"consensus".to_vec();
6441 let executor = deterministic::Runner::timed(Duration::from_secs(300));
6442 executor.start(|mut context| async move {
6443 let Fixture {
6444 participants,
6445 schemes,
6446 ..
6447 } = fixture(&mut context, &namespace, n);
6448 let mut oracle = start_test_network_with_peers(
6449 context.child("network"),
6450 participants.clone(),
6451 false,
6452 )
6453 .await;
6454 let mut registrations = register_validators(&mut oracle, &participants).await;
6455
6456 let epoch = Epoch::new(333);
6460 let participant_set: Set<PublicKey> = participants.clone().try_into().unwrap();
6461 let schedule = elector.clone().build(&participant_set);
6462 let leader_of =
6463 |view: u64| usize::from(schedule.elect(Round::new(epoch, View::new(view)), None));
6464 let byzantine = leader_of(10);
6465 let group = [leader_of(11), leader_of(12)];
6466 let lone = leader_of(13);
6467 assert!(!group.contains(&byzantine) && !group.contains(&lone) && byzantine != lone);
6468
6469 let build_notarization = |proposal: &Proposal<D>| -> TNotarization<_, D> {
6471 let votes: Vec<_> = (0..quorum)
6472 .map(|i| TNotarize::sign(&schemes[i], proposal.clone()).unwrap())
6473 .collect();
6474 TNotarization::from_notarizes(&schemes[0], non_empty![@&votes], &Sequential)
6475 .expect("notarization quorum")
6476 };
6477 let build_finalization = |proposal: &Proposal<D>| -> TFinalization<_, D> {
6478 let votes: Vec<_> = (0..quorum)
6479 .map(|i| TFinalize::sign(&schemes[i], proposal.clone()).unwrap())
6480 .collect();
6481 TFinalization::from_finalizes(&schemes[0], non_empty![@&votes], &Sequential)
6482 .expect("finalization quorum")
6483 };
6484 let build_nullification = |view: u64| -> TNullification<_> {
6485 let round = Round::new(epoch, View::new(view));
6486 let votes: Vec<_> = (0..quorum)
6487 .map(|i| TNullify::sign::<D>(&schemes[i], round).unwrap())
6488 .collect();
6489 TNullification::from_nullifies(&schemes[0], non_empty![@&votes], &Sequential)
6490 .expect("nullification quorum")
6491 };
6492 let payload_b2 = Sha256::hash(&[b"B_2"]);
6493 let proposal_b2 =
6494 Proposal::new(Round::new(epoch, View::new(2)), View::new(1), payload_b2);
6495 let payload_b3 = Sha256::hash(&[b"B_3"]);
6496 let proposal_b3 =
6497 Proposal::new(Round::new(epoch, View::new(3)), View::new(2), payload_b3);
6498 let b2_notarization = build_notarization(&proposal_b2);
6499 let b2_finalization = build_finalization(&proposal_b2);
6500 let b3_notarization = build_notarization(&proposal_b3);
6501 let null_3 = build_nullification(3);
6502
6503 let link = Link {
6504 latency: Duration::from_millis(10),
6505 jitter: Duration::from_millis(0),
6506 success_rate: probability!(1.0),
6507 };
6508 let mut injector_sender =
6509 start_certificate_injector(&context, &mut oracle, &participants, &link).await;
6510
6511 let msg = Certificate::<_, D>::Notarization(b2_notarization).encode();
6513 injector_sender.send(Recipients::All, msg, true);
6514 let msg = Certificate::<_, D>::Finalization(b2_finalization).encode();
6515 injector_sender.send(Recipients::All, msg, true);
6516 let msg = Certificate::<_, D>::Notarization(b3_notarization).encode();
6517 injector_sender.send(Recipients::One(participants[lone].clone()), msg, true);
6518 let msg = Certificate::<_, D>::Nullification(null_3).encode();
6519 for idx in group {
6520 injector_sender.send(
6521 Recipients::One(participants[idx].clone()),
6522 msg.clone(),
6523 true,
6524 );
6525 }
6526 for view in 4..=9 {
6527 let msg = Certificate::<_, D>::Nullification(build_nullification(view)).encode();
6528 injector_sender.send(Recipients::All, msg, true);
6529 }
6530
6531 let mut honest_reporters = start_certified_split_engines(
6533 &context,
6534 CertifiedSplitEngineConfig {
6535 oracle: &oracle,
6536 participants: &participants,
6537 schemes: &schemes,
6538 registrations: &mut registrations,
6539 silent: byzantine,
6540 elector: &elector,
6541 epoch,
6542 view_retention,
6543 skip_timeout,
6544 },
6545 );
6546
6547 context.sleep(Duration::from_secs(2)).await;
6549
6550 let view_2 = View::new(2);
6552 let view_3 = View::new(3);
6553 for (idx, reporter) in honest_reporters.iter() {
6554 assert!(
6555 reporter.finalizations.lock().contains_key(&view_2),
6556 "reporter {idx} missing finalization for view 2"
6557 );
6558 let notarizations = reporter.notarizations.lock();
6559 let nullifications = reporter.nullifications.lock();
6560 if *idx == lone {
6561 assert!(notarizations.contains_key(&view_3));
6562 assert!(!nullifications.contains_key(&view_3));
6563 assert!(reporter.certifications.lock().contains_key(&view_3));
6564 } else {
6565 assert!(nullifications.contains_key(&view_3), "reporter {idx}");
6566 assert!(!notarizations.contains_key(&view_3), "reporter {idx}");
6567 }
6568 }
6569
6570 link_validators(&mut oracle, &participants, Action::Link(link.clone()), None).await;
6572
6573 {
6574 let target = View::new(3);
6575 let mut finalizers = Vec::new();
6576 for reporter in honest_reporters.values_mut() {
6577 let (mut latest, mut monitor) = reporter.subscribe().await;
6578 finalizers.push(
6579 context
6580 .child("resume_finalizer")
6581 .spawn(move |_| async move {
6582 while latest < target {
6583 latest = monitor.recv().await.expect("event missing");
6584 }
6585 }),
6586 );
6587 }
6588 join_all(finalizers).await;
6589 }
6590
6591 for (idx, reporter) in honest_reporters.iter() {
6593 let first = {
6594 let finalizations = reporter.finalizations.lock();
6595 finalizations
6596 .keys()
6597 .filter(|view| **view > view_2)
6598 .min()
6599 .copied()
6600 .expect("no finalization past the preload")
6601 };
6602 assert_eq!(
6603 first,
6604 View::new(11),
6605 "reporter {idx} did not finalize the first honest-led view"
6606 );
6607 }
6608 assert!(
6610 honest_reporters[&lone]
6611 .nullifications
6612 .lock()
6613 .contains_key(&view_3),
6614 "lone did not resolve Nullification(3) from the group leader"
6615 );
6616
6617 for reporter in honest_reporters.values() {
6618 reporter.assert_no_faults();
6619 reporter.assert_no_invalid();
6620 }
6621 let blocked = oracle.blocked().await.unwrap();
6622 assert!(blocked.is_empty(), "blocked peers: {blocked:?}");
6623 });
6624 }
6625
6626 #[test_group("slow")]
6627 #[test_traced]
6628 fn test_certified_split_heals_in_group_led_view() {
6629 certified_split_heals_in_group_led_view::<_, _, RoundRobin>(
6630 ed25519::fixture,
6631 RoundRobin::default(),
6632 );
6633 }
6634
6635 fn certified_split_heals_when_lone_holder_leads_first<S, F, L>(mut fixture: F, elector: L)
6640 where
6641 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
6642 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
6643 L: elector::Config<S>,
6644 {
6645 let n = 4;
6646 let quorum = quorum(n) as usize;
6647 assert_eq!(quorum, 3);
6648 let view_retention = ViewDelta::new(10);
6649 let skip_timeout = Duration::from_secs(12);
6650 let namespace = b"consensus".to_vec();
6651 let executor = deterministic::Runner::timed(Duration::from_secs(300));
6652 executor.start(|mut context| async move {
6653 let Fixture {
6654 participants,
6655 schemes,
6656 ..
6657 } = fixture(&mut context, &namespace, n);
6658 let mut oracle = start_test_network_with_peers(
6659 context.child("network"),
6660 participants.clone(),
6661 false,
6662 )
6663 .await;
6664 let mut registrations = register_validators(&mut oracle, &participants).await;
6665
6666 let epoch = Epoch::new(333);
6670 let participant_set: Set<PublicKey> = participants.clone().try_into().unwrap();
6671 let schedule = elector.clone().build(&participant_set);
6672 let leader_of =
6673 |view: u64| usize::from(schedule.elect(Round::new(epoch, View::new(view)), None));
6674 let byzantine = leader_of(10);
6675 let lone = leader_of(11);
6676 let group = [leader_of(12), leader_of(13)];
6677 assert!(!group.contains(&byzantine) && !group.contains(&lone) && byzantine != lone);
6678
6679 let build_notarization = |proposal: &Proposal<D>| -> TNotarization<_, D> {
6681 let votes: Vec<_> = (0..quorum)
6682 .map(|i| TNotarize::sign(&schemes[i], proposal.clone()).unwrap())
6683 .collect();
6684 TNotarization::from_notarizes(&schemes[0], non_empty![@&votes], &Sequential)
6685 .expect("notarization quorum")
6686 };
6687 let build_finalization = |proposal: &Proposal<D>| -> TFinalization<_, D> {
6688 let votes: Vec<_> = (0..quorum)
6689 .map(|i| TFinalize::sign(&schemes[i], proposal.clone()).unwrap())
6690 .collect();
6691 TFinalization::from_finalizes(&schemes[0], non_empty![@&votes], &Sequential)
6692 .expect("finalization quorum")
6693 };
6694 let build_nullification = |view: u64| -> TNullification<_> {
6695 let round = Round::new(epoch, View::new(view));
6696 let votes: Vec<_> = (0..quorum)
6697 .map(|i| TNullify::sign::<D>(&schemes[i], round).unwrap())
6698 .collect();
6699 TNullification::from_nullifies(&schemes[0], non_empty![@&votes], &Sequential)
6700 .expect("nullification quorum")
6701 };
6702 let payload_b2 = Sha256::hash(&[b"B_2"]);
6703 let proposal_b2 =
6704 Proposal::new(Round::new(epoch, View::new(2)), View::new(1), payload_b2);
6705 let payload_b3 = Sha256::hash(&[b"B_3"]);
6706 let proposal_b3 =
6707 Proposal::new(Round::new(epoch, View::new(3)), View::new(2), payload_b3);
6708 let b2_notarization = build_notarization(&proposal_b2);
6709 let b2_finalization = build_finalization(&proposal_b2);
6710 let b3_notarization = build_notarization(&proposal_b3);
6711 let null_3 = build_nullification(3);
6712
6713 let link = Link {
6714 latency: Duration::from_millis(10),
6715 jitter: Duration::from_millis(0),
6716 success_rate: probability!(1.0),
6717 };
6718 let mut injector_sender =
6719 start_certificate_injector(&context, &mut oracle, &participants, &link).await;
6720
6721 let msg = Certificate::<_, D>::Notarization(b2_notarization).encode();
6723 injector_sender.send(Recipients::All, msg, true);
6724 let msg = Certificate::<_, D>::Finalization(b2_finalization).encode();
6725 injector_sender.send(Recipients::All, msg, true);
6726 let msg = Certificate::<_, D>::Notarization(b3_notarization).encode();
6727 injector_sender.send(Recipients::One(participants[lone].clone()), msg, true);
6728 let msg = Certificate::<_, D>::Nullification(null_3).encode();
6729 for idx in group {
6730 injector_sender.send(
6731 Recipients::One(participants[idx].clone()),
6732 msg.clone(),
6733 true,
6734 );
6735 }
6736 for view in 4..=9 {
6737 let msg = Certificate::<_, D>::Nullification(build_nullification(view)).encode();
6738 injector_sender.send(Recipients::All, msg, true);
6739 }
6740
6741 let mut honest_reporters = start_certified_split_engines(
6743 &context,
6744 CertifiedSplitEngineConfig {
6745 oracle: &oracle,
6746 participants: &participants,
6747 schemes: &schemes,
6748 registrations: &mut registrations,
6749 silent: byzantine,
6750 elector: &elector,
6751 epoch,
6752 view_retention,
6753 skip_timeout,
6754 },
6755 );
6756
6757 context.sleep(Duration::from_secs(2)).await;
6759
6760 let view_2 = View::new(2);
6762 let view_3 = View::new(3);
6763 for (idx, reporter) in honest_reporters.iter() {
6764 assert!(
6765 reporter.finalizations.lock().contains_key(&view_2),
6766 "reporter {idx} missing finalization for view 2"
6767 );
6768 let notarizations = reporter.notarizations.lock();
6769 let nullifications = reporter.nullifications.lock();
6770 if *idx == lone {
6771 assert!(notarizations.contains_key(&view_3));
6772 assert!(!nullifications.contains_key(&view_3));
6773 assert!(reporter.certifications.lock().contains_key(&view_3));
6774 } else {
6775 assert!(nullifications.contains_key(&view_3), "reporter {idx}");
6776 assert!(!notarizations.contains_key(&view_3), "reporter {idx}");
6777 }
6778 }
6779
6780 link_validators(&mut oracle, &participants, Action::Link(link.clone()), None).await;
6782
6783 {
6784 let target = View::new(3);
6785 let mut finalizers = Vec::new();
6786 for reporter in honest_reporters.values_mut() {
6787 let (mut latest, mut monitor) = reporter.subscribe().await;
6788 finalizers.push(
6789 context
6790 .child("resume_finalizer")
6791 .spawn(move |_| async move {
6792 while latest < target {
6793 latest = monitor.recv().await.expect("event missing");
6794 }
6795 }),
6796 );
6797 }
6798 join_all(finalizers).await;
6799 }
6800
6801 for (idx, reporter) in honest_reporters.iter() {
6803 let first = {
6804 let finalizations = reporter.finalizations.lock();
6805 finalizations
6806 .keys()
6807 .filter(|view| **view > view_2)
6808 .min()
6809 .copied()
6810 .expect("no finalization past the preload")
6811 };
6812 assert_eq!(
6813 first, view_3,
6814 "reporter {idx} did not finalize the recovered parent view"
6815 );
6816 }
6817
6818 for idx in group {
6820 let reporter = &honest_reporters[&idx];
6821 assert!(
6822 reporter.notarizations.lock().contains_key(&view_3),
6823 "group reporter {idx} did not resolve Notarization(3) from lone"
6824 );
6825 assert!(
6826 reporter.certifications.lock().contains_key(&view_3),
6827 "group reporter {idx} did not certify Notarization(3)"
6828 );
6829 }
6830
6831 for reporter in honest_reporters.values() {
6832 reporter.assert_no_faults();
6833 reporter.assert_no_invalid();
6834 }
6835 let blocked = oracle.blocked().await.unwrap();
6836 assert!(blocked.is_empty(), "blocked peers: {blocked:?}");
6837 });
6838 }
6839
6840 #[test_group("slow")]
6841 #[test_traced]
6842 fn test_certified_split_heals_when_lone_holder_leads_first() {
6843 certified_split_heals_when_lone_holder_leads_first::<_, _, RoundRobin>(
6844 ed25519::fixture,
6845 RoundRobin::default(),
6846 );
6847 }
6848
6849 fn certified_split_heals_with_displaced_certified_view<S, F, L>(mut fixture: F, elector: L)
6855 where
6856 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
6857 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
6858 L: elector::Config<S>,
6859 {
6860 let n = 4;
6861 let quorum = quorum(n) as usize;
6862 assert_eq!(quorum, 3);
6863 let view_retention = ViewDelta::new(10);
6864 let skip_timeout = Duration::from_secs(12);
6865 let namespace = b"consensus".to_vec();
6866 let executor = deterministic::Runner::timed(Duration::from_secs(300));
6867 executor.start(|mut context| async move {
6868 let Fixture {
6869 participants,
6870 schemes,
6871 ..
6872 } = fixture(&mut context, &namespace, n);
6873 let mut oracle = start_test_network_with_peers(
6874 context.child("network"),
6875 participants.clone(),
6876 false,
6877 )
6878 .await;
6879 let mut registrations = register_validators(&mut oracle, &participants).await;
6880
6881 let epoch = Epoch::new(333);
6885 let participant_set: Set<PublicKey> = participants.clone().try_into().unwrap();
6886 let schedule = elector.clone().build(&participant_set);
6887 let leader_of =
6888 |view: u64| usize::from(schedule.elect(Round::new(epoch, View::new(view)), None));
6889 let byzantine = leader_of(10);
6890 let group = [leader_of(11), leader_of(12)];
6891 let lone = leader_of(13);
6892 assert!(!group.contains(&byzantine) && !group.contains(&lone) && byzantine != lone);
6893
6894 let build_notarization = |proposal: &Proposal<D>| -> TNotarization<_, D> {
6896 let votes: Vec<_> = (0..quorum)
6897 .map(|i| TNotarize::sign(&schemes[i], proposal.clone()).unwrap())
6898 .collect();
6899 TNotarization::from_notarizes(&schemes[0], non_empty![@&votes], &Sequential)
6900 .expect("notarization quorum")
6901 };
6902 let build_finalization = |proposal: &Proposal<D>| -> TFinalization<_, D> {
6903 let votes: Vec<_> = (0..quorum)
6904 .map(|i| TFinalize::sign(&schemes[i], proposal.clone()).unwrap())
6905 .collect();
6906 TFinalization::from_finalizes(&schemes[0], non_empty![@&votes], &Sequential)
6907 .expect("finalization quorum")
6908 };
6909 let build_nullification = |view: u64| -> TNullification<_> {
6910 let round = Round::new(epoch, View::new(view));
6911 let votes: Vec<_> = (0..quorum)
6912 .map(|i| TNullify::sign::<D>(&schemes[i], round).unwrap())
6913 .collect();
6914 TNullification::from_nullifies(&schemes[0], non_empty![@&votes], &Sequential)
6915 .expect("nullification quorum")
6916 };
6917 let payload_b2 = Sha256::hash(&[b"B_2"]);
6918 let proposal_b2 =
6919 Proposal::new(Round::new(epoch, View::new(2)), View::new(1), payload_b2);
6920 let payload_b5 = Sha256::hash(&[b"B_5"]);
6921 let proposal_b5 =
6922 Proposal::new(Round::new(epoch, View::new(5)), View::new(2), payload_b5);
6923 let b2_notarization = build_notarization(&proposal_b2);
6924 let b2_finalization = build_finalization(&proposal_b2);
6925 let b5_notarization = build_notarization(&proposal_b5);
6926
6927 let link = Link {
6928 latency: Duration::from_millis(10),
6929 jitter: Duration::from_millis(0),
6930 success_rate: probability!(1.0),
6931 };
6932 let mut injector_sender =
6933 start_certificate_injector(&context, &mut oracle, &participants, &link).await;
6934
6935 let msg = Certificate::<_, D>::Notarization(b2_notarization).encode();
6937 injector_sender.send(Recipients::All, msg, true);
6938 let msg = Certificate::<_, D>::Finalization(b2_finalization).encode();
6939 injector_sender.send(Recipients::All, msg, true);
6940 let msg = Certificate::<_, D>::Notarization(b5_notarization).encode();
6941 injector_sender.send(Recipients::One(participants[lone].clone()), msg, true);
6942 for view in 3..=5 {
6943 let msg = Certificate::<_, D>::Nullification(build_nullification(view)).encode();
6944 for idx in group {
6945 injector_sender.send(
6946 Recipients::One(participants[idx].clone()),
6947 msg.clone(),
6948 true,
6949 );
6950 }
6951 }
6952 for view in 6..=9 {
6953 let msg = Certificate::<_, D>::Nullification(build_nullification(view)).encode();
6954 injector_sender.send(Recipients::All, msg, true);
6955 }
6956
6957 let mut honest_reporters = start_certified_split_engines(
6959 &context,
6960 CertifiedSplitEngineConfig {
6961 oracle: &oracle,
6962 participants: &participants,
6963 schemes: &schemes,
6964 registrations: &mut registrations,
6965 silent: byzantine,
6966 elector: &elector,
6967 epoch,
6968 view_retention,
6969 skip_timeout,
6970 },
6971 );
6972
6973 context.sleep(Duration::from_secs(2)).await;
6975
6976 let view_2 = View::new(2);
6978 let view_5 = View::new(5);
6979 for (idx, reporter) in honest_reporters.iter() {
6980 assert!(
6981 reporter.finalizations.lock().contains_key(&view_2),
6982 "reporter {idx} missing finalization for view 2"
6983 );
6984 let notarizations = reporter.notarizations.lock();
6985 let nullifications = reporter.nullifications.lock();
6986 if *idx == lone {
6987 assert!(notarizations.contains_key(&view_5));
6988 assert!(reporter.certifications.lock().contains_key(&view_5));
6989 for view in 3..=5 {
6990 assert!(!nullifications.contains_key(&View::new(view)));
6991 }
6992 } else {
6993 for view in 3..=5 {
6994 assert!(
6995 nullifications.contains_key(&View::new(view)),
6996 "reporter {idx}"
6997 );
6998 }
6999 assert!(!notarizations.contains_key(&view_5), "reporter {idx}");
7000 }
7001 }
7002
7003 link_validators(&mut oracle, &participants, Action::Link(link.clone()), None).await;
7005
7006 {
7007 let target = View::new(3);
7008 let mut finalizers = Vec::new();
7009 for reporter in honest_reporters.values_mut() {
7010 let (mut latest, mut monitor) = reporter.subscribe().await;
7011 finalizers.push(
7012 context
7013 .child("resume_finalizer")
7014 .spawn(move |_| async move {
7015 while latest < target {
7016 latest = monitor.recv().await.expect("event missing");
7017 }
7018 }),
7019 );
7020 }
7021 join_all(finalizers).await;
7022 }
7023
7024 for (idx, reporter) in honest_reporters.iter() {
7026 let first = {
7027 let finalizations = reporter.finalizations.lock();
7028 finalizations
7029 .keys()
7030 .filter(|view| **view > view_2)
7031 .min()
7032 .copied()
7033 .expect("no finalization past the preload")
7034 };
7035 assert_eq!(
7036 first,
7037 View::new(11),
7038 "reporter {idx} did not finalize the first honest-led view"
7039 );
7040 }
7041 let lone_reporter = &honest_reporters[&lone];
7042 for view in 3..=5 {
7043 assert!(
7044 lone_reporter
7045 .nullifications
7046 .lock()
7047 .contains_key(&View::new(view)),
7048 "lone did not resolve Nullification({view}) from the group leader"
7049 );
7050 }
7051
7052 for reporter in honest_reporters.values() {
7053 reporter.assert_no_faults();
7054 reporter.assert_no_invalid();
7055 }
7056 let blocked = oracle.blocked().await.unwrap();
7057 assert!(blocked.is_empty(), "blocked peers: {blocked:?}");
7058 });
7059 }
7060
7061 #[test_group("slow")]
7062 #[test_traced]
7063 fn test_certified_split_heals_with_displaced_certified_view() {
7064 certified_split_heals_with_displaced_certified_view::<_, _, RoundRobin>(
7065 ed25519::fixture,
7066 RoundRobin::default(),
7067 );
7068 }
7069
7070 fn stable_leader_cross_term_certified_split<S, F, L>(
7091 mut fixture: F,
7092 elector: L,
7093 suppress_backfill: bool,
7094 ) where
7095 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
7096 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
7097 L: elector::Config<S>,
7098 {
7099 let n = 4;
7100 let quorum = quorum(n) as usize;
7101 assert_eq!(quorum, 3);
7102 let view_retention = ViewDelta::new(10);
7103 let skip_timeout = Duration::from_secs(12);
7104 let namespace = b"consensus".to_vec();
7105 let executor = deterministic::Runner::timed(Duration::from_secs(300));
7106 executor.start(|mut context| async move {
7107 let Fixture {
7108 participants,
7109 schemes,
7110 ..
7111 } = fixture(&mut context, &namespace, n);
7112 let mut oracle = start_test_network_with_peers(
7113 context.child("network"),
7114 participants.clone(),
7115 false,
7116 )
7117 .await;
7118 let mut registrations = register_validators(&mut oracle, &participants).await;
7119
7120 let epoch = Epoch::new(2);
7122 let participant_set: Set<PublicKey> = participants.clone().try_into().unwrap();
7123 let schedule = elector.clone().build(&participant_set);
7124 let leader_of =
7125 |view: u64| usize::from(schedule.elect(Round::new(epoch, View::new(view)), None));
7126 let offline = leader_of(1);
7127 assert_eq!(offline, leader_of(21), "offline must lead terms 1 and 5");
7128 let honest: Vec<usize> = (0..n as usize).filter(|idx| *idx != offline).collect();
7129 let (a, b, c) = (honest[0], honest[1], honest[2]);
7130 for view in [6u64, 11, 16, 26, 31, 36] {
7131 assert_ne!(leader_of(view), offline, "term start must be honest");
7132 }
7133 assert_ne!(
7135 leader_of(26),
7136 c,
7137 "view-26 leader must hold certification 22"
7138 );
7139 assert_eq!(leader_of(41), offline, "term 9 must return to offline");
7141
7142 let build_notarization =
7143 |proposal: &Proposal<D>, signers: [usize; 3]| -> TNotarization<_, D> {
7144 let votes: Vec<_> = signers
7145 .iter()
7146 .map(|i| TNotarize::sign(&schemes[*i], proposal.clone()).unwrap())
7147 .collect();
7148 TNotarization::from_notarizes(&schemes[0], non_empty![@&votes], &Sequential)
7149 .expect("notarization quorum")
7150 };
7151 let build_nullification = |view: u64, signers: [usize; 3]| -> TNullification<_> {
7152 let round = Round::new(epoch, View::new(view));
7153 let votes: Vec<_> = signers
7154 .iter()
7155 .map(|i| TNullify::sign::<D>(&schemes[*i], round).unwrap())
7156 .collect();
7157 TNullification::from_nullifies(&schemes[0], non_empty![@&votes], &Sequential)
7158 .expect("nullification quorum")
7159 };
7160
7161 let payload_1 = Sha256::hash(&[b"V1"]);
7163 let proposal_1 =
7164 Proposal::new(Round::new(epoch, View::new(1)), View::new(0), payload_1);
7165 let payload_2 = Sha256::hash(&[b"V2"]);
7166 let proposal_2 =
7167 Proposal::new(Round::new(epoch, View::new(2)), View::new(1), payload_2);
7168 let payload_21 = Sha256::hash(&[b"V21"]);
7172 let proposal_21 =
7173 Proposal::new(Round::new(epoch, View::new(21)), View::new(0), payload_21);
7174 let payload_22 = Sha256::hash(&[b"V22"]);
7175 let proposal_22 =
7176 Proposal::new(Round::new(epoch, View::new(22)), View::new(21), payload_22);
7177
7178 let link = Link {
7179 latency: Duration::from_millis(10),
7180 jitter: Duration::from_millis(0),
7181 success_rate: probability!(1.0),
7182 };
7183 let mut injector_sender =
7184 start_certificate_injector(&context, &mut oracle, &participants, &link).await;
7185
7186 {
7187 let mut send_to = |certificate: Certificate<S, D>, targets: &[usize]| {
7188 let msg = certificate.encode();
7189 for idx in targets {
7190 injector_sender.send(
7191 Recipients::One(participants[*idx].clone()),
7192 msg.clone(),
7193 true,
7194 );
7195 }
7196 };
7197
7198 send_to(
7201 Certificate::Notarization(build_notarization(&proposal_1, [b, c, offline])),
7202 &[b, c],
7203 );
7204 send_to(
7205 Certificate::Notarization(build_notarization(&proposal_2, [b, c, offline])),
7206 &[b, c],
7207 );
7208 send_to(
7209 Certificate::Nullification(build_nullification(3, [b, c, offline])),
7210 &[b, c],
7211 );
7212 let a_skip = if suppress_backfill { 1 } else { 3 };
7213 send_to(
7214 Certificate::Nullification(build_nullification(a_skip, [a, c, offline])),
7215 &[a, b],
7216 );
7217
7218 for view in [6u64, 11, 16] {
7220 send_to(
7221 Certificate::Nullification(build_nullification(view, [a, b, c])),
7222 &[a, b, c],
7223 );
7224 }
7225
7226 send_to(
7229 Certificate::Notarization(build_notarization(&proposal_21, [a, b, offline])),
7230 &[a, b],
7231 );
7232 send_to(
7233 Certificate::Notarization(build_notarization(&proposal_22, [a, b, offline])),
7234 &[a, b],
7235 );
7236 let c_skip = if suppress_backfill { 21 } else { 23 };
7237 send_to(
7238 Certificate::Nullification(build_nullification(c_skip, [a, c, offline])),
7239 &[c],
7240 );
7241 send_to(
7242 Certificate::Nullification(build_nullification(23, [a, b, offline])),
7243 &[a, b],
7244 );
7245 }
7246
7247 let mut honest_reporters = start_certified_split_engines(
7250 &context,
7251 CertifiedSplitEngineConfig {
7252 oracle: &oracle,
7253 participants: &participants,
7254 schemes: &schemes,
7255 registrations: &mut registrations,
7256 silent: offline,
7257 elector: &elector,
7258 epoch,
7259 view_retention,
7260 skip_timeout,
7261 },
7262 );
7263
7264 context.sleep(Duration::from_secs(2)).await;
7266
7267 for (idx, reporter) in honest_reporters.iter() {
7269 let certifications = reporter.certifications.lock();
7270 let has_term1 = certifications.contains_key(&View::new(2));
7271 let has_term5 = certifications.contains_key(&View::new(22));
7272 if *idx == a {
7273 assert!(!has_term1 && has_term5, "a: {has_term1} {has_term5}");
7274 } else if *idx == b {
7275 assert!(has_term1 && has_term5, "b: {has_term1} {has_term5}");
7276 } else {
7277 assert!(has_term1 && !has_term5, "c: {has_term1} {has_term5}");
7278 }
7279 assert!(
7280 reporter.finalizations.lock().is_empty(),
7281 "reporter {idx} finalized during preload"
7282 );
7283 }
7284
7285 link_validators(&mut oracle, &participants, Action::Link(link.clone()), None).await;
7287
7288 let target = View::new(26);
7290 let mut finalizers = Vec::new();
7291 for (idx, reporter) in honest_reporters.iter_mut() {
7292 let (mut latest, mut monitor) = reporter.subscribe().await;
7293 finalizers.push(
7294 context
7295 .child("finalizer")
7296 .with_attribute("index", idx)
7297 .spawn(move |_| async move {
7298 while latest < target {
7299 latest = monitor.recv().await.expect("finalization missing");
7300 }
7301 }),
7302 );
7303 }
7304 let progress_reporters: Vec<_> = honest_reporters.values().cloned().collect();
7307 let next_byzantine_term =
7308 context
7309 .child("next_byzantine_term")
7310 .spawn(move |context| async move {
7311 loop {
7312 let reached = progress_reporters.iter().all(|reporter| {
7313 reporter
7314 .nullifications
7315 .lock()
7316 .keys()
7317 .any(|view| *view >= View::new(41))
7318 });
7319 if reached {
7320 return;
7321 }
7322 context.sleep(Duration::from_secs(1)).await;
7323 }
7324 });
7325 let finalized = select! {
7326 _ = join_all(finalizers) => true,
7327 _ = next_byzantine_term => false,
7328 };
7329
7330 for reporter in honest_reporters.values() {
7331 reporter.assert_no_faults();
7332 reporter.assert_no_invalid();
7333 }
7334 let blocked = oracle.blocked().await.unwrap();
7335 assert!(blocked.is_empty(), "blocked peers: {blocked:?}");
7336
7337 if !finalized {
7338 let a_reporter = &honest_reporters[&a];
7342 assert!(
7343 a_reporter.notarizations.lock().contains_key(&View::new(2)),
7344 "halted, but a is missing notarization(2)"
7345 );
7346 assert!(
7347 !a_reporter.notarizations.lock().contains_key(&View::new(1)),
7348 "halted, but a repaired notarization(1)"
7349 );
7350 assert!(
7351 !a_reporter.certifications.lock().contains_key(&View::new(2)),
7352 "halted, but a certified view 2"
7353 );
7354
7355 let c_reporter = &honest_reporters[&c];
7356 assert!(
7357 c_reporter.notarizations.lock().contains_key(&View::new(22)),
7358 "halted, but c is missing notarization(22)"
7359 );
7360 assert!(
7361 !c_reporter.notarizations.lock().contains_key(&View::new(21)),
7362 "halted, but c repaired notarization(21)"
7363 );
7364 assert!(
7365 !c_reporter.certifications.lock().contains_key(&View::new(22)),
7366 "halted, but c certified view 22"
7367 );
7368
7369 panic!(
7370 "all honest leaders exhausted a term, but recursive parent repair never finalized"
7371 );
7372 }
7373 for (idx, reporter) in honest_reporters.iter() {
7374 assert!(
7375 reporter
7376 .finalizations
7377 .lock()
7378 .keys()
7379 .any(|view| *view >= target),
7380 "reporter {idx} never finalized after GST"
7381 );
7382 }
7383 let c_certifications = honest_reporters[&c].certifications.lock();
7384 for view in [View::new(21), View::new(22)] {
7385 assert!(
7386 c_certifications.contains_key(&view),
7387 "c did not recover and certify view {view} from an honest validator"
7388 );
7389 }
7390 });
7391 }
7392
7393 #[test_group("slow")]
7394 #[test_traced]
7395 fn test_stable_leader_cross_term_certified_split_recovers() {
7396 stable_leader_cross_term_certified_split::<_, _, RoundRobin>(
7397 ed25519::fixture,
7398 RoundRobin::default().with_term(
7399 TermLength::new(NZU32!(5)),
7400 Duration::from_secs(12),
7401 ViewDelta::new(0),
7402 ),
7403 true,
7404 );
7405 }
7406
7407 #[test_group("slow")]
7408 #[test_traced]
7409 fn test_stable_leader_cross_term_certified_split_backfillable() {
7410 stable_leader_cross_term_certified_split::<_, _, RoundRobin>(
7411 ed25519::fixture,
7412 RoundRobin::default().with_term(
7413 TermLength::new(NZU32!(5)),
7414 Duration::from_secs(12),
7415 ViewDelta::new(0),
7416 ),
7417 false,
7418 );
7419 }
7420
7421 fn tle<V, L>(elector: L)
7422 where
7423 V: Variant,
7424 L: elector::Config<bls12381_threshold_vrf::Scheme<PublicKey, V>>,
7425 {
7426 let n = 4;
7428 let namespace = b"consensus".to_vec();
7429 let view_retention = ViewDelta::new(100);
7430 let skip_timeout = Duration::from_secs(50);
7431 let executor = deterministic::Runner::timed(Duration::from_secs(30));
7432 executor.start(|mut context| async move {
7433 let Fixture {
7435 participants,
7436 schemes,
7437 ..
7438 } = bls12381_threshold_vrf::fixture::<V, _>(&mut context, &namespace, n);
7439 let mut oracle =
7440 start_test_network_with_peers(context.child("network"), participants.clone(), true)
7441 .await;
7442 let mut registrations = register_validators(&mut oracle, &participants).await;
7443
7444 let link = Link {
7446 latency: Duration::from_millis(10),
7447 jitter: Duration::from_millis(5),
7448 success_rate: probability!(1.0),
7449 };
7450 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
7451
7452 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
7454 let mut reporters = Vec::new();
7455 let mut engine_handlers = Vec::new();
7456 let monitor_reporter = Arc::new(Mutex::new(None));
7457 for (idx, validator) in participants.iter().enumerate() {
7458 let context = context
7460 .child("validator")
7461 .with_attribute("public_key", validator);
7462
7463 let reporter_config = mocks::reporter::Config {
7465 participants: participants.clone().try_into().unwrap(),
7466 scheme: schemes[idx].clone(),
7467 elector: elector.clone(),
7468 };
7469 let reporter =
7470 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
7471 reporters.push(reporter.clone());
7472 if idx == 0 {
7473 *monitor_reporter.lock() = Some(reporter.clone());
7474 }
7475
7476 let application_cfg = mocks::application::Config::<Sha256, _> {
7478 relay: relay.clone(),
7479 me: validator.clone(),
7480 propose_latency: (10.0, 5.0),
7481 verify_latency: (10.0, 5.0),
7482 certify_latency: (10.0, 5.0),
7483 should_certify: mocks::application::Certifier::Always,
7484 };
7485 let (actor, application) = mocks::application::Application::new(
7486 context.child("application"),
7487 application_cfg,
7488 );
7489 actor.start();
7490 let blocker = oracle.control(validator.clone());
7491 let cfg = config::Config {
7492 scheme: schemes[idx].clone(),
7493 elector: elector.clone(),
7494 blocker,
7495 automaton: application.clone(),
7496 relay: application.clone(),
7497 reporter: reporter.clone(),
7498 strategy: Sequential,
7499 partition: validator.to_string(),
7500 mailbox_size: NZUsize!(1024),
7501 epoch: Epoch::new(333),
7502 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
7503 Epoch::new(333),
7504 )),
7505 leader_timeout: Duration::from_millis(100),
7506 certification_timeout: Duration::from_millis(200),
7507 timeout_retry: Duration::from_millis(500),
7508 fetch_timeout: Duration::from_millis(100),
7509 view_retention,
7510 skip: SkipPolicy::Enabled {
7511 timeout: skip_timeout,
7512 budget: SkipBudget::Participants,
7513 },
7514 replay_buffer: NZUsize!(1024 * 1024),
7515 write_buffer: NZUsize!(1024 * 1024),
7516 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
7517 forward: ForwardPolicy::Disabled,
7518 track_historical_votes: false,
7519 };
7520 let engine = Engine::new(context.child("engine"), cfg);
7521
7522 let (pending, recovered, resolver) = registrations
7524 .remove(validator)
7525 .expect("validator should be registered");
7526 engine_handlers.push(engine.start(pending, recovered, resolver));
7527 }
7528
7529 let target = Round::new(Epoch::new(333), View::new(10)); let message = b"Secret message for future view10"; let ciphertext = schemes[0]
7535 .encrypt(&mut context, target, *message)
7536 .expect("valid TLE encryption inputs");
7537
7538 let reporter = monitor_reporter.lock().clone().unwrap();
7540 loop {
7541 context.sleep(Duration::from_millis(100)).await;
7543 let notarizations = reporter.notarizations.lock();
7544 let Some(notarization) = notarizations.get(&target.view()) else {
7545 continue;
7546 };
7547
7548 let seed = notarization.seed();
7550 let decrypted = seed
7551 .decrypt(&ciphertext)
7552 .expect("Decryption should succeed with valid seed signature");
7553 assert_eq!(
7554 message,
7555 decrypted.as_ref(),
7556 "Decrypted message should match original message"
7557 );
7558 break;
7559 }
7560 });
7561 }
7562
7563 #[test_traced]
7564 fn test_tle() {
7565 tle::<MinPk, Random>(Random::new(RandomVersion::V1));
7566 tle::<MinSig, Random>(Random::new(RandomVersion::V1));
7567 }
7568
7569 fn run_hailstorm<S, F, L>(
7570 seed: u64,
7571 shutdowns: usize,
7572 interval: ViewDelta,
7573 elector: L,
7574 mut fixture: F,
7575 ) -> String
7576 where
7577 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
7578 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
7579 L: elector::Config<S>,
7580 {
7581 let n = 5;
7583 let view_retention = ViewDelta::new(10);
7584 let skip_timeout = Duration::from_secs(11);
7585 let namespace = b"consensus".to_vec();
7586 let cfg = deterministic::Config::new().with_seed(seed);
7587 let executor = deterministic::Runner::new(cfg);
7588 executor.start(|mut context| async move {
7589 let Fixture {
7591 participants,
7592 schemes,
7593 ..
7594 } = fixture(&mut context, &namespace, n);
7595 let mut oracle =
7596 start_test_network_with_peers(context.child("network"), participants.clone(), true)
7597 .await;
7598 let mut registrations = register_validators(&mut oracle, &participants).await;
7599
7600 let link = Link {
7602 latency: Duration::from_millis(10),
7603 jitter: Duration::from_millis(1),
7604 success_rate: probability!(1.0),
7605 };
7606 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
7607
7608 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
7610 let mut reporters = BTreeMap::new();
7611 let mut engine_handlers = BTreeMap::new();
7612 for (idx, validator) in participants.iter().enumerate() {
7613 let context = context
7615 .child("validator")
7616 .with_attribute("public_key", validator);
7617
7618 let reporter_config = mocks::reporter::Config {
7620 participants: participants.clone().try_into().unwrap(),
7621 scheme: schemes[idx].clone(),
7622 elector: elector.clone(),
7623 };
7624 let reporter =
7625 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
7626 reporters.insert(idx, reporter.clone());
7627 let application_cfg = mocks::application::Config::<Sha256, _> {
7628 relay: relay.clone(),
7629 me: validator.clone(),
7630 propose_latency: (10.0, 5.0),
7631 verify_latency: (10.0, 5.0),
7632 certify_latency: (10.0, 5.0),
7633 should_certify: mocks::application::Certifier::Always,
7634 };
7635 let (actor, application) = mocks::application::Application::new(
7636 context.child("application"),
7637 application_cfg,
7638 );
7639 actor.start();
7640 let blocker = oracle.control(validator.clone());
7641 let cfg = config::Config {
7642 scheme: schemes[idx].clone(),
7643 elector: elector.clone(),
7644 blocker,
7645 automaton: application.clone(),
7646 relay: application.clone(),
7647 reporter: reporter.clone(),
7648 strategy: Sequential,
7649 partition: validator.to_string(),
7650 mailbox_size: NZUsize!(1024),
7651 epoch: Epoch::new(333),
7652 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
7653 Epoch::new(333),
7654 )),
7655 leader_timeout: Duration::from_secs(1),
7656 certification_timeout: Duration::from_secs(2),
7657 timeout_retry: Duration::from_secs(10),
7658 fetch_timeout: Duration::from_secs(1),
7659 view_retention,
7660 skip: SkipPolicy::Enabled {
7661 timeout: skip_timeout,
7662 budget: SkipBudget::Participants,
7663 },
7664 replay_buffer: NZUsize!(1024 * 1024),
7665 write_buffer: NZUsize!(1024 * 1024),
7666 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
7667 forward: ForwardPolicy::Disabled,
7668 track_historical_votes: false,
7669 };
7670 let engine = Engine::new(context.child("engine"), cfg);
7671
7672 let (pending, recovered, resolver) = registrations
7674 .remove(validator)
7675 .expect("validator should be registered");
7676 engine_handlers.insert(idx, engine.start(pending, recovered, resolver));
7677 }
7678
7679 let mut target = View::zero();
7681 for i in 0..shutdowns {
7682 target = target.saturating_add(interval);
7684
7685 let mut finalizers = Vec::new();
7687 for reporter in reporters.values_mut() {
7688 let (mut latest, mut monitor) = reporter.subscribe().await;
7689 finalizers.push(context.child("finalizer").spawn(move |_| async move {
7690 while latest < target {
7691 latest = monitor.recv().await.expect("event missing");
7692 }
7693 }));
7694 }
7695 join_all(finalizers).await;
7696 target = target.saturating_add(interval);
7697
7698 let idx = context.random_range(0..engine_handlers.len());
7700 let validator = &participants[idx];
7701 let handle = engine_handlers.remove(&idx).unwrap();
7702 handle.abort();
7703 let _ = handle.await;
7704 let selected_reporter = reporters.remove(&idx).unwrap();
7705 info!(idx, ?validator, "shutdown validator");
7706
7707 let mut finalizers = Vec::new();
7709 for reporter in reporters.values_mut() {
7710 let (mut latest, mut monitor) = reporter.subscribe().await;
7711 finalizers.push(context.child("finalizer").spawn(move |_| async move {
7712 while latest < target {
7713 latest = monitor.recv().await.expect("event missing");
7714 }
7715 }));
7716 }
7717 join_all(finalizers).await;
7718 target = target.saturating_add(interval);
7719
7720 info!(idx, ?validator, "restarting validator");
7722 let context = context
7723 .child("validator_restarted")
7724 .with_attribute("public_key", validator)
7725 .with_attribute("restart", i);
7726
7727 let (pending, recovered, resolver) =
7729 register_validator(&mut oracle, validator.clone()).await;
7730 let application_cfg = mocks::application::Config::<Sha256, _> {
7731 relay: relay.clone(),
7732 me: validator.clone(),
7733 propose_latency: (10.0, 5.0),
7734 verify_latency: (10.0, 5.0),
7735 certify_latency: (10.0, 5.0),
7736 should_certify: mocks::application::Certifier::Always,
7737 };
7738 let (actor, application) = mocks::application::Application::new(
7739 context.child("application"),
7740 application_cfg,
7741 );
7742 actor.start();
7743 reporters.insert(idx, selected_reporter.clone());
7744 let blocker = oracle.control(validator.clone());
7745 let cfg = config::Config {
7746 scheme: schemes[idx].clone(),
7747 elector: elector.clone(),
7748 blocker,
7749 automaton: application.clone(),
7750 relay: application.clone(),
7751 reporter: selected_reporter,
7752 strategy: Sequential,
7753 partition: validator.to_string(),
7754 mailbox_size: NZUsize!(1024),
7755 epoch: Epoch::new(333),
7756 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
7757 Epoch::new(333),
7758 )),
7759 leader_timeout: Duration::from_secs(1),
7760 certification_timeout: Duration::from_secs(2),
7761 timeout_retry: Duration::from_secs(10),
7762 fetch_timeout: Duration::from_secs(1),
7763 view_retention,
7764 skip: SkipPolicy::Enabled {
7765 timeout: skip_timeout,
7766 budget: SkipBudget::Participants,
7767 },
7768 replay_buffer: NZUsize!(1024 * 1024),
7769 write_buffer: NZUsize!(1024 * 1024),
7770 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
7771 forward: ForwardPolicy::Disabled,
7772 track_historical_votes: false,
7773 };
7774 let engine = Engine::new(context.child("engine"), cfg);
7775 engine_handlers.insert(idx, engine.start(pending, recovered, resolver));
7776
7777 let mut finalizers = Vec::new();
7779 for reporter in reporters.values_mut() {
7780 let (mut latest, mut monitor) = reporter.subscribe().await;
7781 finalizers.push(context.child("finalizer").spawn(move |_| async move {
7782 while latest < target {
7783 latest = monitor.recv().await.expect("event missing");
7784 }
7785 }));
7786 }
7787 join_all(finalizers).await;
7788 info!(idx, ?validator, "validator recovered");
7789 }
7790
7791 let latest_complete = target.saturating_sub(view_retention);
7793 for reporter in reporters.values() {
7794 reporter.assert_no_faults();
7796
7797 reporter.assert_no_invalid();
7799
7800 let mut notarized = HashMap::new();
7802 let mut finalized = HashMap::new();
7803 {
7804 let notarizes = reporter.notarizes.lock();
7805 for view in View::range(View::new(1), latest_complete) {
7806 let Some(payloads) = notarizes.get(&view) else {
7808 continue;
7809 };
7810 if payloads.len() > 1 {
7811 panic!("view: {view}");
7812 }
7813 let (digest, _) = payloads.iter().next().unwrap();
7814 notarized.insert(view, *digest);
7815 }
7816 }
7817 {
7818 let notarizations = reporter.notarizations.lock();
7819 for view in View::range(View::new(1), latest_complete) {
7820 let Some(notarization) = notarizations.get(&view) else {
7822 continue;
7823 };
7824 let Some(digest) = notarized.get(&view) else {
7825 continue;
7826 };
7827 assert_eq!(¬arization.proposal.payload, digest);
7828 }
7829 }
7830 {
7831 let finalizes = reporter.finalizes.lock();
7832 for view in View::range(View::new(1), latest_complete) {
7833 let Some(payloads) = finalizes.get(&view) else {
7835 continue;
7836 };
7837 if payloads.len() > 1 {
7838 panic!("view: {view}");
7839 }
7840 let (digest, _) = payloads.iter().next().unwrap();
7841 finalized.insert(view, *digest);
7842
7843 if view > latest_complete {
7845 continue;
7846 }
7847
7848 let nullifies = reporter.nullifies.lock();
7850 let Some(nullifies) = nullifies.get(&view) else {
7851 continue;
7852 };
7853 for finalizers in payloads.values() {
7854 for finalizer in finalizers.iter() {
7855 if nullifies.contains(finalizer) {
7856 panic!("should not nullify and finalize at same view");
7857 }
7858 }
7859 }
7860 }
7861 }
7862 {
7863 let finalizations = reporter.finalizations.lock();
7864 for view in View::range(View::new(1), latest_complete) {
7865 let Some(finalization) = finalizations.get(&view) else {
7867 continue;
7868 };
7869 let Some(digest) = finalized.get(&view) else {
7870 continue;
7871 };
7872 assert_eq!(&finalization.proposal.payload, digest);
7873 }
7874 }
7875 }
7876
7877 let blocked = oracle.blocked().await.unwrap();
7879 assert!(blocked.is_empty());
7880
7881 context.auditor().state()
7883 })
7884 }
7885
7886 fn hailstorm<S, F, L>(fixture: F, elector: L)
7889 where
7890 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
7891 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S> + Copy,
7892 L: elector::Config<S>,
7893 {
7894 assert_eq!(
7895 run_hailstorm::<_, _, L>(0, 10, ViewDelta::new(15), elector.clone(), fixture,),
7896 run_hailstorm::<_, _, L>(0, 10, ViewDelta::new(15), elector, fixture),
7897 );
7898 }
7899
7900 test_for_all_fixtures!(hailstorm);
7901
7902 #[test_group("slow")]
7903 #[test_traced]
7904 fn test_hailstorm_stable_leader_ed25519() {
7905 assert_eq!(
7906 run_hailstorm::<_, _, RoundRobin>(
7907 0,
7908 10,
7909 ViewDelta::new(15),
7910 RoundRobin::default().with_term(
7911 TermLength::new(NZU32!(3)),
7912 Duration::from_secs(12),
7913 ViewDelta::new(0)
7914 ),
7915 ed25519::fixture
7916 ),
7917 run_hailstorm::<_, _, RoundRobin>(
7918 0,
7919 10,
7920 ViewDelta::new(15),
7921 RoundRobin::default().with_term(
7922 TermLength::new(NZU32!(3)),
7923 Duration::from_secs(12),
7924 ViewDelta::new(0)
7925 ),
7926 ed25519::fixture
7927 )
7928 );
7929 }
7930
7931 #[derive(Clone, Copy, Debug)]
7973 struct TwinsCampaign {
7974 n: u32,
7975 rounds: usize,
7976 mode: twins::Mode,
7977 max_cases: usize,
7978 trailing_finalizations: usize,
7979 }
7980
7981 fn twins_campaign<S, F, L>(
7982 rng: &mut impl CryptoRng,
7983 campaign: TwinsCampaign,
7984 elector: L,
7985 link: Link,
7986 mut fixture: F,
7987 ) where
7988 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
7989 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
7990 L: elector::Config<S>,
7991 {
7992 let n = campaign.n;
7993 let faults = N3f1::max_faults(n) as usize;
7994 let cases = twins::cases(
7995 rng,
7996 twins::Framework {
7997 participants: n as usize,
7998 faults,
7999 rounds: campaign.rounds,
8000 mode: campaign.mode,
8001 max_cases: campaign.max_cases,
8002 },
8003 );
8004 assert!(
8005 !cases.is_empty(),
8006 "twins campaign should generate at least one case"
8007 );
8008 for case in cases {
8009 let scenario = case.scenario.clone();
8010 let twin_indices = case.compromised.clone();
8011 assert_eq!(
8012 twin_indices.len(),
8013 faults,
8014 "unexpected twins count for n={n} (expected f={faults})",
8015 );
8016
8017 let view_retention = ViewDelta::new(10);
8018 let skip_timeout = Duration::from_secs(11);
8019 let namespace = b"consensus".to_vec();
8020 let link = link.clone();
8021 let trailing_finalizations = campaign.trailing_finalizations;
8022 let elector = elector.clone();
8023 let mut case_fixture =
8024 |ctx: &mut deterministic::Context, ns: &[u8], n: u32| fixture(ctx, ns, n);
8025 let rng: deterministic::BoxDynRng = Box::new(StdRng::from_rng(&mut *rng));
8026 let cfg = deterministic::Config::new().with_rng(rng);
8027 let executor = deterministic::Runner::new(cfg);
8028 executor.start(|mut context| async move {
8029 let Fixture {
8030 participants,
8031 schemes,
8032 ..
8033 } = case_fixture(&mut context, &namespace, n);
8034 let participants: Arc<[_]> = participants.into();
8035 let mut oracle = start_test_network_with_peers(
8036 context.child("network"),
8037 participants.iter().cloned(),
8038 false,
8039 )
8040 .await;
8041 let mut registrations = register_validators(&mut oracle, &participants).await;
8042 link_validators(&mut oracle, &participants, Action::Link(link), None).await;
8043
8044 let term_length = elector
8048 .clone()
8049 .build(schemes[0].participants())
8050 .terms()
8051 .length();
8052 let elector = TwinsElector::new(
8053 elector.clone(),
8054 &scenario,
8055 n as usize,
8056 );
8057 let relay = Arc::new(mocks::relay::Relay::<Sha256Digest, _>::new());
8058 let mut reporters = Vec::new();
8059 let mut engine_handlers = Vec::new();
8060 let twin_index_set: HashSet<usize> = twin_indices.iter().copied().collect();
8061
8062 for idx in twin_indices.iter().copied() {
8064 let validator = &participants[idx];
8065 let (
8066 (vote_sender, vote_receiver),
8067 (certificate_sender, certificate_receiver),
8068 (_resolver_sender, _resolver_receiver),
8069 ) = registrations
8070 .remove(validator)
8071 .expect("validator should be registered");
8072
8073 let make_vote_forwarder = || {
8074 let participants = participants.clone();
8075 let scenario = scenario.clone();
8076 move |origin: SplitOrigin, _: &Recipients<_>, message: &IoBuf| {
8077 let msg: Vote<S, D> = Vote::decode(message.clone()).unwrap();
8078 let (primary, secondary) =
8079 scenario.partitions(msg.view(), term_length, participants.as_ref());
8080 match origin {
8081 SplitOrigin::Primary => Some(Recipients::Some(primary)),
8082 SplitOrigin::Secondary => Some(Recipients::Some(secondary)),
8083 }
8084 }
8085 };
8086 let make_certificate_forwarder = || {
8087 let codec = schemes[idx].certificate_codec_config();
8088 let participants = participants.clone();
8089 let scenario = scenario.clone();
8090 move |origin: SplitOrigin, _: &Recipients<_>, message: &IoBuf| {
8091 let msg: Certificate<S, D> =
8092 Certificate::decode_cfg(&mut message.as_ref(), &codec).unwrap();
8093 let (primary, secondary) =
8094 scenario.partitions(msg.view(), term_length, participants.as_ref());
8095 match origin {
8096 SplitOrigin::Primary => Some(Recipients::Some(primary)),
8097 SplitOrigin::Secondary => Some(Recipients::Some(secondary)),
8098 }
8099 }
8100 };
8101 let make_vote_router = || {
8102 let participants = participants.clone();
8103 let scenario = scenario.clone();
8104 move |(sender, message): &(_, IoBuf)| {
8105 let msg: Vote<S, D> = Vote::decode(message.clone()).unwrap();
8106 scenario.route(msg.view(), term_length, sender, participants.as_ref())
8107 }
8108 };
8109 let make_certificate_router = || {
8110 let codec = schemes[idx].certificate_codec_config();
8111 let participants = participants.clone();
8112 let scenario = scenario.clone();
8113 move |(sender, message): &(_, IoBuf)| {
8114 let msg: Certificate<S, D> =
8115 Certificate::decode_cfg(&mut message.as_ref(), &codec).unwrap();
8116 scenario.route(msg.view(), term_length, sender, participants.as_ref())
8117 }
8118 };
8119 let (vote_sender_primary, vote_sender_secondary) =
8120 vote_sender.split_with(make_vote_forwarder());
8121 let (vote_receiver_primary, vote_receiver_secondary) = vote_receiver
8122 .split_with(
8123 context.child("pending_split").with_attribute("index", idx),
8124 make_vote_router(),
8125 );
8126 let (certificate_sender_primary, certificate_sender_secondary) =
8127 certificate_sender.split_with(make_certificate_forwarder());
8128 let (certificate_receiver_primary, certificate_receiver_secondary) =
8129 certificate_receiver.split_with(
8130 context
8131 .child("recovered_split")
8132 .with_attribute("index", idx),
8133 make_certificate_router(),
8134 );
8135
8136 for (twin_label, pending, recovered) in [
8137 (
8138 "primary",
8139 (vote_sender_primary, vote_receiver_primary),
8140 (certificate_sender_primary, certificate_receiver_primary),
8141 ),
8142 (
8143 "secondary",
8144 (vote_sender_secondary, vote_receiver_secondary),
8145 (certificate_sender_secondary, certificate_receiver_secondary),
8146 ),
8147 ] {
8148 let partition = format!("twin_{idx}_{twin_label}");
8149 let context = context
8150 .child("twin")
8151 .with_attribute("index", idx)
8152 .with_attribute("side", twin_label);
8153
8154 let reporter_config = mocks::reporter::Config {
8155 participants: participants.as_ref().try_into().unwrap(),
8156 scheme: schemes[idx].clone(),
8157 elector: elector.clone(),
8158 };
8159 let reporter = mocks::reporter::Reporter::new(
8160 context.child("reporter"),
8161 reporter_config,
8162 );
8163 reporters.push(reporter.clone());
8164
8165 let application_cfg = mocks::application::Config::<Sha256, _> {
8166 relay: relay.clone(),
8167 me: validator.clone(),
8168 propose_latency: (10.0, 5.0),
8169 verify_latency: (10.0, 5.0),
8170 certify_latency: (10.0, 5.0),
8171 should_certify: mocks::application::Certifier::Always,
8172 };
8173 let (actor, application) = mocks::application::Application::new(
8174 context.child("application"),
8175 application_cfg,
8176 );
8177 actor.start();
8178
8179 let blocker = oracle.control(validator.clone());
8180 let cfg = config::Config {
8181 scheme: schemes[idx].clone(),
8182 elector: elector.clone(),
8183 blocker,
8184 automaton: application.clone(),
8185 relay: application.clone(),
8186 reporter: reporter.clone(),
8187 strategy: Sequential,
8188 partition,
8189 mailbox_size: NZUsize!(1024),
8190 epoch: Epoch::new(333),
8191 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
8192 Epoch::new(333),
8193 )),
8194 leader_timeout: Duration::from_secs(1),
8195 certification_timeout: Duration::from_millis(1_500),
8196 timeout_retry: Duration::from_secs(10),
8197 fetch_timeout: Duration::from_secs(1),
8198 view_retention,
8199 skip: SkipPolicy::Enabled {
8200 timeout: skip_timeout,
8201 budget: SkipBudget::Participants,
8202 },
8203 replay_buffer: NZUsize!(1024 * 1024),
8204 write_buffer: NZUsize!(1024 * 1024),
8205 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
8206 forward: ForwardPolicy::Disabled,
8207 track_historical_votes: false,
8208 };
8209 let engine = Engine::new(context.child("engine"), cfg);
8210 engine_handlers.push(engine.start(
8211 pending,
8212 recovered,
8213 inert_channel(participants.as_ref()),
8214 ));
8215 }
8216 }
8217
8218 let honest_start = reporters.len();
8220 for (idx, validator) in participants.iter().enumerate() {
8221 if twin_index_set.contains(&idx) {
8222 continue;
8223 }
8224
8225 let partition = format!("honest_{idx}");
8226 let context = context.child("honest").with_attribute("index", idx);
8227
8228 let reporter_config = mocks::reporter::Config {
8229 participants: participants.as_ref().try_into().unwrap(),
8230 scheme: schemes[idx].clone(),
8231 elector: elector.clone(),
8232 };
8233 let reporter =
8234 mocks::reporter::Reporter::new(context.child("reporter"), reporter_config);
8235 reporters.push(reporter.clone());
8236
8237 let application_cfg = mocks::application::Config::<Sha256, _> {
8238 relay: relay.clone(),
8239 me: validator.clone(),
8240 propose_latency: (10.0, 5.0),
8241 verify_latency: (10.0, 5.0),
8242 certify_latency: (10.0, 5.0),
8243 should_certify: mocks::application::Certifier::Always,
8244 };
8245 let (actor, application) = mocks::application::Application::new(
8246 context.child("application"),
8247 application_cfg,
8248 );
8249 actor.start();
8250
8251 let blocker = oracle.control(validator.clone());
8252 let cfg = config::Config {
8253 scheme: schemes[idx].clone(),
8254 elector: elector.clone(),
8255 blocker,
8256 automaton: application.clone(),
8257 relay: application.clone(),
8258 reporter: reporter.clone(),
8259 strategy: Sequential,
8260 partition,
8261 mailbox_size: NZUsize!(1024),
8262 epoch: Epoch::new(333),
8263 floor: config::Floor::Genesis(mocks::application::genesis::<Sha256>(
8264 Epoch::new(333),
8265 )),
8266 leader_timeout: Duration::from_secs(1),
8267 certification_timeout: Duration::from_millis(1_500),
8268 timeout_retry: Duration::from_secs(10),
8269 fetch_timeout: Duration::from_secs(1),
8270 view_retention,
8271 skip: SkipPolicy::Enabled {
8272 timeout: skip_timeout,
8273 budget: SkipBudget::Participants,
8274 },
8275 replay_buffer: NZUsize!(1024 * 1024),
8276 write_buffer: NZUsize!(1024 * 1024),
8277 page_cache: CacheRef::from_pooler(&context, PAGE_SIZE, PAGE_CACHE_SIZE),
8278 forward: ForwardPolicy::Disabled,
8279 track_historical_votes: false,
8280 };
8281 let engine = Engine::new(context.child("engine"), cfg);
8282
8283 let (
8284 (pending_sender, pending_receiver),
8285 (recovered_sender, recovered_receiver),
8286 _,
8287 ) = registrations
8288 .remove(validator)
8289 .expect("validator should be registered");
8290 engine_handlers.push(engine.start(
8291 (pending_sender, pending_receiver),
8292 (recovered_sender, recovered_receiver),
8293 inert_channel(participants.as_ref()),
8294 ));
8295 }
8296
8297 let prefix_end = View::new(scenario.rounds().len() as u64 * term_length.get());
8310 let mut finalizers = Vec::new();
8311 for (i, reporter) in reporters.iter_mut().skip(honest_start).enumerate() {
8312 let (_latest, mut monitor) = reporter.subscribe().await;
8313 let required = trailing_finalizations;
8314 finalizers.push(context.child("finalizer").with_attribute("index", i).spawn(
8315 move |_| async move {
8316 let mut count = 0usize;
8317 while count < required {
8318 let view = monitor.recv().await.expect("event missing");
8319 if view > prefix_end {
8320 count += 1;
8321 }
8322 }
8323 },
8324 ));
8325 }
8326 join_all(finalizers).await;
8327
8328 let mut finalized_at_view: BTreeMap<View, D> = BTreeMap::new();
8330 for reporter in reporters.iter().skip(honest_start) {
8331 let finalizations = reporter.finalizations.lock();
8332 for (view, finalization) in finalizations.iter() {
8333 let digest = finalization.proposal.payload;
8334 if let Some(existing) = finalized_at_view.get(view) {
8335 assert_eq!(
8336 existing, &digest,
8337 "safety violation: conflicting finalizations at view {view}"
8338 );
8339 } else {
8340 finalized_at_view.insert(*view, digest);
8341 }
8342 }
8343 }
8344
8345 for reporter in reporters.iter().skip(honest_start) {
8347 reporter.assert_no_invalid();
8348 }
8349
8350 let twin_keys: HashSet<_> = twin_indices
8352 .iter()
8353 .map(|idx| participants[*idx].clone())
8354 .collect();
8355 let mut notarized_by_signer: BTreeMap<View, HashMap<PublicKey, D>> =
8356 BTreeMap::new();
8357 let mut finalized_by_signer: BTreeMap<View, HashMap<PublicKey, D>> =
8358 BTreeMap::new();
8359 for reporter in reporters.iter().skip(honest_start) {
8360 let notarizes = reporter.notarizes.lock();
8361 for (view, payloads) in notarizes.iter() {
8362 let signers = notarized_by_signer.entry(*view).or_default();
8363 for (digest, payload_signers) in payloads.iter() {
8364 for signer in payload_signers.iter() {
8365 if twin_keys.contains(signer) {
8366 continue;
8367 }
8368 if let Some(existing) = signers.insert(signer.clone(), *digest) {
8369 assert_eq!(
8370 existing, *digest,
8371 "honest signer produced conflicting notarizes at view {view}"
8372 );
8373 }
8374 }
8375 }
8376 }
8377
8378 let finalizes = reporter.finalizes.lock();
8379 for (view, payloads) in finalizes.iter() {
8380 let signers = finalized_by_signer.entry(*view).or_default();
8381 for (digest, payload_signers) in payloads.iter() {
8382 for signer in payload_signers.iter() {
8383 if twin_keys.contains(signer) {
8384 continue;
8385 }
8386 if let Some(existing) = signers.insert(signer.clone(), *digest) {
8387 assert_eq!(
8388 existing, *digest,
8389 "honest signer produced conflicting finalizes at view {view}"
8390 );
8391 }
8392 }
8393 }
8394 }
8395 }
8396
8397 for reporter in reporters.iter().skip(honest_start) {
8399 let faults = reporter.faults.lock();
8400 for faulter in faults.keys() {
8401 assert!(
8402 twin_keys.contains(faulter),
8403 "fault from non-twin participant"
8404 );
8405 }
8406 }
8407
8408 let blocked = oracle.blocked().await.unwrap();
8409 for (_, faulter) in blocked {
8410 assert!(
8411 twin_keys.contains(&faulter),
8412 "blocked peer attributed to non-twin participant"
8413 );
8414 }
8415 });
8416 }
8417 }
8418
8419 const TWINS_CAMPAIGN: TwinsCampaign = TwinsCampaign {
8420 n: 5,
8421 rounds: 3,
8422 mode: twins::Mode::Sampled,
8423 max_cases: 20,
8424 trailing_finalizations: 10,
8425 };
8426
8427 const TWINS_LINK: Link = Link {
8428 latency: Duration::from_millis(500),
8429 jitter: Duration::from_millis(500),
8430 success_rate: probability!(1.0),
8431 };
8432
8433 fn twins_campaign_all_links(campaign: TwinsCampaign, elector: RoundRobin) {
8435 for link in [
8436 Link {
8437 latency: Duration::from_millis(10),
8438 jitter: Duration::from_millis(10),
8439 success_rate: probability!(1.0),
8440 },
8441 TWINS_LINK,
8442 ] {
8443 twins_campaign::<_, _, RoundRobin>(
8444 &mut test_rng(),
8445 campaign,
8446 elector.clone(),
8447 link,
8448 scheme_mocks::fixture,
8449 );
8450 }
8451 }
8452
8453 #[test_group("slow")]
8454 #[test_traced("INFO")]
8455 fn test_twins_sampled() {
8456 twins_campaign_all_links(TWINS_CAMPAIGN, RoundRobin::default());
8457 }
8458
8459 #[test_group("slow")]
8460 #[test_traced("INFO")]
8461 fn test_twins_sustained() {
8462 twins_campaign_all_links(
8463 TwinsCampaign {
8464 mode: twins::Mode::Sustained,
8465 ..TWINS_CAMPAIGN
8466 },
8467 RoundRobin::default(),
8468 );
8469 }
8470
8471 #[test_group("slow")]
8472 #[test_traced("INFO")]
8473 fn test_twins_stable_leader() {
8474 twins_campaign_all_links(
8475 TWINS_CAMPAIGN,
8476 RoundRobin::default().with_term(
8477 TermLength::new(NZU32!(3)),
8478 Duration::from_secs(12),
8479 ViewDelta::new(0),
8480 ),
8481 );
8482 }
8483
8484 #[test_group("slow")]
8485 #[test_traced("INFO")]
8486 fn test_twins_stable_leader_optimistic() {
8487 twins_campaign_all_links(
8488 TWINS_CAMPAIGN,
8489 RoundRobin::default().with_term(
8490 TermLength::new(NZU32!(3)),
8491 Duration::from_secs(12),
8492 ViewDelta::new(2),
8493 ),
8494 );
8495 }
8496
8497 #[test_group("slow")]
8498 #[test_traced("INFO")]
8499 fn test_twins_large_sampled() {
8500 let campaign = TwinsCampaign {
8501 n: 10,
8502 rounds: 5,
8503 ..TWINS_CAMPAIGN
8504 };
8505 twins_campaign::<_, _, RoundRobin>(
8506 &mut test_rng(),
8507 campaign,
8508 RoundRobin::default(),
8509 TWINS_LINK,
8510 scheme_mocks::fixture,
8511 );
8512 }
8513
8514 #[test_group("slow")]
8515 #[test_traced("INFO")]
8516 fn test_twins_large_sustained() {
8517 let campaign = TwinsCampaign {
8518 n: 10,
8519 rounds: 5,
8520 mode: twins::Mode::Sustained,
8521 ..TWINS_CAMPAIGN
8522 };
8523 twins_campaign::<_, _, RoundRobin>(
8524 &mut test_rng(),
8525 campaign,
8526 RoundRobin::default(),
8527 TWINS_LINK,
8528 scheme_mocks::fixture,
8529 );
8530 }
8531
8532 fn twins<S, F, L>(fixture: F, elector: L)
8533 where
8534 S: Scheme<Sha256Digest, PublicKey = PublicKey>,
8535 F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
8536 L: elector::Config<S>,
8537 {
8538 twins_campaign::<_, _, L>(
8539 &mut test_rng(),
8540 TWINS_CAMPAIGN,
8541 elector,
8542 TWINS_LINK,
8543 fixture,
8544 );
8545 }
8546
8547 test_for_all_fixtures!(twins, level = "INFO");
8548
8549 #[test]
8550 fn test_viewport() {
8551 let viewport = Viewport {
8552 finalized: View::new(20),
8553 current: View::new(25),
8554 view_retention: ViewDelta::new(10),
8555 lookahead: Lookahead {
8556 term_length: TermLength::new(commonware_utils::NZU32!(10)),
8557 optimistic_views: ViewDelta::new(0),
8558 },
8559 };
8560
8561 assert!(!viewport.retains(View::zero()));
8563
8564 assert_eq!(viewport.floor(), View::new(10));
8566 assert!(!viewport.retains(View::new(9)));
8567 assert!(viewport.retains(View::new(10)));
8568
8569 assert!(viewport.admits_vote(View::new(10)));
8571 assert!(viewport.admits_vote(View::new(25)));
8572 assert!(viewport.admits_vote(View::new(26)));
8573 assert!(viewport.admits_vote(View::new(31)));
8574 assert!(!viewport.admits_vote(View::new(5)));
8575 assert!(!viewport.admits_vote(View::new(27)));
8576 assert!(!viewport.admits_vote(View::new(34)));
8577
8578 assert!(!viewport.admits_certificate(View::new(9)));
8581 assert!(viewport.admits_certificate(View::new(10_000)));
8582
8583 let optimistic = Viewport {
8586 lookahead: Lookahead {
8587 optimistic_views: ViewDelta::new(2),
8588 ..viewport.lookahead
8589 },
8590 ..viewport
8591 };
8592 assert!(optimistic.admits_vote(View::new(26)));
8593 assert!(optimistic.admits_vote(View::new(27)));
8594 assert!(!optimistic.admits_vote(View::new(28)));
8595 assert!(optimistic.admits_vote(View::new(31)));
8596
8597 let term_edge = Viewport {
8599 current: View::new(29),
8600 lookahead: Lookahead {
8601 optimistic_views: ViewDelta::new(5),
8602 ..viewport.lookahead
8603 },
8604 ..viewport
8605 };
8606 assert!(term_edge.admits_vote(View::new(30)));
8607 assert!(term_edge.admits_vote(View::new(31)));
8608 assert!(!term_edge.admits_vote(View::new(32)));
8609 }
8610}