mod actor;
mod ingress;
mod round;
mod slot;
mod state;
use crate::{
simplex::{elector::Config as Elector, types::Activity},
types::{Epoch, ViewDelta},
CertifiableAutomaton, Relay, Reporter,
};
pub use actor::Actor;
use commonware_cryptography::{certificate::Scheme, Digest};
use commonware_p2p::Blocker;
use commonware_runtime::buffer::PoolRef;
pub use ingress::Mailbox;
#[cfg(test)]
pub use ingress::Message;
use std::{num::NonZeroUsize, time::Duration};
pub struct Config<
S: Scheme,
L: Elector<S>,
B: Blocker,
D: Digest,
A: CertifiableAutomaton,
R: Relay<Digest = D>,
F: Reporter<Activity = Activity<S, D>>,
> {
pub scheme: S,
pub elector: L,
pub blocker: B,
pub automaton: A,
pub relay: R,
pub reporter: F,
pub partition: String,
pub epoch: Epoch,
pub mailbox_size: usize,
pub leader_timeout: Duration,
pub notarization_timeout: Duration,
pub nullify_retry: Duration,
pub activity_timeout: ViewDelta,
pub replay_buffer: NonZeroUsize,
pub write_buffer: NonZeroUsize,
pub buffer_pool: PoolRef,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
simplex::{
actors::{
batcher,
resolver::{self, MailboxMessage},
},
elector::{Config as ElectorConfig, Elector, Random, RoundRobin, RoundRobinElector},
mocks, quorum,
scheme::{bls12381_multisig, bls12381_threshold, ed25519, secp256r1, Scheme},
types::{Certificate, Finalization, Finalize, Notarization, Notarize, Proposal, Vote},
},
types::{Participant, Round, View},
Viewable,
};
use commonware_codec::Encode;
use commonware_cryptography::{
bls12381::primitives::variant::{MinPk, MinSig},
certificate::mocks::Fixture,
ed25519::PublicKey,
sha256::Digest as Sha256Digest,
Hasher as _, Sha256,
};
use commonware_macros::{select, test_collect_traces, test_traced};
use commonware_p2p::simulated::{Config as NConfig, Network};
use commonware_parallel::Sequential;
use commonware_runtime::{
deterministic, telemetry::traces::collector::TraceStorage, Clock, Metrics, Quota, Runner,
};
use commonware_utils::{NZUsize, NZU16};
use futures::{channel::mpsc, FutureExt, StreamExt};
use std::{
num::{NonZeroU16, NonZeroU32},
sync::{Arc, Mutex},
time::Duration,
};
use tracing::Level;
const PAGE_SIZE: NonZeroU16 = NZU16!(1024);
const PAGE_CACHE_SIZE: NonZeroUsize = NZUsize!(10);
const TEST_QUOTA: Quota = Quota::per_second(NonZeroU32::MAX);
fn build_notarization<S: Scheme<Sha256Digest>>(
schemes: &[S],
proposal: &Proposal<Sha256Digest>,
count: u32,
) -> (
Vec<Notarize<S, Sha256Digest>>,
Notarization<S, Sha256Digest>,
) {
let votes: Vec<_> = schemes
.iter()
.take(count as usize)
.map(|scheme| Notarize::sign(scheme, proposal.clone()).unwrap())
.collect();
let certificate = Notarization::from_notarizes(&schemes[0], &votes, &Sequential)
.expect("notarization requires a quorum of votes");
(votes, certificate)
}
fn build_finalization<S: Scheme<Sha256Digest>>(
schemes: &[S],
proposal: &Proposal<Sha256Digest>,
count: u32,
) -> (
Vec<Finalize<S, Sha256Digest>>,
Finalization<S, Sha256Digest>,
) {
let votes: Vec<_> = schemes
.iter()
.take(count as usize)
.map(|scheme| Finalize::sign(scheme, proposal.clone()).unwrap())
.collect();
let certificate = Finalization::from_finalizes(&schemes[0], &votes, &Sequential)
.expect("finalization requires a quorum of votes");
(votes, certificate)
}
#[allow(clippy::too_many_arguments)]
async fn setup_voter<S, L>(
context: &mut deterministic::Context,
oracle: &commonware_p2p::simulated::Oracle<S::PublicKey, deterministic::Context>,
participants: &[S::PublicKey],
schemes: &[S],
elector: L,
leader_timeout: Duration,
notarization_timeout: Duration,
nullify_retry: Duration,
should_certify: mocks::application::Certifier<Sha256Digest>,
) -> (
Mailbox<S, Sha256Digest>,
mpsc::Receiver<batcher::Message<S, Sha256Digest>>,
mpsc::Receiver<resolver::MailboxMessage<S, Sha256Digest>>,
Arc<mocks::relay::Relay<Sha256Digest, S::PublicKey>>,
mocks::reporter::Reporter<deterministic::Context, S, L, Sha256Digest>,
)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
L: ElectorConfig<S>,
{
let signing = schemes[0].clone();
let me = participants[0].clone();
let reporter_cfg = mocks::reporter::Config {
participants: participants.to_vec().try_into().unwrap(),
scheme: signing.clone(),
elector: elector.clone(),
};
let reporter = mocks::reporter::Reporter::new(context.with_label("reporter"), reporter_cfg);
let relay = Arc::new(mocks::relay::Relay::new());
let application_cfg = mocks::application::Config {
hasher: Sha256::default(),
relay: relay.clone(),
me: me.clone(),
propose_latency: (1.0, 0.0),
verify_latency: (1.0, 0.0),
certify_latency: (1.0, 0.0),
should_certify,
};
let (actor, application) =
mocks::application::Application::new(context.with_label("app"), application_cfg);
actor.start();
let voter_cfg = Config {
scheme: signing.clone(),
elector,
blocker: oracle.control(me.clone()),
automaton: application.clone(),
relay: application.clone(),
reporter: reporter.clone(),
partition: format!("voter_test_{me}"),
epoch: Epoch::new(333),
mailbox_size: 128,
leader_timeout,
notarization_timeout,
nullify_retry,
activity_timeout: ViewDelta::new(10),
replay_buffer: NZUsize!(10240),
write_buffer: NZUsize!(10240),
buffer_pool: PoolRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let (voter, mailbox) = Actor::new(context.clone(), voter_cfg);
let (resolver_sender, resolver_receiver) = mpsc::channel(8);
let (batcher_sender, batcher_receiver) = mpsc::channel(16);
let (vote_sender, _) = oracle
.control(me.clone())
.register(0, TEST_QUOTA)
.await
.unwrap();
let (certificate_sender, _) = oracle
.control(me.clone())
.register(1, TEST_QUOTA)
.await
.unwrap();
voter.start(
batcher::Mailbox::new(batcher_sender),
resolver::Mailbox::new(resolver_sender),
vote_sender,
certificate_sender,
);
(
mailbox,
batcher_receiver,
resolver_receiver,
relay,
reporter,
)
}
async fn advance_to_view<S: Scheme<Sha256Digest>>(
mailbox: &mut Mailbox<S, Sha256Digest>,
batcher_receiver: &mut mpsc::Receiver<batcher::Message<S, Sha256Digest>>,
schemes: &[S],
quorum: u32,
target: View,
) -> Sha256Digest {
let prev_view = target.previous().expect("target view must be > 0");
let payload = Sha256::hash(prev_view.get().to_be_bytes().as_slice());
let proposal = Proposal::new(
Round::new(Epoch::new(333), prev_view),
prev_view.previous().unwrap_or(View::zero()),
payload,
);
let (_, finalization) = build_finalization(schemes, &proposal, quorum);
mailbox
.resolved(Certificate::Finalization(finalization))
.await;
loop {
match batcher_receiver.next().await.unwrap() {
batcher::Message::Update {
current, active, ..
} => {
active.send(true).unwrap();
if current < target {
continue;
}
assert_eq!(current, target);
break;
}
batcher::Message::Constructed(_) => {}
}
}
payload
}
fn stale_backfill<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"consensus".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(10));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let me = participants[0].clone();
let elector = L::default();
let reporter_config = mocks::reporter::Config {
participants: participants.clone().try_into().unwrap(),
scheme: schemes[0].clone(),
elector: elector.clone(),
};
let reporter =
mocks::reporter::Reporter::new(context.with_label("reporter"), reporter_config);
let relay = Arc::new(mocks::relay::Relay::new());
let application_cfg = mocks::application::Config {
hasher: Sha256::default(),
relay: relay.clone(),
me: me.clone(),
propose_latency: (10.0, 5.0),
verify_latency: (10.0, 5.0),
certify_latency: (10.0, 5.0),
should_certify: mocks::application::Certifier::Sometimes,
};
let (actor, application) = mocks::application::Application::new(
context.with_label("application"),
application_cfg,
);
actor.start();
let cfg = Config {
scheme: schemes[0].clone(),
elector,
blocker: oracle.control(me.clone()),
automaton: application.clone(),
relay: application.clone(),
reporter: reporter.clone(),
partition: "test".to_string(),
epoch: Epoch::new(333),
mailbox_size: 10,
leader_timeout: Duration::from_secs(5),
notarization_timeout: Duration::from_secs(5),
nullify_retry: Duration::from_secs(5),
activity_timeout: ViewDelta::new(10),
replay_buffer: NonZeroUsize::new(1024 * 1024).unwrap(),
write_buffer: NonZeroUsize::new(1024 * 1024).unwrap(),
buffer_pool: PoolRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let (actor, mut mailbox) = Actor::new(context.clone(), cfg);
let (resolver_sender, mut resolver_receiver) = mpsc::channel(10);
let resolver = resolver::Mailbox::new(resolver_sender);
let (batcher_sender, mut batcher_receiver) = mpsc::channel(1024);
let batcher = batcher::Mailbox::new(batcher_sender);
let (vote_sender, _vote_receiver) = oracle
.control(me.clone())
.register(0, TEST_QUOTA)
.await
.unwrap();
let (certificate_sender, _certificate_receiver) = oracle
.control(me.clone())
.register(1, TEST_QUOTA)
.await
.unwrap();
actor.start(batcher, resolver, vote_sender, certificate_sender);
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(1));
assert_eq!(finalized, View::new(0));
active.send(true).unwrap();
}
_ => panic!("unexpected batcher message"),
}
let payload = Sha256::hash(b"test");
let proposal = Proposal::new(
Round::new(Epoch::new(333), View::new(100)),
View::new(50),
payload,
);
let (_, finalization) = build_finalization(&schemes, &proposal, quorum);
mailbox
.recovered(Certificate::Finalization(finalization))
.await;
loop {
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(101));
assert_eq!(finalized, View::new(100));
active.send(true).unwrap();
break;
}
_ => {
continue;
}
}
}
let msg = resolver_receiver
.next()
.await
.expect("failed to receive resolver message");
match msg {
MailboxMessage::Certificate(Certificate::Finalization(finalization)) => {
assert_eq!(finalization.view(), View::new(100));
}
_ => panic!("unexpected resolver message"),
}
let payload = Sha256::hash(b"test2");
let proposal = Proposal::new(
Round::new(Epoch::new(333), View::new(50)),
View::new(49),
payload,
);
let (_, notarization) = build_notarization(&schemes, &proposal, quorum);
mailbox
.recovered(Certificate::Notarization(notarization))
.await;
let payload = Sha256::hash(b"test3");
let proposal = Proposal::new(
Round::new(Epoch::new(333), View::new(300)),
View::new(100),
payload,
);
let (_, finalization) = build_finalization(&schemes, &proposal, quorum);
mailbox
.recovered(Certificate::Finalization(finalization))
.await;
loop {
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(301));
assert_eq!(finalized, View::new(300));
active.send(true).unwrap();
break;
}
_ => {
continue;
}
}
}
let msg = resolver_receiver
.next()
.await
.expect("failed to receive resolver message");
match msg {
MailboxMessage::Certificate(Certificate::Finalization(finalization)) => {
assert_eq!(finalization.view(), View::new(300));
}
_ => panic!("unexpected resolver message"),
}
});
}
#[test_traced]
fn test_stale_backfill() {
stale_backfill::<_, _, Random>(bls12381_threshold::fixture::<MinPk, _>);
stale_backfill::<_, _, Random>(bls12381_threshold::fixture::<MinSig, _>);
stale_backfill::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinPk, _>);
stale_backfill::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinSig, _>);
stale_backfill::<_, _, RoundRobin>(ed25519::fixture);
stale_backfill::<_, _, RoundRobin>(secp256r1::fixture);
}
fn append_old_interesting_view<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"test_prune_panic".to_vec();
let activity_timeout = ViewDelta::new(10);
let executor = deterministic::Runner::timed(Duration::from_secs(20));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let signing = schemes[0].clone();
let me = participants[0].clone();
let elector = L::default();
let reporter_config = mocks::reporter::Config {
participants: participants.clone().try_into().unwrap(),
scheme: signing.clone(),
elector: elector.clone(),
};
let reporter =
mocks::reporter::Reporter::new(context.with_label("reporter"), reporter_config);
let relay = Arc::new(mocks::relay::Relay::new());
let app_config = mocks::application::Config {
hasher: Sha256::default(),
relay: relay.clone(),
me: me.clone(),
propose_latency: (1.0, 0.0),
verify_latency: (1.0, 0.0),
certify_latency: (1.0, 0.0),
should_certify: mocks::application::Certifier::Sometimes,
};
let (actor, application) =
mocks::application::Application::new(context.with_label("app"), app_config);
actor.start();
let voter_config = Config {
scheme: signing.clone(),
elector,
blocker: oracle.control(me.clone()),
automaton: application.clone(),
relay: application.clone(),
reporter: reporter.clone(),
partition: format!("voter_actor_test_{me}"),
epoch: Epoch::new(333),
mailbox_size: 128,
leader_timeout: Duration::from_millis(500),
notarization_timeout: Duration::from_millis(1000),
nullify_retry: Duration::from_millis(1000),
activity_timeout,
replay_buffer: NZUsize!(10240),
write_buffer: NZUsize!(10240),
buffer_pool: PoolRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let (actor, mut mailbox) = Actor::new(context.clone(), voter_config);
let (resolver_sender, mut resolver_receiver) = mpsc::channel(10);
let resolver_mailbox = resolver::Mailbox::new(resolver_sender);
let (batcher_sender, mut batcher_receiver) = mpsc::channel(10);
let batcher_mailbox = batcher::Mailbox::new(batcher_sender);
let (vote_sender, _vote_receiver) = oracle
.control(me.clone())
.register(0, TEST_QUOTA)
.await
.unwrap();
let (certificate_sender, _certificate_receiver) = oracle
.control(me.clone())
.register(1, TEST_QUOTA)
.await
.unwrap();
actor.start(
batcher_mailbox,
resolver_mailbox,
vote_sender,
certificate_sender,
);
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(1));
assert_eq!(finalized, View::new(0));
active.send(true).unwrap();
}
_ => panic!("unexpected batcher message"),
}
let lf_target = View::new(50);
let journal_floor_target = lf_target
.saturating_sub(activity_timeout)
.saturating_add(ViewDelta::new(5));
let proposal_lf = Proposal::new(
Round::new(Epoch::new(333), lf_target),
lf_target.previous().unwrap(),
Sha256::hash(b"test"),
);
let (_, finalization) = build_finalization(&schemes, &proposal_lf, quorum);
mailbox
.recovered(Certificate::Finalization(finalization))
.await;
loop {
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(51));
assert_eq!(finalized, View::new(50));
active.send(true).unwrap();
break;
}
_ => {
continue;
}
}
}
let msg = resolver_receiver
.next()
.await
.expect("failed to receive resolver message");
match msg {
MailboxMessage::Certificate(Certificate::Finalization(finalization)) => {
assert_eq!(finalization.view(), View::new(50));
}
_ => panic!("unexpected resolver message"),
}
let proposal_jft = Proposal::new(
Round::new(Epoch::new(333), journal_floor_target),
journal_floor_target.previous().unwrap(),
Sha256::hash(b"test2"),
);
let (_, notarization_for_floor) = build_notarization(&schemes, &proposal_jft, quorum);
mailbox
.recovered(Certificate::Notarization(notarization_for_floor))
.await;
let msg = resolver_receiver
.next()
.await
.expect("failed to receive resolver message");
match msg {
MailboxMessage::Certificate(Certificate::Notarization(notarization)) => {
assert_eq!(notarization.view(), journal_floor_target);
}
_ => panic!("unexpected resolver message"),
}
let problematic_view = journal_floor_target.saturating_sub(ViewDelta::new(3));
let proposal_bft = Proposal::new(
Round::new(Epoch::new(333), problematic_view),
problematic_view.previous().unwrap(),
Sha256::hash(b"test3"),
);
let (_, notarization_for_bft) = build_notarization(&schemes, &proposal_bft, quorum);
mailbox
.recovered(Certificate::Notarization(notarization_for_bft))
.await;
let msg = resolver_receiver
.next()
.await
.expect("failed to receive resolver message");
match msg {
MailboxMessage::Certificate(Certificate::Notarization(notarization)) => {
assert_eq!(notarization.view(), problematic_view);
}
_ => panic!("unexpected resolver message"),
}
let proposal_lf = Proposal::new(
Round::new(Epoch::new(333), View::new(100)),
View::new(99),
Sha256::hash(b"test4"),
);
let (_, finalization) = build_finalization(&schemes, &proposal_lf, quorum);
mailbox
.recovered(Certificate::Finalization(finalization))
.await;
loop {
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(101));
assert_eq!(finalized, View::new(100));
active.send(true).unwrap();
break;
}
_ => {
continue;
}
}
}
let msg = resolver_receiver
.next()
.await
.expect("failed to receive resolver message");
match msg {
MailboxMessage::Certificate(Certificate::Finalization(finalization)) => {
assert_eq!(finalization.view(), View::new(100));
}
_ => panic!("unexpected resolver message"),
}
});
}
#[test_traced]
fn test_append_old_interesting_view() {
append_old_interesting_view::<_, _, Random>(bls12381_threshold::fixture::<MinPk, _>);
append_old_interesting_view::<_, _, Random>(bls12381_threshold::fixture::<MinSig, _>);
append_old_interesting_view::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinPk, _>);
append_old_interesting_view::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinSig, _>);
append_old_interesting_view::<_, _, RoundRobin>(ed25519::fixture);
append_old_interesting_view::<_, _, RoundRobin>(secp256r1::fixture);
}
fn finalization_without_notarization_certificate<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"finalization_without_notarization".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(10));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let elector = L::default();
let (mut mailbox, mut batcher_receiver, mut resolver_receiver, _, reporter) =
setup_voter(
&mut context,
&oracle,
&participants,
&schemes,
elector,
Duration::from_millis(500),
Duration::from_secs(1000),
Duration::from_secs(1000),
mocks::application::Certifier::Sometimes,
)
.await;
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(1));
assert_eq!(finalized, View::new(0));
active.send(true).unwrap();
}
_ => panic!("unexpected batcher message"),
}
let view = View::new(2);
let proposal = Proposal::new(
Round::new(Epoch::new(333), view),
view.previous().unwrap(),
Sha256::hash(b"finalize_without_notarization"),
);
let (_, expected_finalization) = build_finalization(&schemes, &proposal, quorum);
mailbox
.recovered(Certificate::Finalization(expected_finalization.clone()))
.await;
let mut finalized_view = None;
while let Some(message) = resolver_receiver.next().await {
match message {
MailboxMessage::Certificate(Certificate::Finalization(finalization)) => {
finalized_view = Some(finalization.view());
break;
}
_ => continue,
}
}
assert_eq!(finalized_view, Some(view));
loop {
{
let finalizations = reporter.finalizations.lock().unwrap();
if matches!(
finalizations.get(&view),
Some(finalization) if finalization == &expected_finalization
) {
break;
}
}
context.sleep(Duration::from_millis(10)).await;
}
let notarizations = reporter.notarizations.lock().unwrap();
assert!(notarizations.is_empty());
});
}
#[test_traced]
fn test_finalization_without_notarization_certificate() {
finalization_without_notarization_certificate::<_, _, Random>(
bls12381_threshold::fixture::<MinPk, _>,
);
finalization_without_notarization_certificate::<_, _, Random>(
bls12381_threshold::fixture::<MinSig, _>,
);
finalization_without_notarization_certificate::<_, _, RoundRobin>(
bls12381_multisig::fixture::<MinPk, _>,
);
finalization_without_notarization_certificate::<_, _, RoundRobin>(
bls12381_multisig::fixture::<MinSig, _>,
);
finalization_without_notarization_certificate::<_, _, RoundRobin>(ed25519::fixture);
finalization_without_notarization_certificate::<_, _, RoundRobin>(secp256r1::fixture);
}
fn certificate_conflicts_proposal<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"certificate_conflicts_proposal_test".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(10));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let elector = L::default();
let (mut mailbox, mut batcher_receiver, mut resolver_receiver, _, reporter) =
setup_voter(
&mut context,
&oracle,
&participants,
&schemes,
elector,
Duration::from_millis(500),
Duration::from_secs(1000),
Duration::from_secs(1000),
mocks::application::Certifier::Sometimes,
)
.await;
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(1));
assert_eq!(finalized, View::new(0));
active.send(true).unwrap();
}
_ => panic!("unexpected batcher message"),
}
let view = View::new(2);
let proposal_a = Proposal::new(
Round::new(Epoch::new(333), view),
view.previous().unwrap(),
Sha256::hash(b"proposal_a"),
);
mailbox.proposal(proposal_a.clone()).await;
context.sleep(Duration::from_millis(10)).await;
let proposal_b = Proposal::new(
Round::new(Epoch::new(333), view),
view.previous().unwrap(),
Sha256::hash(b"proposal_b"),
);
let (_, notarization_b) = build_notarization(&schemes, &proposal_b, quorum);
mailbox
.recovered(Certificate::Notarization(notarization_b.clone()))
.await;
let msg = resolver_receiver
.next()
.await
.expect("failed to receive resolver message");
match msg {
MailboxMessage::Certificate(Certificate::Notarization(notarization)) => {
assert_eq!(notarization.proposal, proposal_b);
assert_eq!(notarization, notarization_b);
}
_ => panic!("unexpected resolver message"),
}
loop {
{
let notarizations = reporter.notarizations.lock().unwrap();
if matches!(
notarizations.get(&view),
Some(notarization) if notarization == ¬arization_b
) {
break;
}
}
context.sleep(Duration::from_millis(10)).await;
}
context.sleep(Duration::from_millis(100)).await;
loop {
let Some(Some(message)) = batcher_receiver.next().now_or_never() else {
break;
};
match message {
batcher::Message::Constructed(Vote::Finalize(_)) => {
panic!("finalize vote should not be broadcast");
}
batcher::Message::Update { active, .. } => {
active.send(true).unwrap();
}
_ => continue,
}
}
});
}
#[test_traced]
fn test_certificate_conflicts_proposal() {
certificate_conflicts_proposal::<_, _, Random>(bls12381_threshold::fixture::<MinPk, _>);
certificate_conflicts_proposal::<_, _, Random>(bls12381_threshold::fixture::<MinSig, _>);
certificate_conflicts_proposal::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinPk, _>);
certificate_conflicts_proposal::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinSig, _>);
certificate_conflicts_proposal::<_, _, RoundRobin>(ed25519::fixture);
certificate_conflicts_proposal::<_, _, RoundRobin>(secp256r1::fixture);
}
fn proposal_conflicts_certificate<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"proposal_conflicts_certificate_test".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(10));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let elector = L::default();
let (mut mailbox, mut batcher_receiver, mut resolver_receiver, _, reporter) =
setup_voter(
&mut context,
&oracle,
&participants,
&schemes,
elector,
Duration::from_millis(500),
Duration::from_secs(1000),
Duration::from_secs(1000),
mocks::application::Certifier::Sometimes,
)
.await;
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update { active, .. } => {
active.send(true).unwrap();
}
_ => panic!("unexpected batcher message"),
}
let view = View::new(2);
let proposal_a = Proposal::new(
Round::new(Epoch::new(333), view),
view.previous().unwrap(),
Sha256::hash(b"proposal_a"),
);
let proposal_b = Proposal::new(
Round::new(Epoch::new(333), view),
view.previous().unwrap(),
Sha256::hash(b"proposal_b"),
);
let (_, notarization_a) = build_notarization(&schemes, &proposal_a, quorum);
mailbox
.recovered(Certificate::Notarization(notarization_a.clone()))
.await;
let msg = resolver_receiver.next().await.unwrap();
match msg {
MailboxMessage::Certificate(Certificate::Notarization(notarization)) => {
assert_eq!(notarization.proposal, proposal_a);
}
_ => panic!("unexpected resolver message"),
}
loop {
{
let notarizations = reporter.notarizations.lock().unwrap();
if matches!(
notarizations.get(&view),
Some(notarization) if notarization == ¬arization_a
) {
break;
}
}
context.sleep(Duration::from_millis(10)).await;
}
loop {
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Constructed(Vote::Finalize(finalize)) => {
assert_eq!(
finalize.proposal, proposal_a,
"finalize should be for certificate's proposal A"
);
break;
}
batcher::Message::Update { active, .. } => {
active.send(true).unwrap();
}
_ => context.sleep(Duration::from_millis(10)).await,
}
}
mailbox.proposal(proposal_b.clone()).await;
context.sleep(Duration::from_millis(100)).await;
});
}
#[test_traced]
fn test_proposal_conflicts_certificate() {
proposal_conflicts_certificate::<_, _, Random>(bls12381_threshold::fixture::<MinPk, _>);
proposal_conflicts_certificate::<_, _, Random>(bls12381_threshold::fixture::<MinSig, _>);
proposal_conflicts_certificate::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinPk, _>);
proposal_conflicts_certificate::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinSig, _>);
proposal_conflicts_certificate::<_, _, RoundRobin>(ed25519::fixture);
proposal_conflicts_certificate::<_, _, RoundRobin>(secp256r1::fixture);
}
fn certificate_verifies_proposal<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"certificate_conflicts_proposal_test".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(10));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let elector = L::default();
let reporter_cfg = mocks::reporter::Config {
participants: participants.clone().try_into().unwrap(),
scheme: schemes[0].clone(),
elector: elector.clone(),
};
let reporter =
mocks::reporter::Reporter::new(context.with_label("reporter"), reporter_cfg);
let relay = Arc::new(mocks::relay::Relay::new());
let application_cfg = mocks::application::Config {
hasher: Sha256::default(),
relay: relay.clone(),
me: participants[0].clone(),
propose_latency: (1.0, 0.0),
verify_latency: (100_000.0, 0.0), certify_latency: (1.0, 0.0),
should_certify: mocks::application::Certifier::Sometimes,
};
let (actor, application) =
mocks::application::Application::new(context.with_label("app"), application_cfg);
actor.start();
let voter_cfg = Config {
scheme: schemes[0].clone(),
elector,
blocker: oracle.control(participants[0].clone()),
automaton: application.clone(),
relay: application.clone(),
reporter: reporter.clone(),
partition: "voter_certificate_verifies_proposal_test".to_string(),
epoch: Epoch::new(333),
mailbox_size: 128,
leader_timeout: Duration::from_millis(500),
notarization_timeout: Duration::from_secs(1000),
nullify_retry: Duration::from_secs(1000),
activity_timeout: ViewDelta::new(10),
replay_buffer: NZUsize!(1024 * 1024),
write_buffer: NZUsize!(1024 * 1024),
buffer_pool: PoolRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let (voter, mut mailbox) = Actor::new(context.clone(), voter_cfg);
let (resolver_sender, mut resolver_receiver) = mpsc::channel(8);
let resolver_mailbox = resolver::Mailbox::new(resolver_sender);
let (batcher_sender, mut batcher_receiver) = mpsc::channel(8);
let batcher_mailbox = batcher::Mailbox::new(batcher_sender);
let me = participants[0].clone();
let (vote_sender, _) = oracle
.control(me.clone())
.register(0, TEST_QUOTA)
.await
.unwrap();
let (certificate_sender, _) = oracle
.control(me.clone())
.register(1, TEST_QUOTA)
.await
.unwrap();
voter.start(
batcher_mailbox,
resolver_mailbox,
vote_sender,
certificate_sender,
);
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update { active, .. } => {
active.send(true).unwrap();
}
_ => panic!("unexpected batcher message"),
}
let view = View::new(2);
let proposal = Proposal::new(
Round::new(Epoch::new(333), view),
view.previous().unwrap(),
Sha256::hash(b"same_proposal"),
);
mailbox.proposal(proposal.clone()).await;
context.sleep(Duration::from_millis(10)).await;
let (_, notarization) = build_notarization(&schemes, &proposal, quorum);
mailbox
.recovered(Certificate::Notarization(notarization.clone()))
.await;
let msg = resolver_receiver.next().await.unwrap();
match msg {
MailboxMessage::Certificate(Certificate::Notarization(n)) => {
assert_eq!(n.proposal, proposal);
}
_ => panic!("unexpected resolver message"),
}
loop {
{
let notarizations = reporter.notarizations.lock().unwrap();
if matches!(
notarizations.get(&view),
Some(n) if n == ¬arization
) {
break;
}
}
context.sleep(Duration::from_millis(10)).await;
}
loop {
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Constructed(Vote::Finalize(finalize)) => {
assert_eq!(finalize.proposal, proposal);
break;
}
batcher::Message::Update { active, .. } => {
active.send(true).unwrap();
}
_ => context.sleep(Duration::from_millis(10)).await,
}
}
});
}
#[test_traced]
fn test_certificate_verifies_proposal() {
certificate_verifies_proposal::<_, _, Random>(bls12381_threshold::fixture::<MinPk, _>);
certificate_verifies_proposal::<_, _, Random>(bls12381_threshold::fixture::<MinSig, _>);
certificate_verifies_proposal::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinPk, _>);
certificate_verifies_proposal::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinSig, _>);
certificate_verifies_proposal::<_, _, RoundRobin>(ed25519::fixture);
certificate_verifies_proposal::<_, _, RoundRobin>(secp256r1::fixture);
}
fn drop_our_proposal_on_conflict<S, F>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"drop_our_proposal_on_conflict_test".to_vec();
let epoch = Epoch::new(333);
let executor = deterministic::Runner::timed(Duration::from_secs(10));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: false,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
verifier: _,
..
} = fixture(&mut context, &namespace, n);
let view2_round = Round::new(epoch, View::new(2));
let elector_config = RoundRobin::<Sha256>::default();
let temp_elector: RoundRobinElector<S> =
elector_config.clone().build(schemes[0].participants());
let leader_idx = temp_elector.elect(view2_round, None);
let leader = participants[usize::from(leader_idx)].clone();
let leader_scheme = schemes[usize::from(leader_idx)].clone();
let relay = Arc::new(mocks::relay::Relay::new());
let application_cfg = mocks::application::Config {
hasher: Sha256::default(),
relay: relay.clone(),
me: leader.clone(),
propose_latency: (50.0, 10.0),
verify_latency: (1.0, 0.0),
certify_latency: (1.0, 0.0),
should_certify: mocks::application::Certifier::Always,
};
let (actor, application) =
mocks::application::Application::new(context.with_label("app"), application_cfg);
actor.start();
let reporter_cfg = mocks::reporter::Config {
participants: participants.clone().try_into().unwrap(),
scheme: leader_scheme.clone(),
elector: elector_config.clone(),
};
let reporter =
mocks::reporter::Reporter::new(context.with_label("reporter"), reporter_cfg);
let voter_cfg = Config {
scheme: leader_scheme.clone(),
elector: elector_config,
blocker: oracle.control(leader.clone()),
automaton: application.clone(),
relay: application.clone(),
reporter: reporter.clone(),
partition: "voter_leader".to_string(),
epoch,
mailbox_size: 128,
leader_timeout: Duration::from_millis(500),
notarization_timeout: Duration::from_secs(1000),
nullify_retry: Duration::from_secs(1000),
activity_timeout: ViewDelta::new(10),
replay_buffer: NZUsize!(1024 * 1024),
write_buffer: NZUsize!(1024 * 1024),
buffer_pool: PoolRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let (voter, mut mailbox) = Actor::new(context.clone(), voter_cfg);
let (resolver_sender, _resolver_receiver) = mpsc::channel(8);
let resolver_mailbox = resolver::Mailbox::new(resolver_sender);
let (batcher_sender, mut batcher_receiver) = mpsc::channel(8);
let batcher_mailbox = batcher::Mailbox::new(batcher_sender);
let (vote_sender, _) = oracle
.control(leader.clone())
.register(0, TEST_QUOTA)
.await
.unwrap();
let (certificate_sender, _certificate_receiver) = oracle
.control(leader.clone())
.register(1, TEST_QUOTA)
.await
.unwrap();
voter.start(
batcher_mailbox,
resolver_mailbox,
vote_sender,
certificate_sender,
);
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(1));
assert_eq!(finalized, View::new(0));
active.send(true).unwrap();
}
_ => panic!("unexpected batcher message"),
}
let view1_round = Round::new(epoch, View::new(1));
let view1_proposal =
Proposal::new(view1_round, View::new(0), Sha256::hash(b"view1_payload"));
let (_, finalization) = build_finalization(&schemes, &view1_proposal, quorum);
mailbox
.recovered(Certificate::Finalization(finalization))
.await;
loop {
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(2));
assert_eq!(finalized, View::new(1));
active.send(true).unwrap();
break;
}
_ => {
continue;
}
}
}
context.sleep(Duration::from_millis(5)).await;
let conflicting_proposal =
Proposal::new(view2_round, View::new(1), Sha256::hash(b"leader_proposal"));
mailbox.proposal(conflicting_proposal.clone()).await;
while let Ok(Some(message)) = batcher_receiver.try_next() {
match message {
batcher::Message::Constructed(Vote::Notarize(notarize)) => {
assert!(notarize.proposal == conflicting_proposal);
}
_ => panic!("unexpected batcher message"),
}
}
context.sleep(Duration::from_millis(100)).await;
let (_, conflicting_notarization) =
build_notarization(&schemes, &conflicting_proposal, quorum);
mailbox
.recovered(Certificate::Notarization(conflicting_notarization.clone()))
.await;
loop {
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Constructed(Vote::Finalize(f)) => {
assert_eq!(f.proposal, conflicting_proposal);
break;
}
batcher::Message::Update { active, .. } => {
active.send(true).unwrap();
}
_ => context.sleep(Duration::from_millis(10)).await,
}
}
});
}
#[test_traced]
fn test_drop_our_proposal_on_conflict() {
drop_our_proposal_on_conflict(bls12381_threshold::fixture::<MinPk, _>);
drop_our_proposal_on_conflict(bls12381_threshold::fixture::<MinSig, _>);
drop_our_proposal_on_conflict(bls12381_multisig::fixture::<MinPk, _>);
drop_our_proposal_on_conflict(bls12381_multisig::fixture::<MinSig, _>);
drop_our_proposal_on_conflict(ed25519::fixture);
drop_our_proposal_on_conflict(secp256r1::fixture);
}
fn populate_resolver_on_restart<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"populate_resolver_on_restart_test".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(10));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let elector = L::default();
let reporter_cfg = mocks::reporter::Config {
participants: participants.clone().try_into().unwrap(),
scheme: schemes[0].clone(),
elector: elector.clone(),
};
let reporter =
mocks::reporter::Reporter::new(context.with_label("reporter"), reporter_cfg);
let relay = Arc::new(mocks::relay::Relay::new());
let application_cfg = mocks::application::Config {
hasher: Sha256::default(),
relay: relay.clone(),
me: participants[0].clone(),
propose_latency: (1.0, 0.0),
verify_latency: (1.0, 0.0),
certify_latency: (1.0, 0.0),
should_certify: mocks::application::Certifier::Sometimes,
};
let (actor, application) =
mocks::application::Application::new(context.with_label("app"), application_cfg);
actor.start();
let voter_cfg = Config {
scheme: schemes[0].clone(),
elector: elector.clone(),
blocker: oracle.control(participants[0].clone()),
automaton: application.clone(),
relay: application.clone(),
reporter: reporter.clone(),
partition: "voter_populate_resolver_on_restart_test".to_string(),
epoch: Epoch::new(333),
mailbox_size: 128,
leader_timeout: Duration::from_millis(500),
notarization_timeout: Duration::from_secs(1000),
nullify_retry: Duration::from_secs(1000),
activity_timeout: ViewDelta::new(10),
replay_buffer: NZUsize!(1024 * 1024),
write_buffer: NZUsize!(1024 * 1024),
buffer_pool: PoolRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let (voter, mut mailbox) = Actor::new(context.clone(), voter_cfg);
let (resolver_sender, mut resolver_receiver) = mpsc::channel(8);
let resolver_mailbox = resolver::Mailbox::new(resolver_sender);
let (batcher_sender, mut batcher_receiver) = mpsc::channel(8);
let batcher_mailbox = batcher::Mailbox::new(batcher_sender);
let me = participants[0].clone();
let (vote_sender, _vote_receiver) = oracle
.control(me.clone())
.register(0, TEST_QUOTA)
.await
.unwrap();
let (certificate_sender, _certificate_receiver) = oracle
.control(me.clone())
.register(1, TEST_QUOTA)
.await
.unwrap();
let handle = voter.start(
batcher_mailbox,
resolver_mailbox,
vote_sender,
certificate_sender,
);
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(1));
assert_eq!(finalized, View::new(0));
active.send(true).unwrap();
}
_ => panic!("unexpected batcher message"),
}
let view = View::new(2);
let proposal = Proposal::new(
Round::new(Epoch::new(333), view),
view.previous().unwrap(),
Sha256::hash(b"finalize_without_notarization"),
);
let (_, expected_finalization) = build_finalization(&schemes, &proposal, quorum);
mailbox
.recovered(Certificate::Finalization(expected_finalization.clone()))
.await;
let finalization = resolver_receiver.next().await.unwrap();
match finalization {
MailboxMessage::Certificate(Certificate::Finalization(finalization)) => {
assert_eq!(finalization, expected_finalization);
}
_ => panic!("unexpected resolver message"),
}
handle.abort();
let voter_cfg = Config {
scheme: schemes[0].clone(),
elector: elector.clone(),
blocker: oracle.control(participants[0].clone()),
automaton: application.clone(),
relay: application.clone(),
reporter: reporter.clone(),
partition: "voter_populate_resolver_on_restart_test".to_string(),
epoch: Epoch::new(333),
mailbox_size: 128,
leader_timeout: Duration::from_millis(500),
notarization_timeout: Duration::from_secs(1000),
nullify_retry: Duration::from_secs(1000),
activity_timeout: ViewDelta::new(10),
replay_buffer: NZUsize!(1024 * 1024),
write_buffer: NZUsize!(1024 * 1024),
buffer_pool: PoolRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let (voter, _mailbox) = Actor::new(context.clone(), voter_cfg);
let (resolver_sender, mut resolver_receiver) = mpsc::channel(8);
let resolver_mailbox = resolver::Mailbox::new(resolver_sender);
let (batcher_sender, mut batcher_receiver) = mpsc::channel(8);
let batcher_mailbox = batcher::Mailbox::new(batcher_sender);
let me = participants[0].clone();
let (vote_sender, _vote_receiver) = oracle
.control(me.clone())
.register(2, TEST_QUOTA)
.await
.unwrap();
let (certificate_sender, _certificate_receiver) = oracle
.control(me.clone())
.register(3, TEST_QUOTA)
.await
.unwrap();
voter.start(
batcher_mailbox,
resolver_mailbox,
vote_sender,
certificate_sender,
);
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(3));
assert_eq!(finalized, View::new(2));
active.send(true).unwrap();
}
_ => panic!("unexpected batcher message"),
}
let finalization = resolver_receiver.next().await.unwrap();
match finalization {
MailboxMessage::Certificate(Certificate::Finalization(finalization)) => {
assert_eq!(finalization, expected_finalization);
}
_ => panic!("unexpected resolver message"),
}
});
}
#[test_traced]
fn test_populate_resolver_on_restart() {
populate_resolver_on_restart::<_, _, Random>(bls12381_threshold::fixture::<MinPk, _>);
populate_resolver_on_restart::<_, _, Random>(bls12381_threshold::fixture::<MinSig, _>);
populate_resolver_on_restart::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinPk, _>);
populate_resolver_on_restart::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinSig, _>);
populate_resolver_on_restart::<_, _, RoundRobin>(ed25519::fixture);
populate_resolver_on_restart::<_, _, RoundRobin>(secp256r1::fixture);
}
fn finalization_from_resolver<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"finalization_from_resolver".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(10));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let elector = L::default();
let (mut mailbox, mut batcher_receiver, _, _, reporter) = setup_voter(
&mut context,
&oracle,
&participants,
&schemes,
elector,
Duration::from_millis(500),
Duration::from_secs(1000),
Duration::from_secs(1000),
mocks::application::Certifier::Sometimes,
)
.await;
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(1));
assert_eq!(finalized, View::new(0));
active.send(true).unwrap();
}
_ => panic!("unexpected batcher message"),
}
let view = View::new(2);
let proposal = Proposal::new(
Round::new(Epoch::new(333), view),
view.previous().unwrap(),
Sha256::hash(b"finalization_from_resolver"),
);
let (_, finalization) = build_finalization(&schemes, &proposal, quorum);
mailbox
.recovered(Certificate::Finalization(finalization.clone()))
.await;
loop {
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update { finalized, .. } if finalized == view => break,
_ => continue,
}
}
let finalizations = reporter.finalizations.lock().unwrap();
let recorded = finalizations
.get(&view)
.expect("finalization should be recorded");
assert_eq!(recorded, &finalization);
});
}
#[test_traced]
fn test_finalization_from_resolver() {
finalization_from_resolver::<_, _, Random>(bls12381_threshold::fixture::<MinPk, _>);
finalization_from_resolver::<_, _, Random>(bls12381_threshold::fixture::<MinSig, _>);
finalization_from_resolver::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinPk, _>);
finalization_from_resolver::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinSig, _>);
finalization_from_resolver::<_, _, RoundRobin>(ed25519::fixture);
finalization_from_resolver::<_, _, RoundRobin>(secp256r1::fixture);
}
fn no_resolver_boomerang<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"no_resolver_boomerang".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(10));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let elector = L::default();
let (mut mailbox, mut batcher_receiver, mut resolver_receiver, _, reporter) =
setup_voter(
&mut context,
&oracle,
&participants,
&schemes,
elector,
Duration::from_millis(500),
Duration::from_secs(1000),
Duration::from_secs(1000),
mocks::application::Certifier::Sometimes,
)
.await;
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
current,
leader: _,
finalized,
active,
} => {
assert_eq!(current, View::new(1));
assert_eq!(finalized, View::new(0));
active.send(true).unwrap();
}
_ => panic!("unexpected batcher message"),
}
let view = View::new(2);
let proposal = Proposal::new(
Round::new(Epoch::new(333), view),
view.previous().unwrap(),
Sha256::hash(b"no_resolver_boomerang"),
);
let (_, finalization) = build_finalization(&schemes, &proposal, quorum);
mailbox
.resolved(Certificate::Finalization(finalization.clone()))
.await;
loop {
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update {
finalized, active, ..
} if finalized == view => {
active.send(true).unwrap();
break;
}
batcher::Message::Update { active, .. } => {
active.send(true).unwrap();
}
_ => continue,
}
}
let finalizations = reporter.finalizations.lock().unwrap();
let recorded = finalizations
.get(&view)
.expect("finalization should be recorded");
assert_eq!(recorded, &finalization);
drop(finalizations);
assert!(
resolver_receiver.next().now_or_never().is_none(),
"resolver should not receive the certificate back"
);
});
}
#[test_traced]
fn test_no_resolver_boomerang() {
no_resolver_boomerang::<_, _, Random>(bls12381_threshold::fixture::<MinPk, _>);
no_resolver_boomerang::<_, _, Random>(bls12381_threshold::fixture::<MinSig, _>);
no_resolver_boomerang::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinPk, _>);
no_resolver_boomerang::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinSig, _>);
no_resolver_boomerang::<_, _, RoundRobin>(ed25519::fixture);
no_resolver_boomerang::<_, _, RoundRobin>(secp256r1::fixture);
}
fn verification_failure_emits_nullify_immediately<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"consensus".to_vec();
let activity_timeout = ViewDelta::new(10);
let executor = deterministic::Runner::timed(Duration::from_secs(5));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let signing = schemes[0].clone();
let me = participants[0].clone();
let elector = L::default();
let reporter_cfg = mocks::reporter::Config {
participants: participants.clone().try_into().unwrap(),
scheme: signing.clone(),
elector: elector.clone(),
};
let reporter =
mocks::reporter::Reporter::new(context.with_label("reporter"), reporter_cfg);
let relay = Arc::new(mocks::relay::Relay::new());
let application_cfg = mocks::application::Config {
hasher: Sha256::default(),
relay: relay.clone(),
me: me.clone(),
propose_latency: (1.0, 0.0),
verify_latency: (10.0, 0.0), certify_latency: (1.0, 0.0),
should_certify: mocks::application::Certifier::Always,
};
let (mut actor, application) =
mocks::application::Application::new(context.with_label("app"), application_cfg);
actor.set_fail_verification(true);
actor.start();
let voter_cfg = Config {
scheme: signing.clone(),
elector,
blocker: oracle.control(me.clone()),
automaton: application.clone(),
relay: application.clone(),
reporter: reporter.clone(),
partition: format!("voter_verify_fail_test_{me}"),
epoch: Epoch::new(333),
mailbox_size: 128,
leader_timeout: Duration::from_secs(10),
notarization_timeout: Duration::from_secs(10),
nullify_retry: Duration::from_secs(10),
activity_timeout,
replay_buffer: NZUsize!(10240),
write_buffer: NZUsize!(10240),
buffer_pool: PoolRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let (voter, mut mailbox) = Actor::new(context.clone(), voter_cfg);
let (resolver_sender, _resolver_receiver) = mpsc::channel(2);
let resolver_mailbox = resolver::Mailbox::new(resolver_sender);
let (batcher_sender, mut batcher_receiver) = mpsc::channel(16);
let batcher_mailbox = batcher::Mailbox::new(batcher_sender);
let (vote_sender, _vote_receiver) =
oracle.control(me.clone()).register(0, TEST_QUOTA).await.unwrap();
let (certificate_sender, _certificate_receiver) =
oracle.control(me.clone()).register(1, TEST_QUOTA).await.unwrap();
voter.start(
batcher_mailbox,
resolver_mailbox,
vote_sender,
certificate_sender,
);
let message = batcher_receiver.next().await.unwrap();
match message {
batcher::Message::Update { active, .. } => active.send(true).unwrap(),
_ => panic!("expected Update message"),
}
let mut current_view = View::new(1);
let mut prev_proposal = Proposal::new(
Round::new(Epoch::new(333), current_view),
View::zero(),
Sha256::hash(b"v0"),
);
let (target_view, leader) = loop {
let (_, finalization) =
build_finalization(&schemes, &prev_proposal, quorum);
mailbox
.resolved(Certificate::Finalization(finalization))
.await;
let (new_view, leader) = loop {
match batcher_receiver.next().await.unwrap() {
batcher::Message::Update {
current,
leader,
active,
..
} => {
active.send(true).unwrap();
if current > current_view {
break (current, leader);
}
}
batcher::Message::Constructed(_) => {}
}
};
current_view = new_view;
if leader != Participant::new(0) {
break (current_view, participants[usize::from(leader)].clone());
}
prev_proposal = Proposal::new(
Round::new(Epoch::new(333), current_view),
current_view.previous().unwrap(),
Sha256::hash(current_view.get().to_be_bytes().as_slice()),
);
};
let proposal = Proposal::new(
Round::new(Epoch::new(333), target_view),
target_view.previous().unwrap(),
Sha256::hash(b"test_proposal"),
);
let parent_payload = Sha256::hash(
target_view
.previous()
.unwrap()
.get()
.to_be_bytes()
.as_slice(),
);
let contents = (proposal.round, parent_payload, 0u64).encode();
relay
.broadcast(&leader, (proposal.payload, contents))
.await;
mailbox.proposal(proposal).await;
loop {
select! {
msg = batcher_receiver.next() => {
match msg.unwrap() {
batcher::Message::Constructed(Vote::Nullify(nullify)) if nullify.view() == target_view => {
break;
}
batcher::Message::Update { active, .. } => active.send(true).unwrap(),
_ => {}
}
},
_ = context.sleep(Duration::from_secs(1)) => {
panic!("expected nullify for view {} within 1s (timeouts are 10s)", target_view);
},
}
}
});
}
#[test_traced]
fn test_verification_failure_emits_nullify_immediately() {
verification_failure_emits_nullify_immediately::<_, _, Random>(
bls12381_threshold::fixture::<MinPk, _>,
);
verification_failure_emits_nullify_immediately::<_, _, Random>(
bls12381_threshold::fixture::<MinSig, _>,
);
verification_failure_emits_nullify_immediately::<_, _, RoundRobin>(
bls12381_multisig::fixture::<MinPk, _>,
);
verification_failure_emits_nullify_immediately::<_, _, RoundRobin>(
bls12381_multisig::fixture::<MinSig, _>,
);
verification_failure_emits_nullify_immediately::<_, _, RoundRobin>(ed25519::fixture);
verification_failure_emits_nullify_immediately::<_, _, RoundRobin>(secp256r1::fixture);
}
fn no_recertification_after_replay<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"no_recertification_after_replay".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(10));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let certify_calls: Arc<Mutex<Vec<Sha256Digest>>> = Arc::new(Mutex::new(Vec::new()));
let tracker = certify_calls.clone();
let elector = L::default();
let reporter_cfg = mocks::reporter::Config {
participants: participants.clone().try_into().unwrap(),
scheme: schemes[0].clone(),
elector: elector.clone(),
};
let reporter =
mocks::reporter::Reporter::new(context.with_label("reporter"), reporter_cfg);
let relay = Arc::new(mocks::relay::Relay::new());
let me = participants[0].clone();
let app_cfg = mocks::application::Config {
hasher: Sha256::default(),
relay: relay.clone(),
me: me.clone(),
propose_latency: (1.0, 0.0),
verify_latency: (1.0, 0.0),
certify_latency: (1.0, 0.0),
should_certify: mocks::application::Certifier::Custom(Box::new(move |d| {
tracker.lock().unwrap().push(d);
true
})),
};
let (actor, application) =
mocks::application::Application::new(context.with_label("app"), app_cfg);
actor.start();
let voter_cfg = Config {
scheme: schemes[0].clone(),
elector: elector.clone(),
blocker: oracle.control(me.clone()),
automaton: application.clone(),
relay: application.clone(),
reporter: reporter.clone(),
partition: "no_recertification_after_replay".to_string(),
epoch: Epoch::new(333),
mailbox_size: 128,
leader_timeout: Duration::from_millis(500),
notarization_timeout: Duration::from_secs(1000),
nullify_retry: Duration::from_secs(1000),
activity_timeout: ViewDelta::new(10),
replay_buffer: NZUsize!(1024 * 1024),
write_buffer: NZUsize!(1024 * 1024),
buffer_pool: PoolRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let (voter, mut mailbox) = Actor::new(context.clone(), voter_cfg);
let (resolver_sender, _) = mpsc::channel(8);
let (batcher_sender, mut batcher_receiver) = mpsc::channel(8);
let (vote_sender, _) = oracle
.control(me.clone())
.register(0, TEST_QUOTA)
.await
.unwrap();
let (cert_sender, _) = oracle
.control(me.clone())
.register(1, TEST_QUOTA)
.await
.unwrap();
let handle = voter.start(
batcher::Mailbox::new(batcher_sender),
resolver::Mailbox::new(resolver_sender),
vote_sender,
cert_sender,
);
if let batcher::Message::Update { active, .. } = batcher_receiver.next().await.unwrap()
{
active.send(true).unwrap();
}
let view2 = View::new(2);
let proposal2 = Proposal::new(
Round::new(Epoch::new(333), view2),
View::new(1),
Sha256::hash(b"finalized_payload"),
);
let (_, finalization) = build_finalization(&schemes, &proposal2, quorum);
mailbox
.recovered(Certificate::Finalization(finalization))
.await;
loop {
if let batcher::Message::Update {
finalized, active, ..
} = batcher_receiver.next().await.unwrap()
{
active.send(true).unwrap();
if finalized >= view2 {
break;
}
}
}
assert_eq!(
certify_calls.lock().unwrap().len(),
0,
"certify should not be called for finalization"
);
let view3 = View::new(3);
let digest3 = Sha256::hash(b"payload_for_certification");
let proposal3 = Proposal::new(Round::new(Epoch::new(333), view3), view2, digest3);
let contents = (proposal3.round, proposal2.payload, 0u64).encode();
relay.broadcast(&me, (digest3, contents)).await;
mailbox.proposal(proposal3.clone()).await;
let (_, notarization) = build_notarization(&schemes, &proposal3, quorum);
mailbox
.recovered(Certificate::Notarization(notarization))
.await;
loop {
if let batcher::Message::Update {
current, active, ..
} = batcher_receiver.next().await.unwrap()
{
active.send(true).unwrap();
if current > view3 {
break;
}
}
}
assert_eq!(
certify_calls.lock().unwrap().len(),
1,
"certify should be called once for notarization"
);
handle.abort();
let tracker = certify_calls.clone();
let app_cfg = mocks::application::Config {
hasher: Sha256::default(),
relay: relay.clone(),
me: me.clone(),
propose_latency: (1.0, 0.0),
verify_latency: (1.0, 0.0),
certify_latency: (1.0, 0.0),
should_certify: mocks::application::Certifier::Custom(Box::new(move |d| {
tracker.lock().unwrap().push(d);
true
})),
};
let (actor, application) =
mocks::application::Application::new(context.with_label("app2"), app_cfg);
actor.start();
let voter_cfg = Config {
scheme: schemes[0].clone(),
elector: elector.clone(),
blocker: oracle.control(me.clone()),
automaton: application.clone(),
relay: application.clone(),
reporter: reporter.clone(),
partition: "no_recertification_after_replay".to_string(),
epoch: Epoch::new(333),
mailbox_size: 128,
leader_timeout: Duration::from_millis(500),
notarization_timeout: Duration::from_secs(1000),
nullify_retry: Duration::from_secs(1000),
activity_timeout: ViewDelta::new(10),
replay_buffer: NZUsize!(1024 * 1024),
write_buffer: NZUsize!(1024 * 1024),
buffer_pool: PoolRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let (voter, _) = Actor::new(context.clone(), voter_cfg);
let (resolver_sender, _) = mpsc::channel(8);
let (batcher_sender, mut batcher_receiver) = mpsc::channel(8);
let (vote_sender, _) = oracle
.control(me.clone())
.register(2, TEST_QUOTA)
.await
.unwrap();
let (cert_sender, _) = oracle
.control(me.clone())
.register(3, TEST_QUOTA)
.await
.unwrap();
voter.start(
batcher::Mailbox::new(batcher_sender),
resolver::Mailbox::new(resolver_sender),
vote_sender,
cert_sender,
);
if let batcher::Message::Update { active, .. } = batcher_receiver.next().await.unwrap()
{
active.send(true).unwrap();
}
context.sleep(Duration::from_millis(100)).await;
assert_eq!(
certify_calls.lock().unwrap().len(),
1,
"certify should not be called again after replay"
);
});
}
#[test_traced]
fn test_no_recertification_after_replay() {
no_recertification_after_replay::<_, _, Random>(bls12381_threshold::fixture::<MinPk, _>);
no_recertification_after_replay::<_, _, Random>(bls12381_threshold::fixture::<MinSig, _>);
no_recertification_after_replay::<_, _, RoundRobin>(bls12381_multisig::fixture::<MinPk, _>);
no_recertification_after_replay::<_, _, RoundRobin>(
bls12381_multisig::fixture::<MinSig, _>,
);
no_recertification_after_replay::<_, _, RoundRobin>(ed25519::fixture);
no_recertification_after_replay::<_, _, RoundRobin>(secp256r1::fixture);
}
fn certification_cancelled_on_finalization<S, F, L>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
L: ElectorConfig<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"consensus".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(30));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let me = participants[0].clone();
let elector = L::default();
let reporter_config = mocks::reporter::Config {
participants: participants.clone().try_into().unwrap(),
scheme: schemes[0].clone(),
elector: elector.clone(),
};
let reporter =
mocks::reporter::Reporter::new(context.with_label("reporter"), reporter_config);
let relay = Arc::new(mocks::relay::Relay::new());
let application_cfg = mocks::application::Config {
hasher: Sha256::default(),
relay: relay.clone(),
me: me.clone(),
propose_latency: (1.0, 0.0),
verify_latency: (1.0, 0.0),
certify_latency: (2_000.0, 0.0), should_certify: mocks::application::Certifier::Always,
};
let (actor, application) = mocks::application::Application::new(
context.with_label("application"),
application_cfg,
);
actor.start();
let cfg = Config {
scheme: schemes[0].clone(),
elector,
blocker: oracle.control(me.clone()),
automaton: application.clone(),
relay: application.clone(),
reporter: reporter.clone(),
partition: "cert_cancel_test".to_string(),
epoch: Epoch::new(333),
mailbox_size: 128,
leader_timeout: Duration::from_secs(5),
notarization_timeout: Duration::from_secs(5),
nullify_retry: Duration::from_secs(5),
activity_timeout: ViewDelta::new(10),
replay_buffer: NZUsize!(1024 * 1024),
write_buffer: NZUsize!(1024 * 1024),
buffer_pool: PoolRef::new(PAGE_SIZE, PAGE_CACHE_SIZE),
};
let (actor, mut mailbox) = Actor::new(context.clone(), cfg);
let (resolver_sender, mut resolver_receiver) = mpsc::channel(10);
let resolver = resolver::Mailbox::new(resolver_sender);
let (batcher_sender, mut batcher_receiver) = mpsc::channel(1024);
let batcher = batcher::Mailbox::new(batcher_sender);
let (vote_sender, _vote_receiver) = oracle
.control(me.clone())
.register(0, TEST_QUOTA)
.await
.unwrap();
let (certificate_sender, _certificate_receiver) = oracle
.control(me.clone())
.register(1, TEST_QUOTA)
.await
.unwrap();
actor.start(batcher, resolver, vote_sender, certificate_sender);
if let batcher::Message::Update { active, .. } = batcher_receiver.next().await.unwrap()
{
active.send(true).unwrap();
}
let view5 = View::new(5);
let digest5 = Sha256::hash(b"payload_to_certify");
let proposal5 =
Proposal::new(Round::new(Epoch::new(333), view5), View::new(0), digest5);
let contents = (proposal5.round, Sha256::hash(b"genesis"), 42u64).encode();
relay.broadcast(&me, (digest5, contents)).await;
mailbox.proposal(proposal5.clone()).await;
let (_, notarization) = build_notarization(&schemes, &proposal5, quorum);
mailbox
.recovered(Certificate::Notarization(notarization))
.await;
context.sleep(Duration::from_millis(100)).await;
let (_, finalization) = build_finalization(&schemes, &proposal5, quorum);
mailbox
.recovered(Certificate::Finalization(finalization))
.await;
loop {
if let batcher::Message::Update {
finalized, active, ..
} = batcher_receiver.next().await.unwrap()
{
active.send(true).unwrap();
if finalized >= view5 {
break;
}
}
}
loop {
let msg = resolver_receiver
.next()
.await
.expect("expected resolver msg");
match msg {
MailboxMessage::Certificate(Certificate::Finalization(f)) => {
assert_eq!(f.view(), view5);
break;
}
MailboxMessage::Certificate(_) => continue,
MailboxMessage::Certified { .. } => {
panic!("unexpected Certified message before finalization processed")
}
}
}
let certified_received = select! {
msg = resolver_receiver.next() => {
matches!(msg, Some(MailboxMessage::Certified { .. }))
},
_ = context.sleep(Duration::from_secs(4)) => {
false
},
};
assert!(
!certified_received,
"Certified message should NOT have been sent - certification should be cancelled"
);
});
}
#[test_traced]
fn test_certification_cancelled_on_finalization() {
certification_cancelled_on_finalization::<_, _, Random>(
bls12381_threshold::fixture::<MinPk, _>,
);
certification_cancelled_on_finalization::<_, _, Random>(
bls12381_threshold::fixture::<MinSig, _>,
);
certification_cancelled_on_finalization::<_, _, RoundRobin>(
bls12381_multisig::fixture::<MinPk, _>,
);
certification_cancelled_on_finalization::<_, _, RoundRobin>(
bls12381_multisig::fixture::<MinSig, _>,
);
certification_cancelled_on_finalization::<_, _, RoundRobin>(ed25519::fixture);
certification_cancelled_on_finalization::<_, _, RoundRobin>(secp256r1::fixture);
}
fn certification_after_timeout<S, F>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"certification_after_timeout".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(60));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let elector = RoundRobin::<Sha256>::default();
let built_elector: RoundRobinElector<S> = elector
.clone()
.build(&participants.clone().try_into().unwrap());
let (mut mailbox, mut batcher_receiver, _, _, _) = setup_voter(
&mut context,
&oracle,
&participants,
&schemes,
elector,
Duration::from_secs(10),
Duration::from_secs(10),
Duration::from_secs(100),
mocks::application::Certifier::Always,
)
.await;
let target_view = View::new(3);
advance_to_view(
&mut mailbox,
&mut batcher_receiver,
&schemes,
quorum,
target_view,
)
.await;
assert_ne!(
built_elector.elect(Round::new(Epoch::new(333), target_view), None),
Participant::new(0),
"we should not be leader at view 3"
);
loop {
select! {
msg = batcher_receiver.next() => {
match msg.unwrap() {
batcher::Message::Constructed(Vote::Nullify(n)) if n.view() == target_view => break,
batcher::Message::Update { active, .. } => active.send(true).unwrap(),
_ => {}
}
},
_ = context.sleep(Duration::from_secs(15)) => {
panic!("expected nullify vote");
},
}
}
let proposal = Proposal::new(
Round::new(Epoch::new(333), target_view),
target_view.previous().unwrap(),
Sha256::hash(b"timeout_test"),
);
let (_, notarization) = build_notarization(&schemes, &proposal, quorum);
mailbox
.recovered(Certificate::Notarization(notarization))
.await;
let advanced = loop {
select! {
msg = batcher_receiver.next() => {
if let batcher::Message::Update { current, active, .. } = msg.unwrap() {
active.send(true).unwrap();
if current > target_view {
break true;
}
}
},
_ = context.sleep(Duration::from_secs(5)) => {
break false;
},
}
};
assert!(advanced, "view should advance after certification (timeout case)");
});
}
#[test_traced]
fn test_certification_after_timeout() {
certification_after_timeout::<_, _>(bls12381_threshold::fixture::<MinPk, _>);
certification_after_timeout::<_, _>(bls12381_threshold::fixture::<MinSig, _>);
certification_after_timeout::<_, _>(bls12381_multisig::fixture::<MinPk, _>);
certification_after_timeout::<_, _>(bls12381_multisig::fixture::<MinSig, _>);
certification_after_timeout::<_, _>(ed25519::fixture);
certification_after_timeout::<_, _>(secp256r1::fixture);
}
fn certification_after_notarize_timeout_as_follower<S, F>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"certification_after_notarize_timeout_as_follower".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(60));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let elector = RoundRobin::<Sha256>::default();
let built_elector: RoundRobinElector<S> = elector
.clone()
.build(&participants.clone().try_into().unwrap());
let (mut mailbox, mut batcher_receiver, _, relay, _) = setup_voter(
&mut context,
&oracle,
&participants,
&schemes,
elector,
Duration::from_secs(10),
Duration::from_secs(10),
Duration::from_secs(100),
mocks::application::Certifier::Always,
)
.await;
let target_view = View::new(3);
let parent_payload = advance_to_view(
&mut mailbox,
&mut batcher_receiver,
&schemes,
quorum,
target_view,
)
.await;
assert_ne!(
built_elector.elect(Round::new(Epoch::new(333), target_view), None),
Participant::new(0),
"we should not be leader at view 3"
);
let proposal = Proposal::new(
Round::new(Epoch::new(333), target_view),
target_view.previous().unwrap(),
Sha256::hash(b"follower_test"),
);
let leader = participants[1].clone();
let contents = (proposal.round, parent_payload, 0u64).encode();
relay
.broadcast(&leader, (proposal.payload, contents))
.await;
mailbox.proposal(proposal.clone()).await;
loop {
select! {
msg = batcher_receiver.next() => {
match msg.unwrap() {
batcher::Message::Constructed(Vote::Notarize(n)) if n.view() == target_view => break,
batcher::Message::Update { active, .. } => active.send(true).unwrap(),
_ => {}
}
},
_ = context.sleep(Duration::from_secs(5)) => {
panic!("expected notarize vote");
},
}
}
context.sleep(Duration::from_secs(11)).await;
loop {
select! {
msg = batcher_receiver.next() => {
match msg.unwrap() {
batcher::Message::Constructed(Vote::Nullify(n)) if n.view() == target_view => break,
batcher::Message::Update { active, .. } => active.send(true).unwrap(),
_ => {}
}
},
_ = context.sleep(Duration::from_secs(1)) => {
panic!("expected nullify vote");
},
}
}
let (_, notarization) = build_notarization(&schemes, &proposal, quorum);
mailbox
.recovered(Certificate::Notarization(notarization))
.await;
let advanced = loop {
select! {
msg = batcher_receiver.next() => {
if let batcher::Message::Update { current, active, .. } = msg.unwrap() {
active.send(true).unwrap();
if current > target_view {
break true;
}
}
},
_ = context.sleep(Duration::from_secs(5)) => {
break false;
},
}
};
assert!(advanced, "view should advance after certification (follower case)");
});
}
#[test_traced]
fn test_certification_after_notarize_timeout_as_follower() {
certification_after_notarize_timeout_as_follower::<_, _>(
bls12381_threshold::fixture::<MinPk, _>,
);
certification_after_notarize_timeout_as_follower::<_, _>(
bls12381_threshold::fixture::<MinSig, _>,
);
certification_after_notarize_timeout_as_follower::<_, _>(
bls12381_multisig::fixture::<MinPk, _>,
);
certification_after_notarize_timeout_as_follower::<_, _>(
bls12381_multisig::fixture::<MinSig, _>,
);
certification_after_notarize_timeout_as_follower::<_, _>(ed25519::fixture);
certification_after_notarize_timeout_as_follower::<_, _>(secp256r1::fixture);
}
fn certification_after_notarize_timeout_as_leader<S, F>(mut fixture: F)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"certification_after_notarize_timeout_as_leader".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(60));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let elector = RoundRobin::<Sha256>::default();
let built_elector: RoundRobinElector<S> = elector
.clone()
.build(&participants.clone().try_into().unwrap());
let (mut mailbox, mut batcher_receiver, _, _, _) = setup_voter(
&mut context,
&oracle,
&participants,
&schemes,
elector,
Duration::from_secs(10),
Duration::from_secs(10),
Duration::from_secs(100),
mocks::application::Certifier::Always,
)
.await;
let target_view = View::new(2);
advance_to_view(
&mut mailbox,
&mut batcher_receiver,
&schemes,
quorum,
target_view,
)
.await;
assert_eq!(
built_elector.elect(Round::new(Epoch::new(333), target_view), None),
Participant::new(0),
"we should be leader at view 2"
);
let proposal = loop {
select! {
msg = batcher_receiver.next() => {
match msg.unwrap() {
batcher::Message::Constructed(Vote::Notarize(n)) if n.view() == target_view => {
break n.proposal.clone();
}
batcher::Message::Update { active, .. } => active.send(true).unwrap(),
_ => {}
}
},
_ = context.sleep(Duration::from_secs(5)) => {
panic!("expected notarize vote as leader");
},
}
};
context.sleep(Duration::from_secs(11)).await;
loop {
select! {
msg = batcher_receiver.next() => {
match msg.unwrap() {
batcher::Message::Constructed(Vote::Nullify(n)) if n.view() == target_view => break,
batcher::Message::Update { active, .. } => active.send(true).unwrap(),
_ => {}
}
},
_ = context.sleep(Duration::from_secs(1)) => {
panic!("expected nullify vote");
},
}
}
let (_, notarization) = build_notarization(&schemes, &proposal, quorum);
mailbox
.recovered(Certificate::Notarization(notarization))
.await;
let advanced = loop {
select! {
msg = batcher_receiver.next() => {
if let batcher::Message::Update { current, active, .. } = msg.unwrap() {
active.send(true).unwrap();
if current > target_view {
break true;
}
}
},
_ = context.sleep(Duration::from_secs(5)) => {
break false;
},
}
};
assert!(advanced, "view should advance after certification (leader case)");
});
}
#[test_traced]
fn test_certification_after_notarize_timeout_as_leader() {
certification_after_notarize_timeout_as_leader::<_, _>(
bls12381_threshold::fixture::<MinPk, _>,
);
certification_after_notarize_timeout_as_leader::<_, _>(
bls12381_threshold::fixture::<MinSig, _>,
);
certification_after_notarize_timeout_as_leader::<_, _>(
bls12381_multisig::fixture::<MinPk, _>,
);
certification_after_notarize_timeout_as_leader::<_, _>(
bls12381_multisig::fixture::<MinSig, _>,
);
certification_after_notarize_timeout_as_leader::<_, _>(ed25519::fixture);
certification_after_notarize_timeout_as_leader::<_, _>(secp256r1::fixture);
}
fn cancelled_certification_does_not_hang<S, F>(mut fixture: F, traces: TraceStorage)
where
S: Scheme<Sha256Digest, PublicKey = PublicKey>,
F: FnMut(&mut deterministic::Context, &[u8], u32) -> Fixture<S>,
{
let n = 5;
let quorum = quorum(n);
let namespace = b"consensus".to_vec();
let executor = deterministic::Runner::timed(Duration::from_secs(10));
executor.start(|mut context| async move {
let (network, oracle) = Network::new(
context.with_label("network"),
NConfig {
max_size: 1024 * 1024,
disconnect_on_block: true,
tracked_peer_sets: None,
},
);
network.start();
let Fixture {
participants,
schemes,
..
} = fixture(&mut context, &namespace, n);
let elector = RoundRobin::<Sha256>::default();
let (mut mailbox, mut batcher_receiver, _, relay, _) = setup_voter(
&mut context,
&oracle,
&participants,
&schemes,
elector,
Duration::from_millis(500),
Duration::from_millis(500),
Duration::from_millis(500),
mocks::application::Certifier::Cancel,
)
.await;
let target_view = View::new(3);
let parent_payload = advance_to_view(
&mut mailbox,
&mut batcher_receiver,
&schemes,
quorum,
target_view,
)
.await;
let proposal = Proposal::new(
Round::new(Epoch::new(333), target_view),
target_view.previous().unwrap(),
Sha256::hash(b"test_proposal"),
);
let leader = participants[1].clone();
let contents = (proposal.round, parent_payload, 0u64).encode();
relay
.broadcast(&leader, (proposal.payload, contents))
.await;
mailbox.proposal(proposal.clone()).await;
let (_, notarization) = build_notarization(&schemes, &proposal, quorum);
mailbox
.resolved(Certificate::Notarization(notarization))
.await;
loop {
select! {
msg = batcher_receiver.next() => {
match msg.unwrap() {
batcher::Message::Constructed(Vote::Nullify(nullify)) if nullify.view() == target_view => {
break;
}
batcher::Message::Update { active, .. } => active.send(true).unwrap(),
_ => {}
}
},
_ = context.sleep(Duration::from_secs(5)) => {
panic!(
"voter should emit nullify for view {target_view} despite cancelled certification",
);
},
}
}
let expected_round = format!("Round {{ epoch: Epoch(333), view: View({target_view}) }}");
traces
.get_by_level(Level::DEBUG)
.expect_event(|event| {
event.metadata.content == "failed to certify proposal"
&& event
.metadata
.fields
.iter()
.any(|(name, value)| name == "err" && value == "Canceled")
&& event
.metadata
.fields
.iter()
.any(|(name, value)| name == "round" && value == &expected_round)
})
.unwrap();
});
}
#[test_collect_traces]
fn test_cancelled_certification_does_not_hang(traces: TraceStorage) {
cancelled_certification_does_not_hang(
bls12381_threshold::fixture::<MinPk, _>,
traces.clone(),
);
cancelled_certification_does_not_hang(
bls12381_threshold::fixture::<MinSig, _>,
traces.clone(),
);
cancelled_certification_does_not_hang(
bls12381_multisig::fixture::<MinPk, _>,
traces.clone(),
);
cancelled_certification_does_not_hang(
bls12381_multisig::fixture::<MinSig, _>,
traces.clone(),
);
cancelled_certification_does_not_hang(ed25519::fixture, traces.clone());
cancelled_certification_does_not_hang(secp256r1::fixture, traces);
}
}