iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

commit b495a6c1528259f59c847c09f8ce8e3234a44843
parent 6b86727bfc91fd2af9fd3cd1c21f15d97515ef52
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Thu,  3 Sep 2026 23:58:41 +0200

Parallelize adversarial release tests

Diffstat:
Mdeployment.sh | 2+-
Msrc/domain/adversarial_tests.rs | 262++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mtests/properties.rs | 2++
3 files changed, 175 insertions(+), 91 deletions(-)

diff --git a/deployment.sh b/deployment.sh @@ -145,7 +145,7 @@ run_release_tests() { validate_positive_integer IUNA_FUZZ_RUNS "$fuzz_runs" validate_positive_integer IUNA_VDF_FUZZ_RUNS "$vdf_fuzz_runs" - cargo test --locked domain::adversarial_tests:: -- --ignored + cargo test --locked --release --lib domain::adversarial_tests:: -- --ignored cargo run --locked --manifest-path fuzz/Cargo.toml --bin p2p_envelope -- -runs="$fuzz_runs" fuzz/corpus/p2p_envelope cargo run --locked --manifest-path fuzz/Cargo.toml --bin compact_snapshot -- -runs="$fuzz_runs" fuzz/corpus/compact_snapshot cargo run --locked --manifest-path fuzz/Cargo.toml --bin domain_json -- -runs="$fuzz_runs" fuzz/corpus/domain_json diff --git a/src/domain/adversarial_tests.rs b/src/domain/adversarial_tests.rs @@ -1,7 +1,7 @@ use std::collections::{BTreeMap, BTreeSet}; use proptest::prelude::*; -use proptest::test_runner::Config; +use proptest::test_runner::{Config, TestCaseResult}; use sha2::{Digest, Sha256}; use super::ledger_ops::{block_reward, verify_address_signature}; @@ -2294,73 +2294,134 @@ proptest! { ); } - #[test] - #[ignore = "long-running adversarial proptest; run via deployment.sh"] - fn committee_selection_is_deterministic_and_sybil_resistant(seed in any::<u64>(), lineage_idx in 0usize..LEVELS.len()) { - let mut harness = Harness::new(seed, 10, LEVELS[lineage_idx], AdversaryStrategy::AddressRotation); - harness.mature_lineages(2, 4); - let committee = harness.ledger.burn_committee_for_next_block(); - let mini_committee = mini_burn_committee_for_next_block(&harness.ledger) - .expect("mini committee oracle should replay generated lineage"); - let snapshot_committee = Ledger::from_snapshot_at(harness.ledger.snapshot(), NOW_MS) - .unwrap() - .burn_committee_for_next_block(); +} - prop_assert_eq!(committee.clone(), snapshot_committee, "seed={} committee selection is not deterministic", seed); - prop_assert_eq!(committee.clone(), mini_committee, "seed={} mini committee oracle diverged", seed); - prop_assert!(committee_roots_are_unique(&committee), "seed={} selected one lineage more than once: {:?}", seed, committee); - let non_finalizer_roots = committee.iter().filter(|member| member.slot > 0).count(); - prop_assert!(non_finalizer_roots < super::BURN_COMMITTEE_SIZE); - } +fn check_committee_selection(seed: u64, lineage_idx: usize) -> TestCaseResult { + let mut harness = Harness::new( + seed, + 10, + LEVELS[lineage_idx], + AdversaryStrategy::AddressRotation, + ); + harness.mature_lineages(2, 4); + let committee = harness.ledger.burn_committee_for_next_block(); + let mini_committee = mini_burn_committee_for_next_block(&harness.ledger) + .expect("mini committee oracle should replay generated lineage"); + let snapshot_committee = Ledger::from_snapshot_at(harness.ledger.snapshot(), NOW_MS) + .unwrap() + .burn_committee_for_next_block(); - #[test] - #[ignore = "long-running adversarial state-machine proptest; run via deployment.sh"] - fn adversarial_state_machine_keeps_invalid_paths_out( - seed in any::<u64>(), - strategy_idx in 0usize..10, - level_idx in 0usize..LEVELS.len(), - blocks in 3usize..8, - ) { - let strategy = AdversaryStrategy::from_index(strategy_idx); - let level = LEVELS[level_idx]; - let mut harness = Harness::new(seed, level, level, strategy); - let metrics = harness.run_strategy(blocks); - prop_assert!(metrics.attacker_finalization_share.is_finite()); - prop_assert!(metrics.attacker_committee_share.is_finite()); - match strategy { - AdversaryStrategy::Honest - | AdversaryStrategy::MaximizeBurnWeight - | AdversaryStrategy::MaximizeCommitteeWeight - | AdversaryStrategy::AddressRotation => { - prop_assert_eq!(metrics.third_party_burn_censorship_rate, 0.0); - prop_assert_eq!(metrics.fallback_rate, 0.0); - prop_assert_eq!(metrics.recovery_rate, 0.0); - } - AdversaryStrategy::CensorBurns => { - prop_assert_eq!(metrics.third_party_burn_censorship_rate, 0.0); - } - AdversaryStrategy::WithholdBurnFromCommittee => { - prop_assert!(metrics.third_party_burn_censorship_rate > 0.0); - } - AdversaryStrategy::MissRank0 | AdversaryStrategy::ForceFallback => { - prop_assert_eq!(metrics.fallback_blocks, metrics.fallback_opportunities); + prop_assert_eq!( + committee.clone(), + snapshot_committee, + "seed={} committee selection is not deterministic", + seed + ); + prop_assert_eq!( + committee.clone(), + mini_committee, + "seed={} mini committee oracle diverged", + seed + ); + prop_assert!( + committee_roots_are_unique(&committee), + "seed={} selected one lineage more than once: {:?}", + seed, + committee + ); + let non_finalizer_roots = committee.iter().filter(|member| member.slot > 0).count(); + prop_assert!(non_finalizer_roots < super::BURN_COMMITTEE_SIZE); + Ok(()) +} + +fn check_adversarial_state_machine( + seed: u64, + strategy_idx: usize, + level_idx: usize, + blocks: usize, +) -> TestCaseResult { + let strategy = AdversaryStrategy::from_index(strategy_idx); + let level = LEVELS[level_idx]; + let mut harness = Harness::new(seed, level, level, strategy); + let metrics = harness.run_strategy(blocks); + prop_assert!(metrics.attacker_finalization_share.is_finite()); + prop_assert!(metrics.attacker_committee_share.is_finite()); + match strategy { + AdversaryStrategy::Honest + | AdversaryStrategy::MaximizeBurnWeight + | AdversaryStrategy::MaximizeCommitteeWeight + | AdversaryStrategy::AddressRotation => { + prop_assert_eq!(metrics.third_party_burn_censorship_rate, 0.0); + prop_assert_eq!(metrics.fallback_rate, 0.0); + prop_assert_eq!(metrics.recovery_rate, 0.0); + } + AdversaryStrategy::CensorBurns => { + prop_assert_eq!(metrics.third_party_burn_censorship_rate, 0.0); + } + AdversaryStrategy::WithholdBurnFromCommittee => { + prop_assert!(metrics.third_party_burn_censorship_rate > 0.0); + } + AdversaryStrategy::MissRank0 | AdversaryStrategy::ForceFallback => { + prop_assert_eq!(metrics.fallback_blocks, metrics.fallback_opportunities); + prop_assert_eq!(metrics.recovery_rate, 0.0); + } + AdversaryStrategy::AttemptRecovery => { + if blocks >= 7 { + prop_assert!(metrics.recovery_rate > 0.0); + } else { prop_assert_eq!(metrics.recovery_rate, 0.0); } - AdversaryStrategy::AttemptRecovery => { - if blocks >= 7 { - prop_assert!(metrics.recovery_rate > 0.0); - } else { - prop_assert_eq!(metrics.recovery_rate, 0.0); - } + } + AdversaryStrategy::CombinedStrategy => { + prop_assert!(metrics.third_party_burn_censorship_rate > 0.0); + prop_assert_eq!(metrics.fallback_blocks, metrics.fallback_opportunities); + } + } + Ok(()) +} + +macro_rules! adversarial_proptest_shard { + ($committee_name:ident, $state_machine_name:ident) => { + proptest! { + #![proptest_config(Config { cases: 8, .. Config::default() })] + + #[test] + #[ignore = "long-running adversarial proptest; run via deployment.sh"] + fn $committee_name(seed in any::<u64>(), lineage_idx in 0usize..LEVELS.len()) { + check_committee_selection(seed, lineage_idx)?; } - AdversaryStrategy::CombinedStrategy => { - prop_assert!(metrics.third_party_burn_censorship_rate > 0.0); - prop_assert_eq!(metrics.fallback_blocks, metrics.fallback_opportunities); + + #[test] + #[ignore = "long-running adversarial state-machine proptest; run via deployment.sh"] + fn $state_machine_name( + seed in any::<u64>(), + strategy_idx in 0usize..10, + level_idx in 0usize..LEVELS.len(), + blocks in 3usize..8, + ) { + check_adversarial_state_machine(seed, strategy_idx, level_idx, blocks)?; } } - } + }; } +adversarial_proptest_shard!( + committee_selection_is_deterministic_and_sybil_resistant_shard_1, + adversarial_state_machine_keeps_invalid_paths_out_shard_1 +); +adversarial_proptest_shard!( + committee_selection_is_deterministic_and_sybil_resistant_shard_2, + adversarial_state_machine_keeps_invalid_paths_out_shard_2 +); +adversarial_proptest_shard!( + committee_selection_is_deterministic_and_sybil_resistant_shard_3, + adversarial_state_machine_keeps_invalid_paths_out_shard_3 +); +adversarial_proptest_shard!( + committee_selection_is_deterministic_and_sybil_resistant_shard_4, + adversarial_state_machine_keeps_invalid_paths_out_shard_4 +); + #[test] fn attested_burn_is_not_selected_again_as_normal_transaction() { let mut harness = harness_for_percent(10, 25); @@ -3998,41 +4059,62 @@ fn adversarial_scenarios_cover_resource_matrix() { AdversaryStrategy::Honest | AdversaryStrategy::MissRank0 => unreachable!(), } } +} - for burn in LEVELS { - for lineage in LEVELS { - let mut harness = Harness::new( - 200 + u64::from(burn) * 10 + u64::from(lineage), - burn, - lineage, - AdversaryStrategy::Honest, - ); - let metrics = harness.run_strategy(3); - assert!( - metrics.attacker_finalization_share.is_finite() - && metrics.attacker_committee_share.is_finite(), - "burn={burn}% lineage={lineage}% configured={:?} metrics={metrics:?}", - ( - harness.resource.burn_percent, - harness.resource.lineage_percent - ), - ); - assert_eq!( - metrics.third_party_burn_censorship_rate, 0.0, - "honest resource matrix run censored third-party burns: {metrics:?}" - ); - assert_eq!( - metrics.fallback_rate, 0.0, - "honest resource matrix run produced fallback blocks: {metrics:?}" - ); - assert_eq!( - metrics.recovery_rate, 0.0, - "honest resource matrix run produced recovery blocks: {metrics:?}" - ); - } +fn assert_honest_resource_matrix_for_burn(burn: u8) { + for lineage in LEVELS { + let mut harness = Harness::new( + 200 + u64::from(burn) * 10 + u64::from(lineage), + burn, + lineage, + AdversaryStrategy::Honest, + ); + let metrics = harness.run_strategy(3); + assert!( + metrics.attacker_finalization_share.is_finite() + && metrics.attacker_committee_share.is_finite(), + "burn={burn}% lineage={lineage}% configured={:?} metrics={metrics:?}", + ( + harness.resource.burn_percent, + harness.resource.lineage_percent + ), + ); + assert_eq!( + metrics.third_party_burn_censorship_rate, 0.0, + "honest resource matrix run censored third-party burns: {metrics:?}" + ); + assert_eq!( + metrics.fallback_rate, 0.0, + "honest resource matrix run produced fallback blocks: {metrics:?}" + ); + assert_eq!( + metrics.recovery_rate, 0.0, + "honest resource matrix run produced recovery blocks: {metrics:?}" + ); } } +macro_rules! honest_resource_matrix_tests { + ($($name:ident: $burn:literal),+ $(,)?) => { + $( + #[test] + #[ignore = "long-running adversarial resource matrix; run via deployment.sh"] + fn $name() { + assert_honest_resource_matrix_for_burn($burn); + } + )+ + }; +} + +honest_resource_matrix_tests! { + adversarial_honest_resource_matrix_burn_1: 1, + adversarial_honest_resource_matrix_burn_5: 5, + adversarial_honest_resource_matrix_burn_10: 10, + adversarial_honest_resource_matrix_burn_25: 25, + adversarial_honest_resource_matrix_burn_33: 33, + adversarial_honest_resource_matrix_burn_50: 50, +} + #[test] #[ignore = "long-running economic attack sweep; run via deployment.sh"] fn attack_economics_visible_burns_have_no_finite_censorship_price() { diff --git a/tests/properties.rs b/tests/properties.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "e2e")] + use std::{ net::{Ipv4Addr, SocketAddr, TcpListener as StdTcpListener}, path::Path,