#[allow(dead_code)]
mod common;
#[allow(dead_code)]
mod components;
use crate::common::primary::{TestNetwork, TestNetworkConfig};
use deadline::deadline;
use itertools::Itertools;
use snarkos_node_bft::MAX_FETCH_TIMEOUT_IN_MS;
use std::time::Duration;
use tokio::time::sleep;
#[tokio::test(flavor = "multi_thread")]
#[ignore = "long-running e2e test"]
async fn test_state_coherence() {
const N: u16 = 4;
const TRANSMISSION_INTERVAL_MS: u64 = 10;
let mut network = tokio::task::spawn_blocking(|| {
TestNetwork::new(TestNetworkConfig {
num_nodes: N,
bft: true,
connect_all: true,
fire_transmissions: Some(TRANSMISSION_INTERVAL_MS),
log_level: Some(0),
log_connections: true,
})
})
.await
.unwrap();
network.start().await;
std::future::pending::<()>().await;
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "fails"]
async fn test_resync() {
const N: u16 = 4;
const TRANSMISSION_INTERVAL_MS: u64 = 10;
let mut network = tokio::task::spawn_blocking(|| {
TestNetwork::new(TestNetworkConfig {
num_nodes: N,
bft: true,
connect_all: true,
fire_transmissions: Some(TRANSMISSION_INTERVAL_MS),
log_level: Some(0),
log_connections: false,
})
})
.await
.unwrap();
network.start().await;
const BREAK_ROUND: u64 = 4;
let network_clone = network.clone();
deadline!(Duration::from_secs(20), move || { network_clone.is_round_reached(BREAK_ROUND) });
network.disconnect(N).await;
let mut spare_network = TestNetwork::new(TestNetworkConfig {
num_nodes: N,
bft: true,
connect_all: false,
fire_transmissions: None,
log_level: None,
log_connections: false,
});
spare_network.start().await;
for i in 1..N {
let spare_validator = spare_network.validators.get(&i).cloned().unwrap();
network.validators.insert(i, spare_validator);
}
network.connect_all().await;
const RECOVERY_ROUND: u64 = 8;
let network_clone = network.clone();
deadline!(Duration::from_secs(20), move || { network_clone.is_round_reached(RECOVERY_ROUND) });
}
#[tokio::test(flavor = "multi_thread")]
async fn test_quorum_threshold() {
const N: u16 = 4;
const TRANSMISSION_INTERVAL_MS: u64 = 10;
let mut network = tokio::task::spawn_blocking(|| {
TestNetwork::new(TestNetworkConfig {
num_nodes: N,
bft: true,
connect_all: false,
fire_transmissions: None,
log_level: None,
log_connections: true,
})
})
.await
.unwrap();
network.start().await;
for validators in network.validators.values() {
assert_eq!(validators.primary.current_round(), 1);
}
network.fire_transmissions_at(0, TRANSMISSION_INTERVAL_MS);
sleep(Duration::from_millis(MAX_FETCH_TIMEOUT_IN_MS)).await;
for validator in network.validators.values() {
assert_eq!(validator.primary.current_round(), 1);
}
network.connect_validators(0, 1).await;
network.fire_transmissions_at(1, TRANSMISSION_INTERVAL_MS);
sleep(Duration::from_millis(MAX_FETCH_TIMEOUT_IN_MS)).await;
for validator in network.validators.values() {
assert_eq!(validator.primary.current_round(), 1);
}
network.connect_validators(0, 2).await;
network.connect_validators(1, 2).await;
network.fire_transmissions_at(2, TRANSMISSION_INTERVAL_MS);
const TARGET_ROUND: u64 = 4;
let net = network.clone();
deadline!(Duration::from_secs(20), move || { net.is_round_reached(TARGET_ROUND) });
}
#[tokio::test(flavor = "multi_thread")]
async fn test_quorum_break() {
const N: u16 = 4;
const TRANSMISSION_INTERVAL_MS: u64 = 10;
let mut network = tokio::task::spawn_blocking(|| {
TestNetwork::new(TestNetworkConfig {
num_nodes: N,
bft: true,
connect_all: true,
fire_transmissions: Some(TRANSMISSION_INTERVAL_MS),
log_level: None,
log_connections: true,
})
})
.await
.unwrap();
network.start().await;
const TARGET_ROUND: u64 = 4;
let network_clone = network.clone();
deadline!(Duration::from_secs(20), move || { network_clone.is_round_reached(TARGET_ROUND) });
const NUM_NODES: u16 = 2;
network.disconnect(NUM_NODES).await;
assert!(network.is_halted().await);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_leader_election_consistency() {
const STARTING_ROUND: u64 = 4;
const MAX_ROUND: u64 = 20;
const N: u16 = 4;
const CANNON_INTERVAL_MS: u64 = 10;
let mut network = tokio::task::spawn_blocking(|| {
TestNetwork::new(TestNetworkConfig {
num_nodes: N,
bft: true,
connect_all: true,
fire_transmissions: Some(CANNON_INTERVAL_MS),
log_level: None,
log_connections: true,
})
})
.await
.unwrap();
network.start().await;
let cloned_network = network.clone();
deadline!(Duration::from_secs(60), move || { cloned_network.is_round_reached(STARTING_ROUND) });
for target_round in (STARTING_ROUND..=MAX_ROUND).step_by(2) {
let cloned_network = network.clone();
deadline!(Duration::from_secs(20), move || { cloned_network.is_round_reached(target_round) });
let validators = network.validators.values().collect_vec();
let mut leaders = Vec::new();
for validator in validators.iter() {
if validator.primary.current_round() == target_round {
let bft = validator.bft.get().unwrap();
if let Some(leader) = bft.leader() {
if validator.primary.current_round() == target_round {
leaders.push(leader);
}
}
}
}
println!("Found {} validators with a leader ({} out of sync)", leaders.len(), validators.len() - leaders.len());
assert!(leaders.iter().all_equal());
}
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "run multiple times to see failure"]
async fn test_transient_break() {
const N: u16 = 4;
const TRANSMISSION_INTERVAL_MS: u64 = 10;
let mut network = tokio::task::spawn_blocking(|| {
TestNetwork::new(TestNetworkConfig {
num_nodes: N,
bft: true,
connect_all: true,
fire_transmissions: Some(TRANSMISSION_INTERVAL_MS),
log_level: Some(6),
log_connections: false,
})
})
.await
.unwrap();
network.start().await;
const FIRST_BREAK_ROUND: u64 = 10;
let network_clone = network.clone();
deadline!(Duration::from_secs(60), move || { network_clone.is_round_reached(FIRST_BREAK_ROUND) });
network.disconnect_one(3).await;
const SECOND_BREAK_ROUND: u64 = 25;
let network_clone = network.clone();
deadline!(Duration::from_secs(80), move || { network_clone.is_round_reached(SECOND_BREAK_ROUND) });
network.disconnect_one(2).await;
assert!(network.is_halted().await);
network.connect_one(3).await;
const RECOVERY_ROUND: u64 = 30;
let network_clone = network.clone();
deadline!(Duration::from_secs(60), move || { network_clone.is_round_reached(RECOVERY_ROUND) });
}