#![cfg(any(feature = "mdbx", feature = "lmdb", feature = "redb"))] use rand::{prelude::*, rng}; use slasher::{ Config, Slasher, SlasherDB, test_utils::{ E, block, chain_spec, indexed_att, slashed_validators_from_attestations, slashed_validators_from_slashings, }, }; use std::cmp::max; use std::sync::Arc; use tempfile::{TempDir, tempdir}; use types::{Epoch, EthSpec}; #[derive(Debug)] struct TestConfig { num_validators: usize, max_attestations: usize, check_slashings: bool, add_blocks: bool, } impl Default for TestConfig { fn default() -> Self { Self { num_validators: 4, max_attestations: 50, check_slashings: false, add_blocks: false, } } } fn make_db() -> (TempDir, SlasherDB) { let tempdir = tempdir().unwrap(); let initial_config = Arc::new(Config::new(tempdir.path().into())); let spec = chain_spec(); let db = SlasherDB::open(initial_config.clone(), spec).unwrap(); (tempdir, db) } fn random_test(seed: u64, mut db: SlasherDB, test_config: TestConfig) -> SlasherDB { let check_slashings = test_config.check_slashings; let num_validators = test_config.num_validators; let max_attestations = test_config.max_attestations; println!("Running with seed {}", seed); let mut rng = StdRng::seed_from_u64(seed); let mut config = Config::new(db.get_config().database_path.clone()); config.validator_chunk_size = 1 << rng.random_range(1..4); let chunk_size_exponent = rng.random_range(1..4); config.chunk_size = 1 << chunk_size_exponent; config.history_length = 1 << rng.random_range(chunk_size_exponent..chunk_size_exponent + 3); let config = Arc::new(config); db.update_config(config.clone()); let slasher = Slasher::::from_config_and_db(config.clone(), db).unwrap(); let validators = (0..num_validators as u64).collect::>(); let num_attestations = rng.random_range(2..max_attestations + 1); let mut current_epoch = Epoch::new(0); let mut attestations = vec![]; for _ in 0..num_attestations { let num_attesters = rng.random_range(1..num_validators); let mut attesting_indices = validators .choose_multiple(&mut rng, num_attesters) .copied() .collect::>(); attesting_indices.sort_unstable(); // If checking slashings, generate valid attestations in range. let (source, target) = if check_slashings { let source = rng.random_range( current_epoch .as_u64() .saturating_sub(config.history_length as u64 - 1) ..current_epoch.as_u64() + 1, ); let target = rng.random_range(source..current_epoch.as_u64() + 1); (source, target) } else { let source = rng.random_range(0..max(3 * current_epoch.as_u64(), 1)); let target = rng.random_range(source..max(3 * current_epoch.as_u64(), source + 1)); (source, target) }; let target_root = rng.random_range(0..3); let attestation = indexed_att(&attesting_indices, source, target, target_root); if check_slashings { attestations.push(attestation.clone()); } // Supply to slasher slasher.accept_attestation(attestation); // Maybe add a random block too if test_config.add_blocks && rng.random_bool(0.1) { let slot = rng.random_range(0..1 + 3 * current_epoch.as_u64() * E::slots_per_epoch() / 2); let proposer = rng.random_range(0..num_validators as u64); let block_root = rng.random_range(0..2); slasher.accept_block_header(block(slot, proposer, block_root)); } // Maybe process if rng.random_bool(0.1) { slasher.process_queued(current_epoch).unwrap(); // Maybe prune if rng.random_bool(0.1) { slasher.prune_database(current_epoch).unwrap(); } } // Maybe advance to the next epoch if rng.random_bool(0.5) { if check_slashings { slasher.process_queued(current_epoch).unwrap(); } current_epoch += 1; } } if !check_slashings { return slasher.into_reset_db().unwrap(); } slasher.process_queued(current_epoch).unwrap(); let slashings = slasher.get_attester_slashings(); let slashed_validators = slashed_validators_from_slashings(&slashings); let expected_slashed_validators = slashed_validators_from_attestations(&attestations); assert_eq!(slashed_validators, expected_slashed_validators); // Return the database for reuse. slasher.into_reset_db().unwrap() } // Fuzz-like test that runs forever on different seeds looking for crashes. #[test] #[ignore] fn no_crash() { let mut rng = rng(); let (_tempdir, mut db) = make_db(); loop { db = random_test(rng.random(), db, TestConfig::default()); } } // Fuzz-like test that runs forever on different seeds looking for crashes. #[test] #[ignore] fn no_crash_with_blocks() { let mut rng = rng(); let (_tempdir, mut db) = make_db(); loop { db = random_test( rng.random(), db, TestConfig { add_blocks: true, ..TestConfig::default() }, ); } } // Fuzz-like test that runs forever on different seeds looking for missed slashings. #[test] #[ignore] fn check_slashings() { let mut rng = rng(); let (_tempdir, mut db) = make_db(); loop { db = random_test( rng.random(), db, TestConfig { check_slashings: true, ..TestConfig::default() }, ); } } #[test] fn check_slashings_example1() { let (_tempdir, db) = make_db(); random_test( 1, db, TestConfig { check_slashings: true, ..TestConfig::default() }, ); } #[test] fn check_slashings_example2() { let (_tempdir, db) = make_db(); random_test( 2, db, TestConfig { check_slashings: true, max_attestations: 3, ..TestConfig::default() }, ); } #[test] fn check_slashings_example3() { let (_tempdir, db) = make_db(); random_test( 3, db, TestConfig { check_slashings: true, max_attestations: 100, ..TestConfig::default() }, ); } #[test] fn no_crash_example1() { let (_tempdir, db) = make_db(); random_test(1, db, TestConfig::default()); } #[test] fn no_crash_example2() { let (_tempdir, db) = make_db(); random_test(2, db, TestConfig::default()); } #[test] fn no_crash_example3() { let (_tempdir, db) = make_db(); random_test(3, db, TestConfig::default()); } #[test] fn no_crash_blocks_example1() { let (_tempdir, db) = make_db(); random_test( 1, db, TestConfig { add_blocks: true, ..TestConfig::default() }, ); } #[test] fn no_crash_aug_24() { let (_tempdir, db) = make_db(); random_test(13519442335106054152, db, TestConfig::default()); }