use serde_derive::Deserialize; use serde_yaml; use state_processing::{per_block_processing, per_slot_processing}; use std::{fs::File, io::prelude::*, path::PathBuf}; use types::*; #[derive(Debug, Deserialize)] pub struct ExpectedState { pub slot: Option, pub genesis_time: Option, pub fork: Option, pub validator_registry: Option>, pub validator_balances: Option>, pub previous_epoch_attestations: Option>, pub current_epoch_attestations: Option>, pub historical_roots: Option>, pub finalized_epoch: Option, pub latest_block_roots: Option>, } impl ExpectedState { // Return a list of fields that differ, and a string representation of the beacon state's field. fn check(&self, state: &BeaconState) -> Vec<(&str, String)> { // Check field equality macro_rules! cfe { ($field_name:ident) => { if self.$field_name.as_ref().map_or(true, |$field_name| { println!(" > Checking {}", stringify!($field_name)); &state.$field_name == $field_name }) { vec![] } else { vec![(stringify!($field_name), format!("{:#?}", state.$field_name))] } }; } vec![ cfe!(slot), cfe!(genesis_time), cfe!(fork), cfe!(validator_registry), cfe!(validator_balances), cfe!(previous_epoch_attestations), cfe!(current_epoch_attestations), cfe!(historical_roots), cfe!(finalized_epoch), cfe!(latest_block_roots), ] .into_iter() .flat_map(|x| x) .collect() } } #[derive(Debug, Deserialize)] pub struct TestCase { pub name: String, pub config: ChainSpec, pub verify_signatures: bool, pub initial_state: BeaconState, pub blocks: Vec, pub expected_state: ExpectedState, } #[derive(Debug, Deserialize)] pub struct TestDoc { pub title: String, pub summary: String, pub fork: String, pub test_cases: Vec, } fn load_test_case(test_name: &str) -> TestDoc { let mut file = { let mut file_path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); file_path_buf.push(format!("yaml_utils/specs/{}", test_name)); File::open(file_path_buf).unwrap() }; let mut yaml_str = String::new(); file.read_to_string(&mut yaml_str).unwrap(); yaml_str = yaml_str.to_lowercase(); serde_yaml::from_str(&yaml_str.as_str()).unwrap() } fn run_state_transition_test(test_name: &str) { let doc = load_test_case(test_name); // Run Tests let mut ok = true; for (i, test_case) in doc.test_cases.iter().enumerate() { let fake_crypto = cfg!(feature = "fake_crypto"); if !test_case.verify_signatures == fake_crypto { println!("Running {}", test_case.name); } else { println!( "Skipping {} (fake_crypto: {}, need fake: {})", test_case.name, fake_crypto, !test_case.verify_signatures ); continue; } let mut state = test_case.initial_state.clone(); for (j, block) in test_case.blocks.iter().enumerate() { while block.slot > state.slot { per_slot_processing(&mut state, &test_case.config).unwrap(); } let res = per_block_processing(&mut state, &block, &test_case.config); if res.is_err() { println!("Error in {} (#{}), on block {}", test_case.name, i, j); println!("{:?}", res); ok = false; } } let mismatched_fields = test_case.expected_state.check(&state); if !mismatched_fields.is_empty() { println!( "Error in expected state, these fields didn't match: {:?}", mismatched_fields.iter().map(|(f, _)| f).collect::>() ); for (field_name, state_val) in mismatched_fields { println!("state.{} was: {}", field_name, state_val); } ok = false; } } assert!(ok, "one or more tests failed, see above"); } #[test] #[cfg(not(debug_assertions))] fn test_read_yaml() { load_test_case("sanity-check_small-config_32-vals.yaml"); load_test_case("sanity-check_default-config_100-vals.yaml"); } #[test] #[cfg(not(debug_assertions))] fn run_state_transition_tests_small() { run_state_transition_test("sanity-check_small-config_32-vals.yaml"); } // Run with --ignored to run this test #[test] #[ignore] fn run_state_transition_tests_large() { run_state_transition_test("sanity-check_default-config_100-vals.yaml"); }