#![allow(clippy::excessive_precision)]
use super::*;
use all_asserts::*;
use rstest::rstest;
const TEST_FINALITY: i64 = 900;
const PYTHON_REFERENCE_CHAIN: [i64; 905] = [
2, 3, 4, 3, 4, 7, 5, 6, 5, 5, 4, 2, 3, 3, 10, 7, 3, 8, 6, 3, 2, 3, 5, 3, 7, 6, 5, 3, 4, 6, 6,
8, 6, 3, 2, 6, 5, 2, 4, 4, 4, 6, 5, 7, 8, 6, 3, 0, 10, 8, 3, 7, 4, 6, 4, 6, 5, 2, 5, 5, 7, 6,
2, 1, 3, 5, 3, 5, 10, 4, 0, 5, 11, 6, 8, 6, 4, 8, 3, 4, 3, 2, 5, 6, 6, 5, 3, 9, 5, 2, 9, 3, 6,
5, 4, 6, 2, 3, 4, 7, 5, 8, 2, 6, 0, 3, 5, 6, 6, 4, 3, 6, 5, 2, 3, 4, 6, 1, 5, 3, 5, 7, 2, 4,
11, 3, 4, 8, 5, 3, 6, 6, 7, 5, 1, 2, 1, 4, 4, 5, 6, 4, 2, 6, 5, 5, 1, 2, 5, 5, 0, 4, 4, 7, 4,
10, 6, 4, 9, 5, 5, 1, 0, 3, 7, 1, 6, 4, 3, 5, 7, 6, 10, 3, 5, 4, 1, 6, 2, 2, 2, 5, 4, 7, 4, 2,
5, 6, 3, 8, 4, 6, 6, 5, 3, 3, 3, 2, 5, 5, 6, 6, 4, 7, 4, 1, 3, 6, 10, 3, 3, 4, 6, 3, 6, 5, 4,
3, 7, 6, 2, 4, 2, 3, 1, 9, 5, 1, 5, 6, 4, 3, 8, 3, 6, 3, 2, 2, 1, 2, 3, 6, 2, 4, 2, 4, 5, 5, 4,
4, 7, 8, 8, 8, 8, 6, 2, 3, 3, 4, 4, 3, 3, 1, 4, 5, 6, 3, 4, 7, 4, 1, 2, 2, 10, 2, 2, 3, 3, 5,
4, 5, 3, 5, 1, 8, 4, 2, 6, 4, 9, 4, 7, 2, 2, 4, 4, 3, 3, 4, 7, 6, 4, 2, 8, 1, 4, 3, 4, 7, 4, 0,
6, 7, 4, 4, 6, 3, 5, 7, 4, 8, 2, 2, 6, 4, 5, 3, 3, 3, 1, 4, 2, 4, 3, 5, 2, 3, 0, 6, 4, 7, 3, 6,
3, 4, 4, 6, 3, 3, 2, 7, 4, 4, 5, 3, 4, 5, 3, 4, 4, 3, 4, 1, 5, 4, 4, 5, 4, 2, 4, 5, 3, 6, 5, 6,
3, 4, 4, 4, 5, 4, 4, 5, 4, 4, 2, 5, 2, 4, 2, 1, 6, 6, 5, 5, 4, 9, 3, 2, 6, 4, 2, 4, 7, 7, 5, 5,
7, 8, 2, 5, 4, 5, 1, 4, 5, 2, 5, 6, 5, 4, 4, 8, 5, 5, 6, 6, 0, 2, 4, 5, 5, 3, 5, 4, 8, 2, 4, 6,
7, 3, 5, 5, 7, 1, 2, 5, 3, 10, 5, 10, 1, 10, 3, 5, 5, 2, 6, 2, 5, 4, 2, 1, 5, 9, 2, 4, 4, 2, 2,
5, 5, 6, 4, 1, 6, 5, 5, 2, 6, 1, 9, 4, 7, 3, 8, 5, 4, 5, 6, 8, 5, 4, 3, 3, 2, 3, 3, 4, 4, 7, 7,
3, 4, 4, 4, 6, 3, 3, 4, 5, 4, 1, 3, 8, 5, 4, 5, 7, 5, 8, 2, 7, 9, 5, 3, 7, 5, 6, 6, 5, 6, 8, 4,
6, 3, 5, 4, 6, 2, 2, 6, 5, 4, 6, 3, 3, 4, 5, 2, 3, 3, 6, 6, 4, 5, 4, 3, 8, 4, 8, 3, 5, 3, 6, 4,
6, 1, 3, 3, 4, 8, 5, 7, 4, 5, 5, 1, 3, 6, 5, 3, 6, 3, 5, 5, 6, 5, 6, 5, 7, 6, 4, 7, 6, 5, 3, 3,
2, 4, 8, 4, 5, 1, 4, 8, 1, 2, 2, 2, 4, 11, 1, 3, 3, 2, 1, 7, 7, 3, 4, 5, 2, 5, 6, 3, 6, 3, 9,
3, 0, 4, 2, 5, 4, 3, 2, 7, 4, 2, 10, 7, 4, 3, 5, 8, 5, 5, 2, 3, 3, 8, 6, 5, 6, 6, 6, 9, 3, 3,
2, 6, 5, 4, 4, 4, 2, 5, 2, 8, 4, 3, 2, 2, 3, 3, 7, 5, 0, 7, 3, 5, 3, 3, 3, 6, 3, 3, 1, 3, 5, 7,
5, 4, 5, 2, 4, 3, 7, 9, 2, 4, 2, 7, 6, 5, 3, 2, 6, 3, 6, 3, 5, 6, 3, 3, 3, 3, 3, 7, 5, 3, 4, 4,
9, 5, 7, 9, 4, 9, 2, 4, 3, 1, 4, 6, 1, 3, 5, 5, 6, 4, 4, 2, 7, 7, 4, 5, 3, 1, 4, 5, 2, 4, 5, 2,
7, 2, 11, 5, 4, 8, 6, 4, 3, 3, 6, 5, 4, 3, 4, 7, 2, 2, 2, 4, 3, 5, 4, 5, 3, 6, 5, 5, 2, 6, 1,
11, 3, 3, 5, 5, 6, 2, 5, 3, 4, 5, 5, 7, 7, 7, 9, 3, 4, 6, 3, 3, 2, 6, 6, 1, 3, 1, 5, 7, 5, 7,
8, 4, 5, 2, 6, 6, 5, 7, 5, 5, 6, 4, 2, 7, 6, 5, 5, 9, 4, 3, 3, 1, 1, 4, 5, 5, 6, 7, 2, 4, 6, 3,
5, 5, 5, 4, 2, 4, 3, 3, 5, 2, 4, 4, 5, 6, 3, 6, 4, 5, 4, 5, 2, 8, 6, 5, 6, 7, 6, 2, 4, 9, 1, 3,
5, 4, 7, 2, 5, 4, 7, 9, 2, 3, 2, 2, 7, 4, 1, 2, 6, 5, 10, 2, 4, 3,
];
#[rstest]
#[case(5, 1.58182730260265891863e-03)]
#[case(10, 1.67515743138728720072e-04)]
#[case(15, 2.80696481196116546684e-06)]
#[case(20, 6.84359796410981096872e-08)]
#[case(25, 1.46218662028857760238e-09)]
#[case(30, 4.62723254179747158594e-12)]
#[case(40, 3.53912692038794048900e-17)]
#[case(50, 1.37790735432279053542e-20)]
#[case(75, 2.40782990048672131651e-24)]
#[case(100, 3.21616912956779552478e-24)]
#[case(905, 1.)]
#[case(906, 1.)]
#[case(0, 1.)]
#[case(-1, 1.)]
fn test_calc_validator_prob_python_reference(#[case] depth: i64, #[case] want: f64) {
let chain = PYTHON_REFERENCE_CHAIN.as_slice();
let current_epoch = chain.len() as i64 - 1;
let target_epoch = current_epoch - depth;
let got = calc_validator_prob(
chain,
TEST_FINALITY,
DEFAULT_BLOCKS_PER_EPOCH,
DEFAULT_BYZANTINE_FRACTION,
current_epoch,
target_epoch,
)
.unwrap();
let relative_error = (got - want).abs() / want.abs();
assert_le!(
relative_error,
1e-12,
"depth {depth}: got {got}, want {want}, relative error {relative_error}"
);
}
#[test]
fn test_calc_validator_prob_healthy_chain() {
let chain = vec![5; 905];
let current_epoch = chain.len() as i64 - 1;
let prob30 = calc_validator_prob(
&chain,
TEST_FINALITY,
DEFAULT_BLOCKS_PER_EPOCH,
DEFAULT_BYZANTINE_FRACTION,
current_epoch,
current_epoch - 30,
)
.unwrap();
assert_lt!(
prob30,
*DEFAULT_GUARANTEE,
"healthy chain at depth 30 should be below 2^-30"
);
let prob5 = calc_validator_prob(
&chain,
TEST_FINALITY,
DEFAULT_BLOCKS_PER_EPOCH,
DEFAULT_BYZANTINE_FRACTION,
current_epoch,
current_epoch - 5,
)
.unwrap();
assert_gt!(
prob5,
prob30,
"shallower depth should have higher reorg probability"
);
}
#[test]
fn test_calc_validator_prob_degraded_chain() {
let chain = vec![2; 905];
let current_epoch = chain.len() as i64 - 1;
let prob30 = calc_validator_prob(
&chain,
TEST_FINALITY,
DEFAULT_BLOCKS_PER_EPOCH,
DEFAULT_BYZANTINE_FRACTION,
current_epoch,
current_epoch - 30,
)
.unwrap();
assert_ge!(
prob30,
*DEFAULT_GUARANTEE,
"degraded chain at depth 30 should NOT achieve 2^-30"
);
}
#[test]
fn test_find_threshold_depth_healthy_chain() {
let chain = vec![5; 905];
let depth = find_threshold_depth(
&chain,
TEST_FINALITY,
DEFAULT_BLOCKS_PER_EPOCH,
DEFAULT_BYZANTINE_FRACTION,
*DEFAULT_GUARANTEE,
)
.unwrap();
assert_gt!(depth, 0, "healthy chain should find a threshold");
assert_lt!(
depth,
35,
"healthy chain should finalize well before depth 35"
);
}
#[test]
fn test_find_threshold_depth_degraded_chain() {
let chain = vec![1; 905];
let depth = find_threshold_depth(
&chain,
TEST_FINALITY,
DEFAULT_BLOCKS_PER_EPOCH,
DEFAULT_BYZANTINE_FRACTION,
*DEFAULT_GUARANTEE,
)
.unwrap();
assert_eq!(
depth, -1,
"severely degraded chain should not find threshold within search range"
);
}
#[test]
fn test_find_threshold_depth_mildly_degraded_chain() {
let chain = vec![3; 905];
let depth = find_threshold_depth(
&chain,
TEST_FINALITY,
DEFAULT_BLOCKS_PER_EPOCH,
DEFAULT_BYZANTINE_FRACTION,
*DEFAULT_GUARANTEE,
)
.unwrap();
assert_gt!(
depth,
35,
"mildly degraded chain should require more depth than healthy"
);
assert_lt!(
depth,
BISECT_HIGH,
"mildly degraded chain should still find a threshold"
);
}
#[rstest]
#[case(4)]
#[case(3)]
#[case(2)]
#[case(1)]
fn test_find_threshold_depth_too_short_chain(#[case] chain_len: usize) {
let chain = vec![3; chain_len];
let depth = find_threshold_depth(
&chain,
TEST_FINALITY,
DEFAULT_BLOCKS_PER_EPOCH,
DEFAULT_BYZANTINE_FRACTION,
*DEFAULT_GUARANTEE,
)
.unwrap();
assert_eq!(depth, -1, "input chain is too short");
}
#[test]
fn test_find_threshold_depth_too_large_guarantee() {
let chain = vec![3; 200];
let guarantee = 2_f64;
let depth = find_threshold_depth(
&chain,
TEST_FINALITY,
DEFAULT_BLOCKS_PER_EPOCH,
DEFAULT_BYZANTINE_FRACTION,
guarantee,
)
.unwrap();
assert_eq!(depth, BISECT_LOW, "guarantee is too large");
}
#[rstest]
#[case(0., -0.1, f64::NEG_INFINITY)]
#[case(0., -1., f64::NEG_INFINITY)]
#[case(0., 1., f64::NEG_INFINITY)]
#[case(0., 0.1, f64::NEG_INFINITY)]
#[case(0., 0., 0.)]
fn poisson_log_prob_tests(#[case] lambda: f64, #[case] x: f64, #[case] want: f64) {
let got = poisson_log_prob(lambda, x);
assert_eq!(got, want);
}