2019-03-03 04:07:54 +00:00
|
|
|
//! Defines execution and testing specs for a `BeaconChainHarness` instance. Supports loading from
|
|
|
|
//! a YAML file.
|
|
|
|
|
2019-03-02 07:59:47 +00:00
|
|
|
use crate::beacon_chain_harness::BeaconChainHarness;
|
|
|
|
use beacon_chain::CheckPoint;
|
|
|
|
use log::{info, warn};
|
2019-03-04 01:21:24 +00:00
|
|
|
use ssz::TreeHash;
|
2019-03-02 07:59:47 +00:00
|
|
|
use types::*;
|
|
|
|
use types::{
|
|
|
|
attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder,
|
|
|
|
};
|
|
|
|
use yaml_rust::Yaml;
|
|
|
|
|
|
|
|
mod config;
|
|
|
|
mod results;
|
2019-03-02 09:17:14 +00:00
|
|
|
mod state_check;
|
2019-03-02 07:59:47 +00:00
|
|
|
mod yaml_helpers;
|
|
|
|
|
2019-03-03 04:07:54 +00:00
|
|
|
pub use config::Config;
|
|
|
|
pub use results::Results;
|
|
|
|
pub use state_check::StateCheck;
|
|
|
|
|
|
|
|
/// Defines the execution and testing of a `BeaconChainHarness` instantiation.
|
|
|
|
///
|
|
|
|
/// Typical workflow is:
|
|
|
|
///
|
2019-03-03 04:12:19 +00:00
|
|
|
/// 1. Instantiate the `TestCase` from YAML: `let test_case = TestCase::from_yaml(&my_yaml);`
|
|
|
|
/// 2. Execute the test_case: `let result = test_case.execute();`
|
|
|
|
/// 3. Test the results against the test_case: `test_case.assert_result_valid(result);`
|
2019-03-02 09:17:14 +00:00
|
|
|
#[derive(Debug)]
|
2019-03-03 04:12:19 +00:00
|
|
|
pub struct TestCase {
|
2019-03-03 04:07:54 +00:00
|
|
|
/// Defines the execution.
|
2019-03-02 07:59:47 +00:00
|
|
|
pub config: Config,
|
2019-03-03 04:07:54 +00:00
|
|
|
/// Defines tests to run against the execution result.
|
|
|
|
pub results: Results,
|
2019-03-02 07:59:47 +00:00
|
|
|
}
|
|
|
|
|
2019-03-03 04:12:19 +00:00
|
|
|
/// The result of executing a `TestCase`.
|
2019-03-03 04:07:54 +00:00
|
|
|
///
|
2019-03-02 07:59:47 +00:00
|
|
|
pub struct ExecutionResult {
|
2019-03-03 04:07:54 +00:00
|
|
|
/// The canonical beacon chain generated from the execution.
|
2019-03-02 07:59:47 +00:00
|
|
|
pub chain: Vec<CheckPoint>,
|
2019-03-03 04:07:54 +00:00
|
|
|
/// The spec used for execution.
|
2019-03-02 09:17:14 +00:00
|
|
|
pub spec: ChainSpec,
|
2019-03-02 07:59:47 +00:00
|
|
|
}
|
|
|
|
|
2019-03-03 04:12:19 +00:00
|
|
|
impl TestCase {
|
|
|
|
/// Load the test case from a YAML document.
|
2019-03-02 07:59:47 +00:00
|
|
|
pub fn from_yaml(test_case: &Yaml) -> Self {
|
|
|
|
Self {
|
|
|
|
results: Results::from_yaml(&test_case["results"]),
|
|
|
|
config: Config::from_yaml(&test_case["config"]),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-03 04:07:54 +00:00
|
|
|
/// Return a `ChainSpec::foundation()`.
|
|
|
|
///
|
2019-03-04 06:51:54 +00:00
|
|
|
/// If specified in `config`, returns it with a modified `slots_per_epoch`.
|
2019-03-02 07:59:47 +00:00
|
|
|
fn spec(&self) -> ChainSpec {
|
|
|
|
let mut spec = ChainSpec::foundation();
|
|
|
|
|
2019-03-04 06:51:54 +00:00
|
|
|
if let Some(n) = self.config.slots_per_epoch {
|
|
|
|
spec.slots_per_epoch = n;
|
2019-03-02 07:59:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
spec
|
|
|
|
}
|
|
|
|
|
2019-03-03 04:12:19 +00:00
|
|
|
/// Executes the test case, returning an `ExecutionResult`.
|
2019-03-02 07:59:47 +00:00
|
|
|
pub fn execute(&self) -> ExecutionResult {
|
|
|
|
let spec = self.spec();
|
|
|
|
let validator_count = self.config.deposits_for_chain_start;
|
|
|
|
let slots = self.config.num_slots;
|
|
|
|
|
|
|
|
info!(
|
|
|
|
"Building BeaconChainHarness with {} validators...",
|
|
|
|
validator_count
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut harness = BeaconChainHarness::new(spec, validator_count);
|
|
|
|
|
|
|
|
info!("Starting simulation across {} slots...", slots);
|
|
|
|
|
2019-03-02 09:17:14 +00:00
|
|
|
// -1 slots because genesis counts as a slot.
|
|
|
|
for slot_height in 0..slots - 1 {
|
2019-03-02 07:59:47 +00:00
|
|
|
// Feed deposits to the BeaconChain.
|
|
|
|
if let Some(ref deposits) = self.config.deposits {
|
|
|
|
for (slot, deposit, keypair) in deposits {
|
|
|
|
if *slot == slot_height {
|
|
|
|
info!("Including deposit at slot height {}.", slot_height);
|
|
|
|
harness.add_deposit(deposit.clone(), Some(keypair.clone()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Feed proposer slashings to the BeaconChain.
|
|
|
|
if let Some(ref slashings) = self.config.proposer_slashings {
|
|
|
|
for (slot, validator_index) in slashings {
|
|
|
|
if *slot == slot_height {
|
|
|
|
info!(
|
|
|
|
"Including proposer slashing at slot height {} for validator #{}.",
|
|
|
|
slot_height, validator_index
|
|
|
|
);
|
|
|
|
let slashing = build_proposer_slashing(&harness, *validator_index);
|
|
|
|
harness.add_proposer_slashing(slashing);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Feed attester slashings to the BeaconChain.
|
|
|
|
if let Some(ref slashings) = self.config.attester_slashings {
|
|
|
|
for (slot, validator_indices) in slashings {
|
|
|
|
if *slot == slot_height {
|
|
|
|
info!(
|
|
|
|
"Including attester slashing at slot height {} for validators {:?}.",
|
|
|
|
slot_height, validator_indices
|
|
|
|
);
|
|
|
|
let slashing =
|
|
|
|
build_double_vote_attester_slashing(&harness, &validator_indices[..]);
|
|
|
|
harness.add_attester_slashing(slashing);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 01:21:24 +00:00
|
|
|
// Feed exits to the BeaconChain.
|
|
|
|
if let Some(ref exits) = self.config.exits {
|
|
|
|
for (slot, validator_index) in exits {
|
|
|
|
if *slot == slot_height {
|
|
|
|
info!(
|
|
|
|
"Including exit at slot height {} for validator {}.",
|
|
|
|
slot_height, validator_index
|
|
|
|
);
|
|
|
|
let exit = build_exit(&harness, *validator_index);
|
|
|
|
harness.add_exit(exit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-02 07:59:47 +00:00
|
|
|
// Build a block or skip a slot.
|
|
|
|
match self.config.skip_slots {
|
|
|
|
Some(ref skip_slots) if skip_slots.contains(&slot_height) => {
|
|
|
|
warn!("Skipping slot at height {}.", slot_height);
|
|
|
|
harness.increment_beacon_chain_slot();
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
info!("Producing block at slot height {}.", slot_height);
|
|
|
|
harness.advance_chain_with_block();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
harness.run_fork_choice();
|
|
|
|
|
|
|
|
info!("Test execution complete!");
|
|
|
|
|
2019-03-02 09:20:06 +00:00
|
|
|
info!("Building chain dump for analysis...");
|
|
|
|
|
2019-03-02 07:59:47 +00:00
|
|
|
ExecutionResult {
|
|
|
|
chain: harness.chain_dump().expect("Chain dump failed."),
|
2019-03-02 09:17:14 +00:00
|
|
|
spec: (*harness.spec).clone(),
|
2019-03-02 07:59:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-03 04:07:54 +00:00
|
|
|
/// Checks that the `ExecutionResult` is consistent with the specifications in `self.results`.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics with a message if any result does not match exepectations.
|
2019-03-02 09:17:14 +00:00
|
|
|
pub fn assert_result_valid(&self, execution_result: ExecutionResult) {
|
2019-03-02 07:59:47 +00:00
|
|
|
info!("Verifying test results...");
|
2019-03-02 09:17:14 +00:00
|
|
|
let spec = &execution_result.spec;
|
2019-03-02 07:59:47 +00:00
|
|
|
|
2019-03-02 09:17:14 +00:00
|
|
|
if let Some(num_skipped_slots) = self.results.num_skipped_slots {
|
2019-03-02 07:59:47 +00:00
|
|
|
assert_eq!(
|
2019-03-02 09:17:14 +00:00
|
|
|
execution_result.chain.len(),
|
|
|
|
self.config.num_slots as usize - num_skipped_slots,
|
|
|
|
"actual skipped slots != expected."
|
2019-03-02 07:59:47 +00:00
|
|
|
);
|
|
|
|
info!(
|
2019-03-02 09:17:14 +00:00
|
|
|
"OK: Chain length is {} ({} skipped slots).",
|
|
|
|
execution_result.chain.len(),
|
|
|
|
num_skipped_slots
|
2019-03-02 07:59:47 +00:00
|
|
|
);
|
|
|
|
}
|
2019-03-02 09:17:14 +00:00
|
|
|
|
|
|
|
if let Some(ref state_checks) = self.results.state_checks {
|
|
|
|
for checkpoint in &execution_result.chain {
|
|
|
|
let state = &checkpoint.beacon_state;
|
|
|
|
|
|
|
|
for state_check in state_checks {
|
|
|
|
let adjusted_state_slot =
|
2019-03-04 06:51:54 +00:00
|
|
|
state.slot - spec.genesis_epoch.start_slot(spec.slots_per_epoch);
|
2019-03-02 09:17:14 +00:00
|
|
|
|
|
|
|
if state_check.slot == adjusted_state_slot {
|
|
|
|
state_check.assert_valid(state, spec);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-02 07:59:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 01:21:24 +00:00
|
|
|
fn build_exit(harness: &BeaconChainHarness, validator_index: u64) -> Exit {
|
|
|
|
let epoch = harness
|
|
|
|
.beacon_chain
|
|
|
|
.state
|
|
|
|
.read()
|
|
|
|
.current_epoch(&harness.spec);
|
|
|
|
|
|
|
|
let mut exit = Exit {
|
|
|
|
epoch,
|
|
|
|
validator_index,
|
|
|
|
signature: Signature::empty_signature(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let message = exit.hash_tree_root();
|
|
|
|
|
|
|
|
exit.signature = harness
|
|
|
|
.validator_sign(
|
|
|
|
validator_index as usize,
|
|
|
|
&message[..],
|
|
|
|
epoch,
|
|
|
|
harness.spec.domain_exit,
|
|
|
|
)
|
|
|
|
.expect("Unable to sign Exit");
|
|
|
|
|
|
|
|
exit
|
|
|
|
}
|
|
|
|
|
2019-03-03 04:07:54 +00:00
|
|
|
/// Builds an `AttesterSlashing` for some `validator_indices`.
|
|
|
|
///
|
|
|
|
/// Signs the message using a `BeaconChainHarness`.
|
2019-03-02 07:59:47 +00:00
|
|
|
fn build_double_vote_attester_slashing(
|
|
|
|
harness: &BeaconChainHarness,
|
|
|
|
validator_indices: &[u64],
|
|
|
|
) -> AttesterSlashing {
|
|
|
|
let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: u64| {
|
|
|
|
harness
|
|
|
|
.validator_sign(validator_index as usize, message, epoch, domain)
|
|
|
|
.expect("Unable to sign AttesterSlashing")
|
|
|
|
};
|
|
|
|
|
|
|
|
AttesterSlashingBuilder::double_vote(validator_indices, signer, &harness.spec)
|
|
|
|
}
|
|
|
|
|
2019-03-03 04:07:54 +00:00
|
|
|
/// Builds an `ProposerSlashing` for some `validator_index`.
|
|
|
|
///
|
|
|
|
/// Signs the message using a `BeaconChainHarness`.
|
2019-03-02 07:59:47 +00:00
|
|
|
fn build_proposer_slashing(harness: &BeaconChainHarness, validator_index: u64) -> ProposerSlashing {
|
|
|
|
let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: u64| {
|
|
|
|
harness
|
|
|
|
.validator_sign(validator_index as usize, message, epoch, domain)
|
|
|
|
.expect("Unable to sign AttesterSlashing")
|
|
|
|
};
|
|
|
|
|
|
|
|
ProposerSlashingBuilder::double_vote(validator_index, signer, &harness.spec)
|
|
|
|
}
|