mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-21 13:54:44 +00:00
Merge branch 'master' into v0.4.0-types
This commit is contained in:
@@ -3,9 +3,13 @@ use bls::create_proof_of_possession;
|
||||
use types::*;
|
||||
use yaml_rust::Yaml;
|
||||
|
||||
pub type DepositTuple = (u64, Deposit, Keypair);
|
||||
pub type ProposerSlashingTuple = (u64, u64);
|
||||
pub type AttesterSlashingTuple = (u64, Vec<u64>);
|
||||
pub type ValidatorIndex = u64;
|
||||
pub type ValidatorIndices = Vec<u64>;
|
||||
|
||||
pub type DepositTuple = (SlotHeight, Deposit, Keypair);
|
||||
pub type ExitTuple = (SlotHeight, ValidatorIndex);
|
||||
pub type ProposerSlashingTuple = (SlotHeight, ValidatorIndex);
|
||||
pub type AttesterSlashingTuple = (SlotHeight, ValidatorIndices);
|
||||
|
||||
/// Defines the execution of a `BeaconStateHarness` across a series of slots.
|
||||
#[derive(Debug)]
|
||||
@@ -24,6 +28,8 @@ pub struct Config {
|
||||
pub proposer_slashings: Option<Vec<ProposerSlashingTuple>>,
|
||||
/// Attester slashings to be including during execution.
|
||||
pub attester_slashings: Option<Vec<AttesterSlashingTuple>>,
|
||||
/// Exits to be including during execution.
|
||||
pub exits: Option<Vec<ExitTuple>>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -40,10 +46,26 @@ impl Config {
|
||||
deposits: parse_deposits(&yaml),
|
||||
proposer_slashings: parse_proposer_slashings(&yaml),
|
||||
attester_slashings: parse_attester_slashings(&yaml),
|
||||
exits: parse_exits(&yaml),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the `attester_slashings` section of the YAML document.
|
||||
fn parse_exits(yaml: &Yaml) -> Option<Vec<ExitTuple>> {
|
||||
let mut tuples = vec![];
|
||||
|
||||
for exit in yaml["exits"].as_vec()? {
|
||||
let slot = as_u64(exit, "slot").expect("Incomplete exit (slot)");
|
||||
let validator_index =
|
||||
as_u64(exit, "validator_index").expect("Incomplete exit (validator_index)");
|
||||
|
||||
tuples.push((SlotHeight::from(slot), validator_index));
|
||||
}
|
||||
|
||||
Some(tuples)
|
||||
}
|
||||
|
||||
/// Parse the `attester_slashings` section of the YAML document.
|
||||
fn parse_attester_slashings(yaml: &Yaml) -> Option<Vec<AttesterSlashingTuple>> {
|
||||
let mut slashings = vec![];
|
||||
@@ -53,7 +75,7 @@ fn parse_attester_slashings(yaml: &Yaml) -> Option<Vec<AttesterSlashingTuple>> {
|
||||
let validator_indices = as_vec_u64(slashing, "validator_indices")
|
||||
.expect("Incomplete attester_slashing (validator_indices)");
|
||||
|
||||
slashings.push((slot, validator_indices));
|
||||
slashings.push((SlotHeight::from(slot), validator_indices));
|
||||
}
|
||||
|
||||
Some(slashings)
|
||||
@@ -68,7 +90,7 @@ fn parse_proposer_slashings(yaml: &Yaml) -> Option<Vec<ProposerSlashingTuple>> {
|
||||
let validator_index = as_u64(slashing, "validator_index")
|
||||
.expect("Incomplete proposer slashing (validator_index)");
|
||||
|
||||
slashings.push((slot, validator_index));
|
||||
slashings.push((SlotHeight::from(slot), validator_index));
|
||||
}
|
||||
|
||||
Some(slashings)
|
||||
@@ -102,7 +124,7 @@ fn parse_deposits(yaml: &Yaml) -> Option<Vec<DepositTuple>> {
|
||||
},
|
||||
};
|
||||
|
||||
deposits.push((slot, deposit, keypair));
|
||||
deposits.push((SlotHeight::from(slot), deposit, keypair));
|
||||
}
|
||||
|
||||
Some(deposits)
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
//! Defines execution and testing specs for a `BeaconChainHarness` instance. Supports loading from
|
||||
//! a YAML file.
|
||||
|
||||
use crate::beacon_chain_harness::BeaconChainHarness;
|
||||
use beacon_chain::CheckPoint;
|
||||
use log::{info, warn};
|
||||
use types::*;
|
||||
use types::{
|
||||
attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder,
|
||||
};
|
||||
use yaml_rust::Yaml;
|
||||
|
||||
mod config;
|
||||
mod results;
|
||||
mod state_check;
|
||||
mod yaml_helpers;
|
||||
|
||||
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:
|
||||
///
|
||||
/// 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);`
|
||||
#[derive(Debug)]
|
||||
pub struct TestCase {
|
||||
/// Defines the execution.
|
||||
pub config: Config,
|
||||
/// Defines tests to run against the execution result.
|
||||
pub results: Results,
|
||||
}
|
||||
|
||||
/// The result of executing a `TestCase`.
|
||||
///
|
||||
pub struct ExecutionResult {
|
||||
/// The canonical beacon chain generated from the execution.
|
||||
pub chain: Vec<CheckPoint>,
|
||||
/// The spec used for execution.
|
||||
pub spec: ChainSpec,
|
||||
}
|
||||
|
||||
impl TestCase {
|
||||
/// Load the test case from a YAML document.
|
||||
pub fn from_yaml(test_case: &Yaml) -> Self {
|
||||
Self {
|
||||
results: Results::from_yaml(&test_case["results"]),
|
||||
config: Config::from_yaml(&test_case["config"]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a `ChainSpec::foundation()`.
|
||||
///
|
||||
/// If specified in `config`, returns it with a modified `slots_per_epoch`.
|
||||
fn spec(&self) -> ChainSpec {
|
||||
let mut spec = ChainSpec::foundation();
|
||||
|
||||
if let Some(n) = self.config.slots_per_epoch {
|
||||
spec.slots_per_epoch = n;
|
||||
}
|
||||
|
||||
spec
|
||||
}
|
||||
|
||||
/// Executes the test case, returning an `ExecutionResult`.
|
||||
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);
|
||||
|
||||
// -1 slots because genesis counts as a slot.
|
||||
for slot_height in 0..slots - 1 {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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!");
|
||||
|
||||
info!("Building chain dump for analysis...");
|
||||
|
||||
ExecutionResult {
|
||||
chain: harness.chain_dump().expect("Chain dump failed."),
|
||||
spec: (*harness.spec).clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that the `ExecutionResult` is consistent with the specifications in `self.results`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics with a message if any result does not match exepectations.
|
||||
pub fn assert_result_valid(&self, execution_result: ExecutionResult) {
|
||||
info!("Verifying test results...");
|
||||
let spec = &execution_result.spec;
|
||||
|
||||
if let Some(num_skipped_slots) = self.results.num_skipped_slots {
|
||||
assert_eq!(
|
||||
execution_result.chain.len(),
|
||||
self.config.num_slots as usize - num_skipped_slots,
|
||||
"actual skipped slots != expected."
|
||||
);
|
||||
info!(
|
||||
"OK: Chain length is {} ({} skipped slots).",
|
||||
execution_result.chain.len(),
|
||||
num_skipped_slots
|
||||
);
|
||||
}
|
||||
|
||||
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 =
|
||||
state.slot - spec.genesis_epoch.start_slot(spec.slots_per_epoch);
|
||||
|
||||
if state_check.slot == adjusted_state_slot {
|
||||
state_check.assert_valid(state, spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds an `AttesterSlashing` for some `validator_indices`.
|
||||
///
|
||||
/// Signs the message using a `BeaconChainHarness`.
|
||||
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)
|
||||
}
|
||||
|
||||
/// Builds an `ProposerSlashing` for some `validator_index`.
|
||||
///
|
||||
/// Signs the message using a `BeaconChainHarness`.
|
||||
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)
|
||||
}
|
||||
@@ -13,8 +13,10 @@ pub struct StateCheck {
|
||||
pub num_validators: Option<usize>,
|
||||
/// A list of validator indices which have been penalized. Must be in ascending order.
|
||||
pub slashed_validators: Option<Vec<u64>>,
|
||||
/// A list of validator indices which have been exited. Must be in ascending order.
|
||||
/// A list of validator indices which have been fully exited. Must be in ascending order.
|
||||
pub exited_validators: Option<Vec<u64>>,
|
||||
/// A list of validator indices which have had an exit initiated. Must be in ascending order.
|
||||
pub exit_initiated_validators: Option<Vec<u64>>,
|
||||
}
|
||||
|
||||
impl StateCheck {
|
||||
@@ -27,6 +29,7 @@ impl StateCheck {
|
||||
num_validators: as_usize(&yaml, "num_validators"),
|
||||
slashed_validators: as_vec_u64(&yaml, "slashed_validators"),
|
||||
exited_validators: as_vec_u64(&yaml, "exited_validators"),
|
||||
exit_initiated_validators: as_vec_u64(&yaml, "exit_initiated_validators"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +43,7 @@ impl StateCheck {
|
||||
|
||||
info!("Running state check for slot height {}.", self.slot);
|
||||
|
||||
// Check the state slot.
|
||||
assert_eq!(
|
||||
self.slot,
|
||||
state.slot - spec.genesis_epoch.start_slot(spec.slots_per_epoch),
|
||||
@@ -55,6 +59,7 @@ impl StateCheck {
|
||||
info!("OK: num_validators = {}.", num_validators);
|
||||
}
|
||||
|
||||
// Check for slashed validators.
|
||||
if let Some(ref slashed_validators) = self.slashed_validators {
|
||||
let actually_slashed_validators: Vec<u64> = state
|
||||
.validator_registry
|
||||
@@ -75,6 +80,7 @@ impl StateCheck {
|
||||
info!("OK: slashed_validators = {:?}.", slashed_validators);
|
||||
}
|
||||
|
||||
// Check for exited validators.
|
||||
if let Some(ref exited_validators) = self.exited_validators {
|
||||
let actually_exited_validators: Vec<u64> = state
|
||||
.validator_registry
|
||||
@@ -94,5 +100,29 @@ impl StateCheck {
|
||||
);
|
||||
info!("OK: exited_validators = {:?}.", exited_validators);
|
||||
}
|
||||
|
||||
// Check for validators that have initiated exit.
|
||||
if let Some(ref exit_initiated_validators) = self.exit_initiated_validators {
|
||||
let actual: Vec<u64> = state
|
||||
.validator_registry
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, validator)| {
|
||||
if validator.has_initiated_exit() {
|
||||
Some(i as u64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
actual, *exit_initiated_validators,
|
||||
"Exit initiated validators != expected."
|
||||
);
|
||||
info!(
|
||||
"OK: exit_initiated_validators = {:?}.",
|
||||
exit_initiated_validators
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user