montana/Монтана-Протокол/Код/crates/mt-timechain/tests/determinism_invariants.rs

196 lines
5.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Automated determinism invariants для mt-timechain.
// M2 batch 3 audit prep — TimeChain VDF + Adaptive D + cemented_bundle_aggregate
// = критическая консенсус-поверхность с защитой [I-8] Network-Bound
// Unpredictability. Любой non-determinism = consensus fork.
use mt_genesis::genesis_params;
use mt_state::NodeId;
use mt_timechain::{cemented_bundle_aggregate, next_d, vdf_step, vdf_verify};
// ---------- VDF determinism ----------
#[test]
fn vdf_step_zero_iterations_identity() {
let prev = [0x42u8; 32];
assert_eq!(
vdf_step(&prev, 0),
prev,
"vdf_step(prev, 0) должна быть identity (no-op)"
);
}
#[test]
fn vdf_step_deterministic() {
let prev = [0x33u8; 32];
let a = vdf_step(&prev, 100);
let b = vdf_step(&prev, 100);
assert_eq!(a, b);
}
#[test]
fn vdf_step_changes_with_iterations() {
let prev = [0x55u8; 32];
let a = vdf_step(&prev, 1);
let b = vdf_step(&prev, 2);
let c = vdf_step(&prev, 3);
assert_ne!(a, b);
assert_ne!(b, c);
assert_ne!(a, c);
}
#[test]
fn vdf_step_changes_with_prev_input() {
assert_ne!(vdf_step(&[0x00u8; 32], 50), vdf_step(&[0xFFu8; 32], 50));
}
#[test]
fn vdf_verify_accepts_correct_chain() {
let prev = [0x77u8; 32];
let claim = vdf_step(&prev, 42);
assert!(vdf_verify(&prev, 42, &claim));
}
#[test]
fn vdf_verify_rejects_wrong_claim() {
let prev = [0x88u8; 32];
let claim = vdf_step(&prev, 50);
let bad_claim = [0x99u8; 32];
// Positive + negative checks
assert!(vdf_verify(&prev, 50, &claim));
assert!(!vdf_verify(&prev, 50, &bad_claim));
}
#[test]
fn vdf_verify_rejects_wrong_iteration_count() {
let prev = [0xAAu8; 32];
let claim = vdf_step(&prev, 100);
assert!(vdf_verify(&prev, 100, &claim));
assert!(!vdf_verify(&prev, 99, &claim));
assert!(!vdf_verify(&prev, 101, &claim));
}
#[test]
fn vdf_chain_composition() {
// vdf_step(vdf_step(prev, A), B) == vdf_step(prev, A+B)
let prev = [0xBBu8; 32];
let a = 30u64;
let b = 70u64;
let intermediate = vdf_step(&prev, a);
let chained = vdf_step(&intermediate, b);
let direct = vdf_step(&prev, a + b);
assert_eq!(chained, direct, "VDF chain composition non-associative");
}
// ---------- next_d Adaptive D feedback ----------
#[test]
fn next_d_dead_zone_unchanged() {
let params = genesis_params();
let current_d = 252_000_000u64;
// dead_zone middle (90%) → unchanged
let result = next_d(current_d, 900, params);
assert_eq!(
result, current_d,
"next_d in dead zone должен быть unchanged"
);
}
#[test]
fn next_d_above_high_threshold_increases() {
let params = genesis_params();
let current_d = 252_000_000u64;
let result = next_d(current_d, 1000, params); // 100% > high (95%)
assert!(
result > current_d,
"next_d above high threshold должен расти"
);
}
#[test]
fn next_d_below_low_threshold_decreases() {
let params = genesis_params();
let current_d = 252_000_000u64;
let result = next_d(current_d, 0, params); // 0% < low (85%)
assert!(
result < current_d,
"next_d below low threshold должен уменьшаться"
);
}
#[test]
fn next_d_deterministic() {
let params = genesis_params();
let current_d = 252_000_000u64;
for ratio in [0u32, 500, 850, 900, 950, 1000] {
let a = next_d(current_d, ratio, params);
let b = next_d(current_d, ratio, params);
assert_eq!(a, b, "next_d non-deterministic at ratio {ratio}");
}
}
// ---------- cemented_bundle_aggregate determinism + [I-8] ----------
#[test]
fn cba_window_zero_returns_genesis_zero() {
let nodes: Vec<NodeId> = vec![[0x11; 32], [0x22; 32]];
assert_eq!(cemented_bundle_aggregate(0, &nodes), [0u8; 32]);
assert_eq!(cemented_bundle_aggregate(1, &nodes), [0u8; 32]);
}
#[test]
fn cba_empty_cemented_returns_empty_marker() {
let result = cemented_bundle_aggregate(100, &[]);
// Must not be all-zeros (genesis case) AND deterministic
assert_ne!(result, [0u8; 32]);
assert_eq!(cemented_bundle_aggregate(100, &[]), result);
}
#[test]
fn cba_changes_on_window() {
let nodes: Vec<NodeId> = vec![[0x33; 32]];
assert_ne!(
cemented_bundle_aggregate(100, &nodes),
cemented_bundle_aggregate(101, &nodes)
);
}
#[test]
fn cba_changes_on_node_ids() {
let nodes_a: Vec<NodeId> = vec![[0x11; 32]];
let nodes_b: Vec<NodeId> = vec![[0x22; 32]];
assert_ne!(
cemented_bundle_aggregate(100, &nodes_a),
cemented_bundle_aggregate(100, &nodes_b)
);
}
#[test]
fn cba_deterministic() {
let nodes: Vec<NodeId> = vec![[0x11; 32], [0x22; 32], [0x33; 32]];
let a = cemented_bundle_aggregate(500, &nodes);
let b = cemented_bundle_aggregate(500, &nodes);
assert_eq!(a, b);
}
#[test]
fn cba_canonical_sort_independence_от_порядкахода() {
// Spec: sorted by node_id asc — input order не должен влиять.
let nodes_forward: Vec<NodeId> = vec![[0x11; 32], [0x22; 32], [0x33; 32]];
let nodes_reverse: Vec<NodeId> = vec![[0x33; 32], [0x22; 32], [0x11; 32]];
assert_eq!(
cemented_bundle_aggregate(500, &nodes_forward),
cemented_bundle_aggregate(500, &nodes_reverse),
"cemented_bundle_aggregate input order должен быть нерелевантен (canonical sort)"
);
}
#[test]
fn cba_empty_vs_nonempty_distinct() {
let with_nodes: Vec<NodeId> = vec![[0x11; 32]];
assert_ne!(
cemented_bundle_aggregate(100, &[]),
cemented_bundle_aggregate(100, &with_nodes),
"empty cemented set должен давать разный CBA от non-empty"
);
}