use crate::types::View;
use commonware_p2p::simulated::SplitTarget;
use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng};
#[derive(Clone, Copy)]
pub enum Strategy {
View,
Fixed(usize),
Broadcast,
Isolate(usize),
Shuffle,
}
impl Strategy {
pub fn partitions<P: Clone>(self, view: View, participants: &[P]) -> (Vec<P>, Vec<P>) {
let n = participants.len();
match self {
Self::View => {
let split = view.get() as usize % n;
let (primary, secondary) = participants.split_at(split);
(primary.to_vec(), secondary.to_vec())
}
Self::Fixed(split) => {
let (primary, secondary) = participants.split_at(split);
(primary.to_vec(), secondary.to_vec())
}
Self::Broadcast => (participants.to_vec(), participants.to_vec()),
Self::Isolate(idx) => (
vec![participants[idx].clone()],
participants
.iter()
.enumerate()
.filter(|(i, _)| *i != idx)
.map(|(_, p)| p.clone())
.collect(),
),
Self::Shuffle => {
let mut rng = StdRng::seed_from_u64(view.get());
let mut shuffled: Vec<_> = participants.to_vec();
shuffled.shuffle(&mut rng);
let split = rng.gen_range(0..n);
let (primary, secondary) = shuffled.split_at(split);
(primary.to_vec(), secondary.to_vec())
}
}
}
pub fn route<P: Clone + PartialEq>(
self,
view: View,
sender: &P,
participants: &[P],
) -> SplitTarget {
let (primary, secondary) = self.partitions(view, participants);
let in_primary = primary.contains(sender);
let in_secondary = secondary.contains(sender);
match (in_primary, in_secondary) {
(true, true) => SplitTarget::Both,
(true, false) => SplitTarget::Primary,
(false, true) => SplitTarget::Secondary,
(false, false) => panic!("sender not in any partition"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_twin_partition_view_split_varies_by_view() {
let participants: Vec<u32> = (0..5).collect();
let (primary, secondary) = Strategy::View.partitions(View::new(0), &participants);
assert!(primary.is_empty());
assert_eq!(secondary, vec![0, 1, 2, 3, 4]);
let (primary, secondary) = Strategy::View.partitions(View::new(1), &participants);
assert_eq!(primary, vec![0]);
assert_eq!(secondary, vec![1, 2, 3, 4]);
let (primary, secondary) = Strategy::View.partitions(View::new(2), &participants);
assert_eq!(primary, vec![0, 1]);
assert_eq!(secondary, vec![2, 3, 4]);
let (primary, secondary) = Strategy::View.partitions(View::new(5), &participants);
assert!(primary.is_empty());
assert_eq!(secondary, vec![0, 1, 2, 3, 4]);
let (primary, secondary) = Strategy::View.partitions(View::new(7), &participants);
assert_eq!(primary, vec![0, 1]);
assert_eq!(secondary, vec![2, 3, 4]);
}
#[test]
fn test_twin_partition_fixed_constant_split() {
let participants: Vec<u32> = (0..5).collect();
let partition = Strategy::Fixed(2);
for view in [0, 1, 5, 100] {
let (primary, secondary) = partition.partitions(View::new(view), &participants);
assert_eq!(primary, vec![0, 1]);
assert_eq!(secondary, vec![2, 3, 4]);
}
let (primary, secondary) = Strategy::Fixed(0).partitions(View::new(0), &participants);
assert!(primary.is_empty());
assert_eq!(secondary, vec![0, 1, 2, 3, 4]);
let (primary, secondary) = Strategy::Fixed(5).partitions(View::new(0), &participants);
assert_eq!(primary, vec![0, 1, 2, 3, 4]);
assert!(secondary.is_empty());
}
#[test]
fn test_twin_partition_broadcast_both_get_all() {
let participants: Vec<u32> = (0..5).collect();
for view in [0, 1, 5, 100] {
let (primary, secondary) =
Strategy::Broadcast.partitions(View::new(view), &participants);
assert_eq!(primary, vec![0, 1, 2, 3, 4]);
assert_eq!(secondary, vec![0, 1, 2, 3, 4]);
}
}
#[test]
fn test_twin_partition_isolate_single_vs_rest() {
let participants: Vec<u32> = (0..5).collect();
let partition = Strategy::Isolate(2);
for view in [0, 1, 5, 100] {
let (primary, secondary) = partition.partitions(View::new(view), &participants);
assert_eq!(primary, vec![2]);
assert_eq!(secondary, vec![0, 1, 3, 4]);
}
let (primary, secondary) = Strategy::Isolate(0).partitions(View::new(0), &participants);
assert_eq!(primary, vec![0]);
assert_eq!(secondary, vec![1, 2, 3, 4]);
let (primary, secondary) = Strategy::Isolate(4).partitions(View::new(0), &participants);
assert_eq!(primary, vec![4]);
assert_eq!(secondary, vec![0, 1, 2, 3]);
}
#[test]
fn test_twin_partition_route_view() {
let participants: Vec<u32> = (0..5).collect();
let partition = Strategy::View;
assert_eq!(
partition.route(View::new(2), &0, &participants),
SplitTarget::Primary
);
assert_eq!(
partition.route(View::new(2), &1, &participants),
SplitTarget::Primary
);
assert_eq!(
partition.route(View::new(2), &2, &participants),
SplitTarget::Secondary
);
assert_eq!(
partition.route(View::new(2), &4, &participants),
SplitTarget::Secondary
);
}
#[test]
fn test_twin_partition_route_fixed() {
let participants: Vec<u32> = (0..5).collect();
let partition = Strategy::Fixed(3);
assert_eq!(
partition.route(View::new(0), &0, &participants),
SplitTarget::Primary
);
assert_eq!(
partition.route(View::new(0), &2, &participants),
SplitTarget::Primary
);
assert_eq!(
partition.route(View::new(0), &3, &participants),
SplitTarget::Secondary
);
assert_eq!(
partition.route(View::new(0), &4, &participants),
SplitTarget::Secondary
);
}
#[test]
fn test_twin_partition_route_broadcast() {
let participants: Vec<u32> = (0..5).collect();
let partition = Strategy::Broadcast;
for sender in &participants {
assert_eq!(
partition.route(View::new(0), sender, &participants),
SplitTarget::Both
);
}
}
#[test]
fn test_twin_partition_route_isolate() {
let participants: Vec<u32> = (0..5).collect();
let partition = Strategy::Isolate(2);
assert_eq!(
partition.route(View::new(0), &2, &participants),
SplitTarget::Primary
);
assert_eq!(
partition.route(View::new(0), &0, &participants),
SplitTarget::Secondary
);
assert_eq!(
partition.route(View::new(0), &1, &participants),
SplitTarget::Secondary
);
assert_eq!(
partition.route(View::new(0), &3, &participants),
SplitTarget::Secondary
);
assert_eq!(
partition.route(View::new(0), &4, &participants),
SplitTarget::Secondary
);
}
#[test]
fn test_twin_partition_shuffle_deterministic() {
let participants: Vec<u32> = (0..5).collect();
let (primary_1, secondary_1) = Strategy::Shuffle.partitions(View::new(42), &participants);
let (primary_2, secondary_2) = Strategy::Shuffle.partitions(View::new(42), &participants);
assert_eq!(primary_1, primary_2);
assert_eq!(secondary_1, secondary_2);
}
#[test]
fn test_twin_partition_shuffle_varies_by_view() {
let participants: Vec<u32> = (0..50).collect();
let (primary_0, secondary_0) = Strategy::Shuffle.partitions(View::new(0), &participants);
let (primary_1, secondary_1) = Strategy::Shuffle.partitions(View::new(1), &participants);
let (primary_2, secondary_2) = Strategy::Shuffle.partitions(View::new(2), &participants);
let all_same = primary_0 == primary_1
&& primary_1 == primary_2
&& secondary_0 == secondary_1
&& secondary_1 == secondary_2;
assert!(!all_same, "shuffle should vary by view");
}
#[test]
fn test_twin_partition_shuffle_covers_all_participants() {
let participants: Vec<u32> = (0..5).collect();
for view in [0, 1, 5, 42, 100] {
let (primary, secondary) = Strategy::Shuffle.partitions(View::new(view), &participants);
let mut combined: Vec<_> = primary.iter().chain(secondary.iter()).copied().collect();
combined.sort();
assert_eq!(combined, participants);
}
}
#[test]
fn test_twin_partition_shuffle_route() {
let participants: Vec<u32> = (0..5).collect();
let partition = Strategy::Shuffle;
for view in [0, 1, 5, 42] {
let (primary, secondary) = partition.partitions(View::new(view), &participants);
for p in &participants {
let target = partition.route(View::new(view), p, &participants);
let in_primary = primary.contains(p);
let in_secondary = secondary.contains(p);
assert!(
in_primary ^ in_secondary,
"participant should be in exactly one partition"
);
if in_primary {
assert_eq!(target, SplitTarget::Primary);
} else {
assert_eq!(target, SplitTarget::Secondary);
}
}
}
}
}