From 7108d057fb8bf0d84326619098755305c41e1572 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 28 Feb 2019 23:09:21 +1100 Subject: [PATCH 01/41] Set `BeaconState` to process deposits in parallel Provides a large speed improvement. --- eth2/types/src/beacon_state.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 3d94a8e3de..61f270e15a 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -9,6 +9,7 @@ use bls::verify_proof_of_possession; use honey_badger_split::SplitExt; use log::{debug, error, trace}; use rand::RngCore; +use rayon::prelude::*; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; use std::collections::HashMap; @@ -202,7 +203,7 @@ impl BeaconState { trace!("Processing genesis deposits..."); let deposit_data = initial_validator_deposits - .iter() + .par_iter() .map(|deposit| &deposit.deposit_data) .collect(); From e0926dcd8da65e8da0f83d06b4572aabe9ccf2de Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 28 Feb 2019 23:10:40 +1100 Subject: [PATCH 02/41] Change log msgs in `BeaconState` Make it louder when shuffling happens, also when deposits are being processed. --- eth2/types/src/beacon_state.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 61f270e15a..5591fa3010 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -200,7 +200,7 @@ impl BeaconState { let mut genesis_state = BeaconState::genesis_without_validators(genesis_time, latest_eth1_data, spec)?; - trace!("Processing genesis deposits..."); + debug!("Processing genesis deposits..."); let deposit_data = initial_validator_deposits .par_iter() @@ -412,6 +412,8 @@ impl BeaconState { return Err(Error::InsufficientValidators); } + debug!("Shuffling {} validators...", active_validator_indices.len()); + let committees_per_epoch = self.get_epoch_committee_count(active_validator_indices.len(), spec); From 8b06fa31da5e44c4a438d297d842695ab4dd315b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 28 Feb 2019 23:13:00 +1100 Subject: [PATCH 03/41] Add basic YAML test_harness tests Works, however ignores a lot of fields in the YAML. --- .../beacon_chain/test_harness/Cargo.toml | 10 ++ .../test_harness/examples/chain.yaml | 77 +++++++++ .../beacon_chain/test_harness/src/bin.rs | 148 ++++++++++++++++++ 3 files changed, 235 insertions(+) create mode 100644 beacon_node/beacon_chain/test_harness/examples/chain.yaml create mode 100644 beacon_node/beacon_chain/test_harness/src/bin.rs diff --git a/beacon_node/beacon_chain/test_harness/Cargo.toml b/beacon_node/beacon_chain/test_harness/Cargo.toml index 657cc79552..bd7a58270b 100644 --- a/beacon_node/beacon_chain/test_harness/Cargo.toml +++ b/beacon_node/beacon_chain/test_harness/Cargo.toml @@ -4,6 +4,14 @@ version = "0.1.0" authors = ["Paul Hauner "] edition = "2018" +[[bin]] +name = "test_harness" +path = "src/bin.rs" + +[lib] +name = "test_harness" +path = "src/lib.rs" + [[bench]] name = "state_transition" harness = false @@ -18,6 +26,7 @@ beacon_chain = { path = "../../beacon_chain" } block_proposer = { path = "../../../eth2/block_proposer" } bls = { path = "../../../eth2/utils/bls" } boolean-bitfield = { path = "../../../eth2/utils/boolean-bitfield" } +clap = "2.32.0" db = { path = "../../db" } parking_lot = "0.7" failure = "0.1" @@ -33,3 +42,4 @@ serde_json = "1.0" slot_clock = { path = "../../../eth2/utils/slot_clock" } ssz = { path = "../../../eth2/utils/ssz" } types = { path = "../../../eth2/types" } +yaml-rust = "0.4.2" diff --git a/beacon_node/beacon_chain/test_harness/examples/chain.yaml b/beacon_node/beacon_chain/test_harness/examples/chain.yaml new file mode 100644 index 0000000000..5d8e347952 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/examples/chain.yaml @@ -0,0 +1,77 @@ +title: Sample Ethereum Serenity State Transition Tests +summary: Testing full state transition block processing +test_suite: prysm +fork: sapphire +version: 1.0 +test_cases: + - config: + epoch_length: 64 + deposits_for_chain_start: 1000 + num_slots: 32 # Testing advancing state to slot < SlotsPerEpoch + results: + slot: 32 + num_validators: 1000 + - config: + epoch_length: 64 + deposits_for_chain_start: 16384 + num_slots: 64 + deposits: + - slot: 1 + amount: 32 + merkle_index: 0 + pubkey: !!binary | + SlAAbShSkUg7PLiPHZI/rTS1uAvKiieOrifPN6Moso0= + - slot: 15 + amount: 32 + merkle_index: 1 + pubkey: !!binary | + Oklajsjdkaklsdlkajsdjlajslkdjlkasjlkdjlajdsd + - slot: 55 + amount: 32 + merkle_index: 2 + pubkey: !!binary | + LkmqmqoodLKAslkjdkajsdljasdkajlksjdasldjasdd + proposer_slashings: + - slot: 16 # At slot 16, we trigger a proposal slashing occurring + proposer_index: 16385 # We penalize the proposer that was just added from slot 15 + proposal_1_shard: 0 + proposal_1_slot: 15 + proposal_1_root: !!binary | + LkmqmqoodLKAslkjdkajsdljasdkajlksjdasldjasdd + proposal_2_shard: 0 + proposal_2_slot: 15 + proposal_2_root: !!binary | + LkmqmqoodLKAslkjdkajsdljasdkajlksjdasldjasdd + attester_slashings: + - slot: 59 # At slot 59, we trigger a attester slashing + slashable_vote_data_1_slot: 55 + slashable_vote_data_2_slot: 55 + slashable_vote_data_1_justified_slot: 0 + slashable_vote_data_2_justified_slot: 1 + slashable_vote_data_1_custody_0_indices: [16386] + slashable_vote_data_1_custody_1_indices: [] + slashable_vote_data_2_custody_0_indices: [] + slashable_vote_data_2_custody_1_indices: [16386] + results: + slot: 64 + num_validators: 16387 + penalized_validators: [16385, 16386] # We test that the validators at indices 16385, 16386 were indeed penalized + - config: + skip_slots: [10, 20] + epoch_length: 64 + deposits_for_chain_start: 1000 + num_slots: 128 # Testing advancing state's slot == 2*SlotsPerEpoch + deposits: + - slot: 10 + amount: 32 + merkle_index: 0 + pubkey: !!binary | + SlAAbShSkUg7PLiPHZI/rTS1uAvKiieOrifPN6Moso0= + - slot: 20 + amount: 32 + merkle_index: 1 + pubkey: !!binary | + Oklajsjdkaklsdlkajsdjlajslkdjlkasjlkdjlajdsd + results: + slot: 128 + num_validators: 1000 # Validator registry should not have grown if slots 10 and 20 were skipped diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs new file mode 100644 index 0000000000..007ec3f600 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -0,0 +1,148 @@ +use self::beacon_chain_harness::BeaconChainHarness; +use self::validator_harness::ValidatorHarness; +use clap::{App, Arg}; +use env_logger::{Builder, Env}; +use log::info; +use std::{fs::File, io::prelude::*}; +use types::*; +use yaml_rust::{Yaml, YamlLoader}; + +mod beacon_chain_harness; +mod validator_harness; + +fn main() { + let matches = App::new("Lighthouse Test Harness Runner") + .version("0.0.1") + .author("Sigma Prime ") + .about("Runs `test_harness` using a YAML manifest.") + .arg( + Arg::with_name("yaml") + .long("yaml") + .value_name("FILE") + .help("YAML file manifest.") + .required(true), + ) + .get_matches(); + + Builder::from_env(Env::default().default_filter_or("debug")).init(); + + if let Some(yaml_file) = matches.value_of("yaml") { + let docs = { + let mut file = File::open(yaml_file).unwrap(); + + let mut yaml_str = String::new(); + file.read_to_string(&mut yaml_str).unwrap(); + + YamlLoader::load_from_str(&yaml_str).unwrap() + }; + + for doc in &docs { + for test_case in doc["test_cases"].as_vec().unwrap() { + let manifest = Manifest::from_yaml(test_case); + manifest.execute(); + } + } + } +} + +struct Manifest { + pub results: Results, + pub config: Config, +} + +impl Manifest { + pub fn from_yaml(test_case: &Yaml) -> Self { + Self { + results: Results::from_yaml(&test_case["results"]), + config: Config::from_yaml(&test_case["config"]), + } + } + + fn spec(&self) -> ChainSpec { + let mut spec = ChainSpec::foundation(); + + if let Some(n) = self.config.epoch_length { + spec.epoch_length = n; + } + + spec + } + + pub fn execute(&self) { + let spec = self.spec(); + let validator_count = self.config.deposits_for_chain_start; + let slots = self.results.slot; + + info!( + "Building BeaconChainHarness with {} validators...", + validator_count + ); + + let mut harness = BeaconChainHarness::new(spec, validator_count); + + info!("Starting simulation across {} slots...", slots); + + for _ in 0..self.results.slot { + harness.advance_chain_with_block(); + } + + harness.run_fork_choice(); + + let dump = harness.chain_dump().expect("Chain dump failed."); + + assert_eq!(dump.len() as u64, slots + 1); // + 1 for genesis block. + + // harness.dump_to_file("/tmp/chaindump.json".to_string(), &dump); + } +} + +struct Results { + pub slot: u64, + pub num_validators: Option, + pub slashed_validators: Option>, + pub exited_validators: Option>, +} + +impl Results { + pub fn from_yaml(yaml: &Yaml) -> Self { + Self { + slot: as_u64(&yaml, "slot").expect("Must have end slot"), + num_validators: as_usize(&yaml, "num_validators"), + slashed_validators: as_vec_u64(&yaml, "slashed_validators"), + exited_validators: as_vec_u64(&yaml, "exited_validators"), + } + } +} + +struct Config { + pub deposits_for_chain_start: usize, + pub epoch_length: Option, +} + +impl Config { + pub fn from_yaml(yaml: &Yaml) -> Self { + Self { + deposits_for_chain_start: as_usize(&yaml, "deposits_for_chain_start") + .expect("Must specify validator count"), + epoch_length: as_u64(&yaml, "epoch_length"), + } + } +} + +fn as_usize(yaml: &Yaml, key: &str) -> Option { + yaml[key].as_i64().and_then(|n| Some(n as usize)) +} + +fn as_u64(yaml: &Yaml, key: &str) -> Option { + yaml[key].as_i64().and_then(|n| Some(n as u64)) +} + +fn as_vec_u64(yaml: &Yaml, key: &str) -> Option> { + yaml[key].clone().into_vec().and_then(|vec| { + Some( + vec.iter() + .map(|item| item.as_i64().unwrap() as u64) + .collect(), + ) + }) +} From 1479013bd01bdd913aa05ad4f9edf360d4f0e41c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 1 Mar 2019 13:28:07 +1100 Subject: [PATCH 04/41] Add skip_slots to test_harness yaml processor --- .../test_harness/examples/chain.yaml | 1 + .../beacon_chain/test_harness/src/bin.rs | 51 +++++++++++++++---- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/examples/chain.yaml b/beacon_node/beacon_chain/test_harness/examples/chain.yaml index 5d8e347952..b170a0b2ee 100644 --- a/beacon_node/beacon_chain/test_harness/examples/chain.yaml +++ b/beacon_node/beacon_chain/test_harness/examples/chain.yaml @@ -8,6 +8,7 @@ test_cases: epoch_length: 64 deposits_for_chain_start: 1000 num_slots: 32 # Testing advancing state to slot < SlotsPerEpoch + skip_slots: [2, 3] results: slot: 32 num_validators: 1000 diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index 007ec3f600..48f349d4ae 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -1,8 +1,10 @@ use self::beacon_chain_harness::BeaconChainHarness; use self::validator_harness::ValidatorHarness; +use beacon_chain::CheckPoint; use clap::{App, Arg}; use env_logger::{Builder, Env}; -use log::info; +use log::{info, warn}; +use std::collections::HashMap; use std::{fs::File, io::prelude::*}; use types::*; use yaml_rust::{Yaml, YamlLoader}; @@ -39,7 +41,7 @@ fn main() { for doc in &docs { for test_case in doc["test_cases"].as_vec().unwrap() { let manifest = Manifest::from_yaml(test_case); - manifest.execute(); + manifest.assert_result_valid(manifest.execute()) } } } @@ -68,7 +70,7 @@ impl Manifest { spec } - pub fn execute(&self) { + pub fn execute(&self) -> ExecutionResult { let spec = self.spec(); let validator_count = self.config.deposits_for_chain_start; let slots = self.results.slot; @@ -82,18 +84,47 @@ impl Manifest { info!("Starting simulation across {} slots...", slots); - for _ in 0..self.results.slot { - harness.advance_chain_with_block(); + for slot_height in 0..self.results.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(); - let dump = harness.chain_dump().expect("Chain dump failed."); + info!("Test execution complete!"); - assert_eq!(dump.len() as u64, slots + 1); // + 1 for genesis block. - - // harness.dump_to_file("/tmp/chaindump.json".to_string(), &dump); + ExecutionResult { + chain: harness.chain_dump().expect("Chain dump failed."), + } } + + pub fn assert_result_valid(&self, result: ExecutionResult) { + info!("Verifying test results..."); + + if let Some(ref skip_slots) = self.config.skip_slots { + for checkpoint in result.chain { + let block_slot = checkpoint.beacon_block.slot.as_u64(); + assert!( + !skip_slots.contains(&block_slot), + "Slot {} was not skipped.", + block_slot + ); + } + } + info!("OK: Skipped slots not present in chain."); + } +} + +struct ExecutionResult { + pub chain: Vec, } struct Results { @@ -117,6 +148,7 @@ impl Results { struct Config { pub deposits_for_chain_start: usize, pub epoch_length: Option, + pub skip_slots: Option>, } impl Config { @@ -125,6 +157,7 @@ impl Config { deposits_for_chain_start: as_usize(&yaml, "deposits_for_chain_start") .expect("Must specify validator count"), epoch_length: as_u64(&yaml, "epoch_length"), + skip_slots: as_vec_u64(yaml, "skip_slots"), } } } From 1097c8089b5baa6da8280cb722098d90228e9fcd Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 1 Mar 2019 16:54:59 +1100 Subject: [PATCH 05/41] Add naive deposit-handling to BeaconChain --- beacon_node/beacon_chain/src/beacon_chain.rs | 39 +++++++++++++++++-- .../state_processing/src/block_processable.rs | 28 ++++++++++--- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 1065f661d6..80cc793051 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -15,9 +15,7 @@ use state_processing::{ use std::sync::Arc; use types::{ readers::{BeaconBlockReader, BeaconStateReader}, - AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, BeaconStateError, ChainSpec, - Crosslink, Deposit, Epoch, Eth1Data, FreeAttestation, Hash256, PublicKey, RelativeEpoch, - Signature, Slot, + *, }; #[derive(Debug, PartialEq)] @@ -66,6 +64,7 @@ pub struct BeaconChain { pub state_store: Arc>, pub slot_clock: U, pub attestation_aggregator: RwLock, + pub deposits_for_inclusion: RwLock>, canonical_head: RwLock, finalized_head: RwLock, pub state: RwLock, @@ -132,6 +131,7 @@ where state_store, slot_clock, attestation_aggregator, + deposits_for_inclusion: RwLock::new(vec![]), state: RwLock::new(genesis_state), finalized_head, canonical_head, @@ -364,6 +364,34 @@ where Ok(aggregation_outcome) } + pub fn receive_deposit_for_inclusion(&self, deposit: Deposit) { + // TODO: deposits are not check for validity; check them. + self.deposits_for_inclusion.write().push(deposit); + } + + pub fn get_deposits_for_block(&self) -> Vec { + // TODO: deposits are indiscriminately included; check them for validity. + self.deposits_for_inclusion.read().clone() + } + + pub fn mark_deposits_as_included(&self, included_deposits: &[Deposit]) { + // TODO: method does not take forks into account; consider this. + let mut indices_to_delete = vec![]; + + for included in included_deposits { + for (i, for_inclusion) in self.deposits_for_inclusion.read().iter().enumerate() { + if included == for_inclusion { + indices_to_delete.push(i); + } + } + } + + let deposits_for_inclusion = &mut self.deposits_for_inclusion.write(); + for i in indices_to_delete { + deposits_for_inclusion.remove(i); + } + } + /// Dumps the entire canonical chain, from the head to genesis to a vector for analysis. /// /// This could be a very expensive operation and should only be done in testing/analysis @@ -488,6 +516,9 @@ where self.block_store.put(&block_root, &ssz_encode(&block)[..])?; self.state_store.put(&state_root, &ssz_encode(&state)[..])?; + // Remove any included deposits from the for-inclusion queue + self.mark_deposits_as_included(&block.body.deposits[..]); + // run the fork_choice add_block logic self.fork_choice .write() @@ -544,7 +575,7 @@ where proposer_slashings: vec![], attester_slashings: vec![], attestations, - deposits: vec![], + deposits: self.get_deposits_for_block(), exits: vec![], }, }; diff --git a/eth2/state_processing/src/block_processable.rs b/eth2/state_processing/src/block_processable.rs index effaa65079..aab87c3ad2 100644 --- a/eth2/state_processing/src/block_processable.rs +++ b/eth2/state_processing/src/block_processable.rs @@ -3,10 +3,7 @@ use hashing::hash; use int_to_bytes::int_to_bytes32; use log::{debug, trace}; use ssz::{ssz_encode, TreeHash}; -use types::{ - AggregatePublicKey, Attestation, BeaconBlock, BeaconState, BeaconStateError, ChainSpec, - Crosslink, Epoch, Exit, Fork, Hash256, PendingAttestation, PublicKey, RelativeEpoch, Signature, -}; +use types::*; // TODO: define elsehwere. const DOMAIN_PROPOSAL: u64 = 2; @@ -35,6 +32,7 @@ pub enum Error { InvalidAttestation(AttestationValidationError), NoBlockRoot, MaxDepositsExceeded, + BadDeposit, MaxExitsExceeded, BadExit, BadCustodyReseeds, @@ -242,7 +240,27 @@ fn per_block_processing_signature_optional( Error::MaxDepositsExceeded ); - // TODO: process deposits. + // TODO: verify deposit merkle branches. + for deposit in &block.body.deposits { + debug!( + "Processing deposit for pubkey {:?}", + deposit.deposit_data.deposit_input.pubkey + ); + state + .process_deposit( + deposit.deposit_data.deposit_input.pubkey.clone(), + deposit.deposit_data.amount, + deposit + .deposit_data + .deposit_input + .proof_of_possession + .clone(), + deposit.deposit_data.deposit_input.withdrawal_credentials, + None, + spec, + ) + .map_err(|_| Error::BadDeposit)?; + } /* * Exits From eeeff9ef02f9d07ebfdbdf8b2c1334fa450301c8 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 1 Mar 2019 16:56:52 +1100 Subject: [PATCH 06/41] Ensure chain-dumps come with earliest block first Previously dump.first() was the latest block. IMO, this is counter-intuitive --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 80cc793051..805ccc9fd7 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -440,6 +440,8 @@ where last_slot = slot; } + dump.reverse(); + Ok(dump) } From c278c08e34eefd6be05a1c64d164807238166922 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 1 Mar 2019 16:57:24 +1100 Subject: [PATCH 07/41] Remove unnecessary clone. --- beacon_node/beacon_chain/src/beacon_chain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 805ccc9fd7..34e1a5183c 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -533,7 +533,7 @@ where if self.head().beacon_block_root == parent_block_root { self.update_canonical_head(block.clone(), block_root, state.clone(), state_root); // Update the local state variable. - *self.state.write() = state.clone(); + *self.state.write() = state; } Ok(BlockProcessingOutcome::ValidBlock(ValidBlock::Processed)) From b0403707eb0f4c9cf1f8c6819f2fdd9684c6c987 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 1 Mar 2019 16:59:55 +1100 Subject: [PATCH 08/41] Add support for deposits to test_harness --- .../test_harness/examples/chain.yaml | 13 ++- .../test_harness/src/beacon_chain_harness.rs | 16 +++- .../beacon_chain/test_harness/src/bin.rs | 85 +++++++++++++++++-- 3 files changed, 101 insertions(+), 13 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/examples/chain.yaml b/beacon_node/beacon_chain/test_harness/examples/chain.yaml index b170a0b2ee..919753afa5 100644 --- a/beacon_node/beacon_chain/test_harness/examples/chain.yaml +++ b/beacon_node/beacon_chain/test_harness/examples/chain.yaml @@ -7,10 +7,19 @@ test_cases: - config: epoch_length: 64 deposits_for_chain_start: 1000 - num_slots: 32 # Testing advancing state to slot < SlotsPerEpoch + num_slots: 65 skip_slots: [2, 3] + deposits: + - slot: 1 + amount: 32 + merkle_index: 0 + - slot: 3 + amount: 32 + merkle_index: 1 + - slot: 5 + amount: 32 + merkle_index: 2 results: - slot: 32 num_validators: 1000 - config: epoch_length: 64 diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index d3bd444d1a..b60454b57d 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -15,10 +15,7 @@ use std::fs::File; use std::io::prelude::*; use std::iter::FromIterator; use std::sync::Arc; -use types::{ - BeaconBlock, ChainSpec, Deposit, DepositData, DepositInput, Eth1Data, FreeAttestation, Hash256, - Keypair, Slot, -}; +use types::*; /// The beacon chain harness simulates a single beacon node with `validator_count` validators connected /// to it. Each validator is provided a borrow to the beacon chain, where it may read @@ -245,6 +242,17 @@ impl BeaconChainHarness { debug!("Free attestations processed."); } + pub fn process_deposit(&mut self, deposit: Deposit, keypair: Option) { + self.beacon_chain.receive_deposit_for_inclusion(deposit); + + // If a keypair is present, add a new `ValidatorHarness` to the rig. + if let Some(keypair) = keypair { + let validator = + ValidatorHarness::new(keypair, self.beacon_chain.clone(), self.spec.clone()); + self.validators.push(validator); + } + } + pub fn run_fork_choice(&mut self) { self.beacon_chain.fork_choice().unwrap() } diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index 48f349d4ae..f747be60a7 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -1,10 +1,10 @@ use self::beacon_chain_harness::BeaconChainHarness; use self::validator_harness::ValidatorHarness; use beacon_chain::CheckPoint; +use bls::create_proof_of_possession; use clap::{App, Arg}; use env_logger::{Builder, Env}; use log::{info, warn}; -use std::collections::HashMap; use std::{fs::File, io::prelude::*}; use types::*; use yaml_rust::{Yaml, YamlLoader}; @@ -73,7 +73,7 @@ impl Manifest { pub fn execute(&self) -> ExecutionResult { let spec = self.spec(); let validator_count = self.config.deposits_for_chain_start; - let slots = self.results.slot; + let slots = self.config.num_slots; info!( "Building BeaconChainHarness with {} validators...", @@ -84,7 +84,18 @@ impl Manifest { info!("Starting simulation across {} slots...", slots); - for slot_height in 0..self.results.slot { + for slot_height in 0..slots { + // Include deposits + 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.process_deposit(deposit.clone(), Some(keypair.clone())); + } + } + } + + // 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); @@ -109,8 +120,24 @@ impl Manifest { pub fn assert_result_valid(&self, result: ExecutionResult) { info!("Verifying test results..."); + let skipped_slots = self + .config + .skip_slots + .clone() + .and_then(|slots| Some(slots.len())) + .unwrap_or_else(|| 0); + let expected_blocks = self.config.num_slots as usize + 1 - skipped_slots; + + assert_eq!(result.chain.len(), expected_blocks); + + info!( + "OK: Chain length is {} ({} skipped slots).", + result.chain.len(), + skipped_slots + ); + if let Some(ref skip_slots) = self.config.skip_slots { - for checkpoint in result.chain { + for checkpoint in &result.chain { let block_slot = checkpoint.beacon_block.slot.as_u64(); assert!( !skip_slots.contains(&block_slot), @@ -118,17 +145,30 @@ impl Manifest { block_slot ); } + info!("OK: Skipped slots not present in chain."); + } + + if let Some(ref deposits) = self.config.deposits { + let latest_state = &result.chain.last().expect("Empty chain.").beacon_state; + assert_eq!( + latest_state.validator_registry.len(), + self.config.deposits_for_chain_start + deposits.len() + ); + info!( + "OK: Validator registry has {} more validators.", + deposits.len() + ); } - info!("OK: Skipped slots not present in chain."); } } +pub type DepositTuple = (u64, Deposit, Keypair); + struct ExecutionResult { pub chain: Vec, } struct Results { - pub slot: u64, pub num_validators: Option, pub slashed_validators: Option>, pub exited_validators: Option>, @@ -137,7 +177,6 @@ struct Results { impl Results { pub fn from_yaml(yaml: &Yaml) -> Self { Self { - slot: as_u64(&yaml, "slot").expect("Must have end slot"), num_validators: as_usize(&yaml, "num_validators"), slashed_validators: as_vec_u64(&yaml, "slashed_validators"), exited_validators: as_vec_u64(&yaml, "exited_validators"), @@ -148,7 +187,9 @@ impl Results { struct Config { pub deposits_for_chain_start: usize, pub epoch_length: Option, + pub num_slots: u64, pub skip_slots: Option>, + pub deposits: Option>, } impl Config { @@ -157,11 +198,41 @@ impl Config { deposits_for_chain_start: as_usize(&yaml, "deposits_for_chain_start") .expect("Must specify validator count"), epoch_length: as_u64(&yaml, "epoch_length"), + num_slots: as_u64(&yaml, "num_slots").expect("Must specify `config.num_slots`"), skip_slots: as_vec_u64(yaml, "skip_slots"), + deposits: process_deposits(&yaml), } } } +fn process_deposits(yaml: &Yaml) -> Option> { + let mut deposits = vec![]; + + for deposit in yaml["deposits"].as_vec()? { + let keypair = Keypair::random(); + let proof_of_possession = create_proof_of_possession(&keypair); + + let slot = as_u64(deposit, "slot").expect("Incomplete deposit"); + let deposit = Deposit { + branch: vec![], + index: as_u64(deposit, "merkle_index").unwrap(), + deposit_data: DepositData { + amount: 32_000_000_000, + timestamp: 1, + deposit_input: DepositInput { + pubkey: keypair.pk.clone(), + withdrawal_credentials: Hash256::zero(), + proof_of_possession, + }, + }, + }; + + deposits.push((slot, deposit, keypair)); + } + + Some(deposits) +} + fn as_usize(yaml: &Yaml, key: &str) -> Option { yaml[key].as_i64().and_then(|n| Some(n as usize)) } From 1de723b2752057f7d94b517700b3fd32c3f3d67c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 11:23:37 +1100 Subject: [PATCH 09/41] Add proposer/attester slash queues to BeaconChain Allows for storing and including AttesterSlashing and ProposerSlashing objects in blocks. --- beacon_node/beacon_chain/src/beacon_chain.rs | 92 ++++++++++++++++++-- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 34e1a5183c..a16fc64729 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -65,6 +65,8 @@ pub struct BeaconChain { pub slot_clock: U, pub attestation_aggregator: RwLock, pub deposits_for_inclusion: RwLock>, + pub proposer_slashings_for_inclusion: RwLock>, + pub attester_slashings_for_inclusion: RwLock>, canonical_head: RwLock, finalized_head: RwLock, pub state: RwLock, @@ -132,6 +134,8 @@ where slot_clock, attestation_aggregator, deposits_for_inclusion: RwLock::new(vec![]), + proposer_slashings_for_inclusion: RwLock::new(vec![]), + attester_slashings_for_inclusion: RwLock::new(vec![]), state: RwLock::new(genesis_state), finalized_head, canonical_head, @@ -374,7 +378,7 @@ where self.deposits_for_inclusion.read().clone() } - pub fn mark_deposits_as_included(&self, included_deposits: &[Deposit]) { + pub fn set_deposits_as_included(&self, included_deposits: &[Deposit]) { // TODO: method does not take forks into account; consider this. let mut indices_to_delete = vec![]; @@ -392,6 +396,82 @@ where } } + pub fn receive_proposer_slashing_for_inclusion(&self, proposer_slashing: ProposerSlashing) { + // TODO: proposer_slashings are not check for validity; check them. + self.proposer_slashings_for_inclusion + .write() + .push(proposer_slashing); + } + + pub fn get_proposer_slashings_for_block(&self) -> Vec { + // TODO: proposer_slashings are indiscriminately included; check them for validity. + self.proposer_slashings_for_inclusion.read().clone() + } + + pub fn set_proposer_slashings_as_included( + &self, + included_proposer_slashings: &[ProposerSlashing], + ) { + // TODO: method does not take forks into account; consider this. + let mut indices_to_delete = vec![]; + + for included in included_proposer_slashings { + for (i, for_inclusion) in self + .proposer_slashings_for_inclusion + .read() + .iter() + .enumerate() + { + if included == for_inclusion { + indices_to_delete.push(i); + } + } + } + + let proposer_slashings_for_inclusion = &mut self.proposer_slashings_for_inclusion.write(); + for i in indices_to_delete { + proposer_slashings_for_inclusion.remove(i); + } + } + + pub fn receive_attester_slashing_for_inclusion(&self, attester_slashing: AttesterSlashing) { + // TODO: attester_slashings are not check for validity; check them. + self.attester_slashings_for_inclusion + .write() + .push(attester_slashing); + } + + pub fn get_attester_slashings_for_block(&self) -> Vec { + // TODO: attester_slashings are indiscriminately included; check them for validity. + self.attester_slashings_for_inclusion.read().clone() + } + + pub fn set_attester_slashings_as_included( + &self, + included_attester_slashings: &[AttesterSlashing], + ) { + // TODO: method does not take forks into account; consider this. + let mut indices_to_delete = vec![]; + + for included in included_attester_slashings { + for (i, for_inclusion) in self + .attester_slashings_for_inclusion + .read() + .iter() + .enumerate() + { + if included == for_inclusion { + indices_to_delete.push(i); + } + } + } + + let attester_slashings_for_inclusion = &mut self.attester_slashings_for_inclusion.write(); + for i in indices_to_delete { + attester_slashings_for_inclusion.remove(i); + } + } + /// Dumps the entire canonical chain, from the head to genesis to a vector for analysis. /// /// This could be a very expensive operation and should only be done in testing/analysis @@ -518,8 +598,10 @@ where self.block_store.put(&block_root, &ssz_encode(&block)[..])?; self.state_store.put(&state_root, &ssz_encode(&state)[..])?; - // Remove any included deposits from the for-inclusion queue - self.mark_deposits_as_included(&block.body.deposits[..]); + // Update the inclusion queues so they aren't re-submitted. + self.set_deposits_as_included(&block.body.deposits[..]); + self.set_proposer_slashings_as_included(&block.body.proposer_slashings[..]); + self.set_attester_slashings_as_included(&block.body.attester_slashings[..]); // run the fork_choice add_block logic self.fork_choice @@ -574,8 +656,8 @@ where }, signature: self.spec.empty_signature.clone(), // To be completed by a validator. body: BeaconBlockBody { - proposer_slashings: vec![], - attester_slashings: vec![], + proposer_slashings: self.get_proposer_slashings_for_block(), + attester_slashings: self.get_attester_slashings_for_block(), attestations, deposits: self.get_deposits_for_block(), exits: vec![], From fd819fb7caaa24996daf8069f0d7a4e561408dd0 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 11:24:41 +1100 Subject: [PATCH 10/41] Set BeaconChain block propose failure log to warn It think it's more suitable to a warn --- beacon_node/beacon_chain/src/beacon_chain.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index a16fc64729..625a891970 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -5,7 +5,7 @@ use db::{ ClientDB, DBError, }; use fork_choice::{ForkChoice, ForkChoiceError}; -use log::{debug, trace}; +use log::{debug, trace, warn}; use parking_lot::{RwLock, RwLockReadGuard}; use slot_clock::SlotClock; use ssz::ssz_encode; @@ -668,7 +668,7 @@ where let result = state.per_block_processing_without_verifying_block_signature(&block, &self.spec); - trace!( + warn!( "BeaconNode::produce_block: state processing result: {:?}", result ); From 7f1e40a8c640304196b0cfde5d392fb0e3aa423d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 11:25:55 +1100 Subject: [PATCH 11/41] Add proposer slashing support to test_harness Test harness will now add signatures to a ProposerSlashing and submit it to the BeaconChain --- .../test_harness/examples/chain.yaml | 11 +++ .../test_harness/src/beacon_chain_harness.rs | 33 ++++++++- .../beacon_chain/test_harness/src/bin.rs | 69 +++++++++++++++++-- 3 files changed, 108 insertions(+), 5 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/examples/chain.yaml b/beacon_node/beacon_chain/test_harness/examples/chain.yaml index 919753afa5..036871aeb5 100644 --- a/beacon_node/beacon_chain/test_harness/examples/chain.yaml +++ b/beacon_node/beacon_chain/test_harness/examples/chain.yaml @@ -19,6 +19,17 @@ test_cases: - slot: 5 amount: 32 merkle_index: 2 + proposer_slashings: + - slot: 8 # At slot 8, we trigger a proposal slashing occurring + proposer_index: 42 # We penalize the validator at position 42 + proposal_1_shard: 0 + proposal_1_slot: 15 + proposal_1_root: !!binary | + LkmqmqoodLKAslkjdkajsdljasdkajlksjdasldjasdd + proposal_2_shard: 0 + proposal_2_slot: 15 + proposal_2_root: !!binary | + DIFFERENTKAslkjdkajsdljasdkajlksjdasldjasdd results: num_validators: 1000 - config: diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index b60454b57d..43b60d5064 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -10,6 +10,7 @@ use fork_choice::BitwiseLMDGhost; use log::debug; use rayon::prelude::*; use slot_clock::TestingSlotClock; +use ssz::TreeHash; use std::collections::HashSet; use std::fs::File; use std::io::prelude::*; @@ -242,7 +243,7 @@ impl BeaconChainHarness { debug!("Free attestations processed."); } - pub fn process_deposit(&mut self, deposit: Deposit, keypair: Option) { + pub fn add_deposit(&mut self, deposit: Deposit, keypair: Option) { self.beacon_chain.receive_deposit_for_inclusion(deposit); // If a keypair is present, add a new `ValidatorHarness` to the rig. @@ -253,6 +254,36 @@ impl BeaconChainHarness { } } + pub fn add_proposer_slashing(&mut self, mut proposer_slashing: ProposerSlashing) { + let validator = &self.validators[proposer_slashing.proposer_index as usize]; + + // This following code is a little awkward, but managing the data_1 and data_1 was getting + // rather confusing. I think this is better + let proposals = vec![ + &proposer_slashing.proposal_data_1, + &proposer_slashing.proposal_data_2, + ]; + let signatures: Vec = proposals + .iter() + .map(|proposal_data| { + let message = proposal_data.hash_tree_root(); + let epoch = proposal_data.slot.epoch(self.spec.epoch_length); + let domain = self + .beacon_chain + .state + .read() + .fork + .get_domain(epoch, self.spec.domain_proposal); + Signature::new(&message[..], domain, &validator.keypair.sk) + }) + .collect(); + proposer_slashing.proposal_signature_1 = signatures[0].clone(); + proposer_slashing.proposal_signature_2 = signatures[1].clone(); + + self.beacon_chain + .receive_proposer_slashing_for_inclusion(proposer_slashing); + } + pub fn run_fork_choice(&mut self) { self.beacon_chain.fork_choice().unwrap() } diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index f747be60a7..e905e9342e 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -85,12 +85,25 @@ impl Manifest { info!("Starting simulation across {} slots...", slots); for slot_height in 0..slots { - // Include deposits + // 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.process_deposit(deposit.clone(), Some(keypair.clone())); + 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, slashing) in slashings { + if *slot == slot_height { + info!( + "Including proposer slashing at slot height {}.", + slot_height + ); + harness.add_proposer_slashing(slashing.clone()); } } } @@ -163,6 +176,7 @@ impl Manifest { } pub type DepositTuple = (u64, Deposit, Keypair); +pub type ProposerSlashingTuple = (u64, ProposerSlashing); struct ExecutionResult { pub chain: Vec, @@ -190,6 +204,7 @@ struct Config { pub num_slots: u64, pub skip_slots: Option>, pub deposits: Option>, + pub proposer_slashings: Option>, } impl Config { @@ -200,12 +215,52 @@ impl Config { epoch_length: as_u64(&yaml, "epoch_length"), num_slots: as_u64(&yaml, "num_slots").expect("Must specify `config.num_slots`"), skip_slots: as_vec_u64(yaml, "skip_slots"), - deposits: process_deposits(&yaml), + deposits: parse_deposits(&yaml), + proposer_slashings: parse_proposer_slashings(&yaml), } } } -fn process_deposits(yaml: &Yaml) -> Option> { +fn parse_proposer_slashings(yaml: &Yaml) -> Option> { + let mut slashings = vec![]; + + for slashing in yaml["proposer_slashings"].as_vec()? { + let slot = as_u64(slashing, "slot").expect("Incomplete slashing"); + + let slashing = ProposerSlashing { + proposer_index: as_u64(slashing, "proposer_index") + .expect("Incomplete slashing (proposer_index)"), + proposal_data_1: ProposalSignedData { + slot: Slot::from( + as_u64(slashing, "proposal_1_slot") + .expect("Incomplete slashing (proposal_1_slot)."), + ), + shard: as_u64(slashing, "proposal_1_shard") + .expect("Incomplete slashing (proposal_1_shard)."), + block_root: as_hash256(slashing, "proposal_1_root") + .expect("Incomplete slashing (proposal_1_root)."), + }, + proposal_signature_1: Signature::empty_signature(), // Will be replaced with real signature at runtime. + proposal_data_2: ProposalSignedData { + slot: Slot::from( + as_u64(slashing, "proposal_2_slot") + .expect("Incomplete slashing (proposal_2_slot)."), + ), + shard: as_u64(slashing, "proposal_2_shard") + .expect("Incomplete slashing (proposal_2_shard)."), + block_root: as_hash256(slashing, "proposal_2_root") + .expect("Incomplete slashing (proposal_2_root)."), + }, + proposal_signature_2: Signature::empty_signature(), // Will be replaced with real signature at runtime. + }; + + slashings.push((slot, slashing)); + } + + Some(slashings) +} + +fn parse_deposits(yaml: &Yaml) -> Option> { let mut deposits = vec![]; for deposit in yaml["deposits"].as_vec()? { @@ -241,6 +296,12 @@ fn as_u64(yaml: &Yaml, key: &str) -> Option { yaml[key].as_i64().and_then(|n| Some(n as u64)) } +fn as_hash256(yaml: &Yaml, key: &str) -> Option { + yaml[key] + .as_str() + .and_then(|s| Some(Hash256::from(s.as_bytes()))) +} + fn as_vec_u64(yaml: &Yaml, key: &str) -> Option> { yaml[key].clone().into_vec().and_then(|vec| { Some( From 867dce34cdd6b7d32c805fd81dfa1cb35d7ed54d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 11:26:39 +1100 Subject: [PATCH 12/41] Remove domain constants from block_processable The information is gather from the `spec` object, not the constants. --- eth2/state_processing/src/block_processable.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/eth2/state_processing/src/block_processable.rs b/eth2/state_processing/src/block_processable.rs index aab87c3ad2..4083d17d8c 100644 --- a/eth2/state_processing/src/block_processable.rs +++ b/eth2/state_processing/src/block_processable.rs @@ -5,12 +5,7 @@ use log::{debug, trace}; use ssz::{ssz_encode, TreeHash}; use types::*; -// TODO: define elsehwere. -const DOMAIN_PROPOSAL: u64 = 2; -const DOMAIN_EXIT: u64 = 3; -const DOMAIN_RANDAO: u64 = 4; const PHASE_0_CUSTODY_BIT: bool = false; -const DOMAIN_ATTESTATION: u64 = 1; #[derive(Debug, PartialEq)] pub enum Error { @@ -111,7 +106,7 @@ fn per_block_processing_signature_optional( &block_proposer.pubkey, &block.proposal_root(spec)[..], &block.signature, - get_domain(&state.fork, state.current_epoch(spec), DOMAIN_PROPOSAL) + get_domain(&state.fork, state.current_epoch(spec), spec.domain_proposal) ), Error::BadBlockSignature ); @@ -125,7 +120,7 @@ fn per_block_processing_signature_optional( &block_proposer.pubkey, &int_to_bytes32(state.current_epoch(spec).as_u64()), &block.randao_reveal, - get_domain(&state.fork, state.current_epoch(spec), DOMAIN_RANDAO) + get_domain(&state.fork, state.current_epoch(spec), spec.domain_randao) ), Error::BadRandaoSignature ); @@ -186,7 +181,7 @@ fn per_block_processing_signature_optional( .proposal_data_1 .slot .epoch(spec.epoch_length), - DOMAIN_PROPOSAL + spec.domain_proposal ) ), Error::BadProposerSlashing @@ -202,7 +197,7 @@ fn per_block_processing_signature_optional( .proposal_data_2 .slot .epoch(spec.epoch_length), - DOMAIN_PROPOSAL + spec.domain_proposal ) ), Error::BadProposerSlashing @@ -294,7 +289,7 @@ fn per_block_processing_signature_optional( &validator.pubkey, &exit_message, &exit.signature, - get_domain(&state.fork, exit.epoch, DOMAIN_EXIT) + get_domain(&state.fork, exit.epoch, spec.domain_exit) ), Error::BadProposerSlashing ); @@ -401,7 +396,7 @@ fn validate_attestation_signature_optional( get_domain( &state.fork, attestation.data.slot.epoch(spec.epoch_length), - DOMAIN_ATTESTATION, + spec.domain_attestation, ) ), AttestationValidationError::BadSignature From 22d59a70cc4d5224ef5a0f5b096693e2142b215f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 11:27:12 +1100 Subject: [PATCH 13/41] Add debug message when validator is penalized --- eth2/types/src/beacon_state.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 5591fa3010..1c486e9b6c 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1004,6 +1004,10 @@ impl BeaconState { whistleblower_reward ); self.validator_registry[validator_index].penalized_epoch = current_epoch; + debug!( + "Whistleblower {} penalized validator {}.", + whistleblower_index, validator_index + ); Ok(()) } From ec5581ce1dd33931c679f6d60e38cc891bcc5439 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 15:28:01 +1100 Subject: [PATCH 14/41] Shorten test_harness YAML to single test --- .../test_harness/examples/chain.yaml | 72 ++----------------- 1 file changed, 6 insertions(+), 66 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/examples/chain.yaml b/beacon_node/beacon_chain/test_harness/examples/chain.yaml index 036871aeb5..4c4946c5db 100644 --- a/beacon_node/beacon_chain/test_harness/examples/chain.yaml +++ b/beacon_node/beacon_chain/test_harness/examples/chain.yaml @@ -20,8 +20,8 @@ test_cases: amount: 32 merkle_index: 2 proposer_slashings: - - slot: 8 # At slot 8, we trigger a proposal slashing occurring - proposer_index: 42 # We penalize the validator at position 42 + - slot: 8 # At slot 8, trigger a proposal slashing + proposer_index: 42 # Penalize the validator at position 42 proposal_1_shard: 0 proposal_1_slot: 15 proposal_1_root: !!binary | @@ -30,69 +30,9 @@ test_cases: proposal_2_slot: 15 proposal_2_root: !!binary | DIFFERENTKAslkjdkajsdljasdkajlksjdasldjasdd + attester_slashings: + # At slot 2, trigger an attester slashing for validators #11 and #12 + - slot: 2 + validator_indices: [11, 12] results: num_validators: 1000 - - config: - epoch_length: 64 - deposits_for_chain_start: 16384 - num_slots: 64 - deposits: - - slot: 1 - amount: 32 - merkle_index: 0 - pubkey: !!binary | - SlAAbShSkUg7PLiPHZI/rTS1uAvKiieOrifPN6Moso0= - - slot: 15 - amount: 32 - merkle_index: 1 - pubkey: !!binary | - Oklajsjdkaklsdlkajsdjlajslkdjlkasjlkdjlajdsd - - slot: 55 - amount: 32 - merkle_index: 2 - pubkey: !!binary | - LkmqmqoodLKAslkjdkajsdljasdkajlksjdasldjasdd - proposer_slashings: - - slot: 16 # At slot 16, we trigger a proposal slashing occurring - proposer_index: 16385 # We penalize the proposer that was just added from slot 15 - proposal_1_shard: 0 - proposal_1_slot: 15 - proposal_1_root: !!binary | - LkmqmqoodLKAslkjdkajsdljasdkajlksjdasldjasdd - proposal_2_shard: 0 - proposal_2_slot: 15 - proposal_2_root: !!binary | - LkmqmqoodLKAslkjdkajsdljasdkajlksjdasldjasdd - attester_slashings: - - slot: 59 # At slot 59, we trigger a attester slashing - slashable_vote_data_1_slot: 55 - slashable_vote_data_2_slot: 55 - slashable_vote_data_1_justified_slot: 0 - slashable_vote_data_2_justified_slot: 1 - slashable_vote_data_1_custody_0_indices: [16386] - slashable_vote_data_1_custody_1_indices: [] - slashable_vote_data_2_custody_0_indices: [] - slashable_vote_data_2_custody_1_indices: [16386] - results: - slot: 64 - num_validators: 16387 - penalized_validators: [16385, 16386] # We test that the validators at indices 16385, 16386 were indeed penalized - - config: - skip_slots: [10, 20] - epoch_length: 64 - deposits_for_chain_start: 1000 - num_slots: 128 # Testing advancing state's slot == 2*SlotsPerEpoch - deposits: - - slot: 10 - amount: 32 - merkle_index: 0 - pubkey: !!binary | - SlAAbShSkUg7PLiPHZI/rTS1uAvKiieOrifPN6Moso0= - - slot: 20 - amount: 32 - merkle_index: 1 - pubkey: !!binary | - Oklajsjdkaklsdlkajsdjlajslkdjlkasjlkdjlajdsd - results: - slot: 128 - num_validators: 1000 # Validator registry should not have grown if slots 10 and 20 were skipped From c885e36a939bcb92a1b943337ad3c2c8153c4f11 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 15:30:50 +1100 Subject: [PATCH 15/41] Add fn to BeaconChainHarness validator signing Signs some message using the priv key of some validator --- .../test_harness/src/beacon_chain_harness.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 43b60d5064..f0fcad5cc3 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -243,6 +243,25 @@ impl BeaconChainHarness { debug!("Free attestations processed."); } + pub fn validator_sign( + &self, + validator_index: usize, + message: &[u8], + epoch: Epoch, + domain_type: u64, + ) -> Option { + let validator = self.validators.get(validator_index)?; + + let domain = self + .beacon_chain + .state + .read() + .fork + .get_domain(epoch, domain_type); + + Some(Signature::new(message, domain, &validator.keypair.sk)) + } + pub fn add_deposit(&mut self, deposit: Deposit, keypair: Option) { self.beacon_chain.receive_deposit_for_inclusion(deposit); From f3a3cfcc45357cd691b9bbb663482056704ebf40 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 15:33:52 +1100 Subject: [PATCH 16/41] Add surround/dbl vote fns to SlashableAttestation Copied from `SlashableVoteData` --- eth2/types/src/attester_slashing.rs | 2 +- eth2/types/src/slashable_attestation.rs | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/eth2/types/src/attester_slashing.rs b/eth2/types/src/attester_slashing.rs index 2a1df9e0cc..9cb2123249 100644 --- a/eth2/types/src/attester_slashing.rs +++ b/eth2/types/src/attester_slashing.rs @@ -1,4 +1,4 @@ -use crate::{test_utils::TestRandom, SlashableAttestation}; +use crate::{test_utils::TestRandom, ChainSpec, SlashableAttestation}; use rand::RngCore; use serde_derive::Serialize; use ssz_derive::{Decode, Encode, TreeHash}; diff --git a/eth2/types/src/slashable_attestation.rs b/eth2/types/src/slashable_attestation.rs index c4a12338a5..d24c5dde42 100644 --- a/eth2/types/src/slashable_attestation.rs +++ b/eth2/types/src/slashable_attestation.rs @@ -1,4 +1,4 @@ -use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield}; +use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield, ChainSpec}; use rand::RngCore; use serde_derive::Serialize; use ssz_derive::{Decode, Encode, TreeHash}; @@ -12,6 +12,27 @@ pub struct SlashableAttestation { pub aggregate_signature: AggregateSignature, } +impl SlashableAttestation { + /// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target. + /// + /// Spec v0.3.0 + pub fn is_double_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool { + self.data.slot.epoch(spec.epoch_length) == other.data.slot.epoch(spec.epoch_length) + } + + /// Check if ``attestation_data_1`` surrounds ``attestation_data_2``. + /// + /// Spec v0.3.0 + pub fn is_surround_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool { + let source_epoch_1 = self.data.justified_epoch; + let source_epoch_2 = other.data.justified_epoch; + let target_epoch_1 = self.data.slot.epoch(spec.epoch_length); + let target_epoch_2 = other.data.slot.epoch(spec.epoch_length); + + (source_epoch_1 < source_epoch_2) && (target_epoch_2 < target_epoch_1) + } +} + #[cfg(test)] mod tests { use super::*; From ff2783a1cb6773e8c392b555973e96b911786593 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 15:35:02 +1100 Subject: [PATCH 17/41] Add AttesterSlashing to test_harness - Adds methods to BeaconChainHarness - Adds YAML parsing --- .../test_harness/src/beacon_chain_harness.rs | 5 + .../beacon_chain/test_harness/src/bin.rs | 115 ++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index f0fcad5cc3..862e037e29 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -303,6 +303,11 @@ impl BeaconChainHarness { .receive_proposer_slashing_for_inclusion(proposer_slashing); } + pub fn add_attester_slashing(&mut self, attester_slashing: AttesterSlashing) { + self.beacon_chain + .receive_attester_slashing_for_inclusion(attester_slashing); + } + pub fn run_fork_choice(&mut self) { self.beacon_chain.fork_choice().unwrap() } diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index e905e9342e..216e03d9f4 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -5,6 +5,7 @@ use bls::create_proof_of_possession; use clap::{App, Arg}; use env_logger::{Builder, Env}; use log::{info, warn}; +use ssz::TreeHash; use std::{fs::File, io::prelude::*}; use types::*; use yaml_rust::{Yaml, YamlLoader}; @@ -108,6 +109,21 @@ impl Manifest { } } + // 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 {}.", + slot_height + ); + 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) => { @@ -175,8 +191,87 @@ impl Manifest { } } +fn build_double_vote_attester_slashing( + harness: &BeaconChainHarness, + validator_indices: &[u64], +) -> AttesterSlashing { + let double_voted_slot = Slot::new(0); + let shard = 0; + let justified_epoch = Epoch::new(0); + let epoch = Epoch::new(0); + let hash_1 = Hash256::from("1".as_bytes()); + let hash_2 = Hash256::from("2".as_bytes()); + + let mut slashable_attestation_1 = SlashableAttestation { + validator_indices: validator_indices.to_vec(), + data: AttestationData { + slot: double_voted_slot, + shard, + beacon_block_root: hash_1, + epoch_boundary_root: hash_1, + shard_block_root: hash_1, + latest_crosslink: Crosslink { + epoch, + shard_block_root: hash_1, + }, + justified_epoch, + justified_block_root: hash_1, + }, + custody_bitfield: Bitfield::new(), + aggregate_signature: AggregateSignature::new(), + }; + + let mut slashable_attestation_2 = SlashableAttestation { + validator_indices: validator_indices.to_vec(), + data: AttestationData { + slot: double_voted_slot, + shard, + beacon_block_root: hash_2, + epoch_boundary_root: hash_2, + shard_block_root: hash_2, + latest_crosslink: Crosslink { + epoch, + shard_block_root: hash_2, + }, + justified_epoch, + justified_block_root: hash_2, + }, + custody_bitfield: Bitfield::new(), + aggregate_signature: AggregateSignature::new(), + }; + + let add_signatures = |attestation: &mut SlashableAttestation| { + for (i, validator_index) in validator_indices.iter().enumerate() { + let attestation_data_and_custody_bit = AttestationDataAndCustodyBit { + data: attestation.data.clone(), + custody_bit: attestation.custody_bitfield.get(i).unwrap(), + }; + let message = attestation_data_and_custody_bit.hash_tree_root(); + let signature = harness + .validator_sign( + *validator_index as usize, + &message[..], + epoch, + harness.spec.domain_attestation, + ) + .expect("Unable to sign attestation with unknown validator index."); + attestation.aggregate_signature.add(&signature); + } + }; + + add_signatures(&mut slashable_attestation_1); + add_signatures(&mut slashable_attestation_2); + + AttesterSlashing { + slashable_attestation_1, + slashable_attestation_2, + } +} + pub type DepositTuple = (u64, Deposit, Keypair); pub type ProposerSlashingTuple = (u64, ProposerSlashing); +// (slot, validator_indices) +pub type AttesterSlashingTuple = (u64, Vec); struct ExecutionResult { pub chain: Vec, @@ -205,6 +300,7 @@ struct Config { pub skip_slots: Option>, pub deposits: Option>, pub proposer_slashings: Option>, + pub attester_slashings: Option>, } impl Config { @@ -217,16 +313,35 @@ impl Config { skip_slots: as_vec_u64(yaml, "skip_slots"), deposits: parse_deposits(&yaml), proposer_slashings: parse_proposer_slashings(&yaml), + attester_slashings: parse_attester_slashings(&yaml), } } } +fn parse_attester_slashings(yaml: &Yaml) -> Option> { + let mut slashings = vec![]; + + for slashing in yaml["attester_slashings"].as_vec()? { + let slot = as_u64(slashing, "slot").expect("Incomplete attester_slashing (slot)"); + let validator_indices = as_vec_u64(slashing, "validator_indices") + .expect("Incomplete attester_slashing (validator_indices)"); + + slashings.push((slot, validator_indices)); + } + + Some(slashings) +} + fn parse_proposer_slashings(yaml: &Yaml) -> Option> { let mut slashings = vec![]; for slashing in yaml["proposer_slashings"].as_vec()? { let slot = as_u64(slashing, "slot").expect("Incomplete slashing"); + // Builds a ProposerSlashing object from YAML fields. + // + // Rustfmt make this look rather ugly, however it is just a simple struct + // instantiation. let slashing = ProposerSlashing { proposer_index: as_u64(slashing, "proposer_index") .expect("Incomplete slashing (proposer_index)"), From bb4d392a98029664f04e114df207f48c6b8069f9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 16:05:25 +1100 Subject: [PATCH 18/41] Add AttestationSlashingBuilder --- .../beacon_chain/test_harness/src/bin.rs | 76 ++--------------- eth2/types/src/attester_slashing.rs | 6 +- eth2/types/src/attester_slashing/builder.rs | 85 +++++++++++++++++++ 3 files changed, 96 insertions(+), 71 deletions(-) create mode 100644 eth2/types/src/attester_slashing/builder.rs diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index 216e03d9f4..fa5aa45a25 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -7,6 +7,7 @@ use env_logger::{Builder, Env}; use log::{info, warn}; use ssz::TreeHash; use std::{fs::File, io::prelude::*}; +use types::attester_slashing::AttesterSlashingBuilder; use types::*; use yaml_rust::{Yaml, YamlLoader}; @@ -195,82 +196,17 @@ fn build_double_vote_attester_slashing( harness: &BeaconChainHarness, validator_indices: &[u64], ) -> AttesterSlashing { - let double_voted_slot = Slot::new(0); - let shard = 0; - let justified_epoch = Epoch::new(0); - let epoch = Epoch::new(0); - let hash_1 = Hash256::from("1".as_bytes()); - let hash_2 = Hash256::from("2".as_bytes()); - - let mut slashable_attestation_1 = SlashableAttestation { - validator_indices: validator_indices.to_vec(), - data: AttestationData { - slot: double_voted_slot, - shard, - beacon_block_root: hash_1, - epoch_boundary_root: hash_1, - shard_block_root: hash_1, - latest_crosslink: Crosslink { - epoch, - shard_block_root: hash_1, - }, - justified_epoch, - justified_block_root: hash_1, - }, - custody_bitfield: Bitfield::new(), - aggregate_signature: AggregateSignature::new(), + 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") }; - let mut slashable_attestation_2 = SlashableAttestation { - validator_indices: validator_indices.to_vec(), - data: AttestationData { - slot: double_voted_slot, - shard, - beacon_block_root: hash_2, - epoch_boundary_root: hash_2, - shard_block_root: hash_2, - latest_crosslink: Crosslink { - epoch, - shard_block_root: hash_2, - }, - justified_epoch, - justified_block_root: hash_2, - }, - custody_bitfield: Bitfield::new(), - aggregate_signature: AggregateSignature::new(), - }; - - let add_signatures = |attestation: &mut SlashableAttestation| { - for (i, validator_index) in validator_indices.iter().enumerate() { - let attestation_data_and_custody_bit = AttestationDataAndCustodyBit { - data: attestation.data.clone(), - custody_bit: attestation.custody_bitfield.get(i).unwrap(), - }; - let message = attestation_data_and_custody_bit.hash_tree_root(); - let signature = harness - .validator_sign( - *validator_index as usize, - &message[..], - epoch, - harness.spec.domain_attestation, - ) - .expect("Unable to sign attestation with unknown validator index."); - attestation.aggregate_signature.add(&signature); - } - }; - - add_signatures(&mut slashable_attestation_1); - add_signatures(&mut slashable_attestation_2); - - AttesterSlashing { - slashable_attestation_1, - slashable_attestation_2, - } + AttesterSlashingBuilder::double_vote(validator_indices, signer, &harness.spec) } pub type DepositTuple = (u64, Deposit, Keypair); pub type ProposerSlashingTuple = (u64, ProposerSlashing); -// (slot, validator_indices) pub type AttesterSlashingTuple = (u64, Vec); struct ExecutionResult { diff --git a/eth2/types/src/attester_slashing.rs b/eth2/types/src/attester_slashing.rs index 9cb2123249..ac75a25626 100644 --- a/eth2/types/src/attester_slashing.rs +++ b/eth2/types/src/attester_slashing.rs @@ -1,9 +1,13 @@ -use crate::{test_utils::TestRandom, ChainSpec, SlashableAttestation}; +use crate::{test_utils::TestRandom, SlashableAttestation}; use rand::RngCore; use serde_derive::Serialize; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; +mod builder; + +pub use builder::AttesterSlashingBuilder; + #[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)] pub struct AttesterSlashing { pub slashable_attestation_1: SlashableAttestation, diff --git a/eth2/types/src/attester_slashing/builder.rs b/eth2/types/src/attester_slashing/builder.rs new file mode 100644 index 0000000000..d382cb64c2 --- /dev/null +++ b/eth2/types/src/attester_slashing/builder.rs @@ -0,0 +1,85 @@ +use crate::*; +use ssz::TreeHash; + +pub struct AttesterSlashingBuilder(); + +impl AttesterSlashingBuilder { + pub fn double_vote( + validator_indices: &[u64], + signer: F, + spec: &ChainSpec, + ) -> AttesterSlashing + where + F: Fn(u64, &[u8], Epoch, u64) -> Signature, + { + let double_voted_slot = Slot::new(0); + let shard = 0; + let justified_epoch = Epoch::new(0); + let epoch = Epoch::new(0); + let hash_1 = Hash256::from("1".as_bytes()); + let hash_2 = Hash256::from("2".as_bytes()); + + let mut slashable_attestation_1 = SlashableAttestation { + validator_indices: validator_indices.to_vec(), + data: AttestationData { + slot: double_voted_slot, + shard, + beacon_block_root: hash_1, + epoch_boundary_root: hash_1, + shard_block_root: hash_1, + latest_crosslink: Crosslink { + epoch, + shard_block_root: hash_1, + }, + justified_epoch, + justified_block_root: hash_1, + }, + custody_bitfield: Bitfield::new(), + aggregate_signature: AggregateSignature::new(), + }; + + let mut slashable_attestation_2 = SlashableAttestation { + validator_indices: validator_indices.to_vec(), + data: AttestationData { + slot: double_voted_slot, + shard, + beacon_block_root: hash_2, + epoch_boundary_root: hash_2, + shard_block_root: hash_2, + latest_crosslink: Crosslink { + epoch, + shard_block_root: hash_2, + }, + justified_epoch, + justified_block_root: hash_2, + }, + custody_bitfield: Bitfield::new(), + aggregate_signature: AggregateSignature::new(), + }; + + let add_signatures = |attestation: &mut SlashableAttestation| { + for (i, validator_index) in validator_indices.iter().enumerate() { + let attestation_data_and_custody_bit = AttestationDataAndCustodyBit { + data: attestation.data.clone(), + custody_bit: attestation.custody_bitfield.get(i).unwrap(), + }; + let message = attestation_data_and_custody_bit.hash_tree_root(); + let signature = signer( + *validator_index, + &message[..], + epoch, + spec.domain_attestation, + ); + attestation.aggregate_signature.add(&signature); + } + }; + + add_signatures(&mut slashable_attestation_1); + add_signatures(&mut slashable_attestation_2); + + AttesterSlashing { + slashable_attestation_1, + slashable_attestation_2, + } + } +} From 8e1380d7c4ad9afe10db13735ce52c2d6ab380cd Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 18:36:44 +1100 Subject: [PATCH 19/41] Add ProposerSlashingBuilder It is capable of producing double votes --- eth2/types/src/proposer_slashing.rs | 4 ++ eth2/types/src/proposer_slashing/builder.rs | 48 +++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 eth2/types/src/proposer_slashing/builder.rs diff --git a/eth2/types/src/proposer_slashing.rs b/eth2/types/src/proposer_slashing.rs index 610017c0cb..ea30d46ece 100644 --- a/eth2/types/src/proposer_slashing.rs +++ b/eth2/types/src/proposer_slashing.rs @@ -6,6 +6,10 @@ use serde_derive::Serialize; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; +mod builder; + +pub use builder::ProposerSlashingBuilder; + #[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)] pub struct ProposerSlashing { pub proposer_index: u64, diff --git a/eth2/types/src/proposer_slashing/builder.rs b/eth2/types/src/proposer_slashing/builder.rs new file mode 100644 index 0000000000..a43a73ff0d --- /dev/null +++ b/eth2/types/src/proposer_slashing/builder.rs @@ -0,0 +1,48 @@ +use crate::*; +use ssz::TreeHash; + +pub struct ProposerSlashingBuilder(); + +impl ProposerSlashingBuilder { + pub fn double_vote(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing + where + F: Fn(u64, &[u8], Epoch, u64) -> Signature, + { + let slot = Slot::new(0); + let shard = 0; + + let proposal_data_1 = ProposalSignedData { + slot, + shard, + block_root: Hash256::from("one".as_bytes()), + }; + + let proposal_data_2 = ProposalSignedData { + slot, + shard, + block_root: Hash256::from("two".as_bytes()), + }; + + let proposal_signature_1 = { + let message = proposal_data_1.hash_tree_root(); + let epoch = slot.epoch(spec.epoch_length); + let domain = spec.domain_proposal; + signer(proposer_index, &message[..], epoch, domain) + }; + + let proposal_signature_2 = { + let message = proposal_data_2.hash_tree_root(); + let epoch = slot.epoch(spec.epoch_length); + let domain = spec.domain_proposal; + signer(proposer_index, &message[..], epoch, domain) + }; + + ProposerSlashing { + proposer_index, + proposal_data_1, + proposal_signature_1, + proposal_data_2, + proposal_signature_2, + } + } +} From e59404f463bc0e8325decff349000cc086c969ed Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 18:37:21 +1100 Subject: [PATCH 20/41] Change test_harness proposer_slashings YAML Removes a lot of the detail from the `proposer_slashings` field -- IMO this is not necessary in the test spec, the details of how a proposer_slashing is created should be held in the program and the spec should only define that one happens. --- .../test_harness/examples/chain.yaml | 21 +++--- .../test_harness/src/beacon_chain_harness.rs | 27 +------- .../beacon_chain/test_harness/src/bin.rs | 66 +++++++------------ 3 files changed, 36 insertions(+), 78 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/examples/chain.yaml b/beacon_node/beacon_chain/test_harness/examples/chain.yaml index 4c4946c5db..9e00f4569e 100644 --- a/beacon_node/beacon_chain/test_harness/examples/chain.yaml +++ b/beacon_node/beacon_chain/test_harness/examples/chain.yaml @@ -20,19 +20,18 @@ test_cases: amount: 32 merkle_index: 2 proposer_slashings: - - slot: 8 # At slot 8, trigger a proposal slashing - proposer_index: 42 # Penalize the validator at position 42 - proposal_1_shard: 0 - proposal_1_slot: 15 - proposal_1_root: !!binary | - LkmqmqoodLKAslkjdkajsdljasdkajlksjdasldjasdd - proposal_2_shard: 0 - proposal_2_slot: 15 - proposal_2_root: !!binary | - DIFFERENTKAslkjdkajsdljasdkajlksjdasldjasdd + # At slot 2, trigger a proposer slashing for validator #42. + - slot: 2 + validator_index: 42 + # At slot 8, trigger a proposer slashing for validator #13. + - slot: 8 + validator_index: 13 attester_slashings: - # At slot 2, trigger an attester slashing for validators #11 and #12 + # At slot 2, trigger an attester slashing for validators #11 and #12. - slot: 2 validator_indices: [11, 12] + # At slot 5, trigger an attester slashing for validator #14. + - slot: 5 + validator_indices: [14] results: num_validators: 1000 diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 862e037e29..9060c4ec19 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -273,32 +273,7 @@ impl BeaconChainHarness { } } - pub fn add_proposer_slashing(&mut self, mut proposer_slashing: ProposerSlashing) { - let validator = &self.validators[proposer_slashing.proposer_index as usize]; - - // This following code is a little awkward, but managing the data_1 and data_1 was getting - // rather confusing. I think this is better - let proposals = vec![ - &proposer_slashing.proposal_data_1, - &proposer_slashing.proposal_data_2, - ]; - let signatures: Vec = proposals - .iter() - .map(|proposal_data| { - let message = proposal_data.hash_tree_root(); - let epoch = proposal_data.slot.epoch(self.spec.epoch_length); - let domain = self - .beacon_chain - .state - .read() - .fork - .get_domain(epoch, self.spec.domain_proposal); - Signature::new(&message[..], domain, &validator.keypair.sk) - }) - .collect(); - proposer_slashing.proposal_signature_1 = signatures[0].clone(); - proposer_slashing.proposal_signature_2 = signatures[1].clone(); - + pub fn add_proposer_slashing(&mut self, proposer_slashing: ProposerSlashing) { self.beacon_chain .receive_proposer_slashing_for_inclusion(proposer_slashing); } diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index fa5aa45a25..7c525c768e 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -7,8 +7,10 @@ use env_logger::{Builder, Env}; use log::{info, warn}; use ssz::TreeHash; use std::{fs::File, io::prelude::*}; -use types::attester_slashing::AttesterSlashingBuilder; use types::*; +use types::{ + attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder, +}; use yaml_rust::{Yaml, YamlLoader}; mod beacon_chain_harness; @@ -99,13 +101,14 @@ impl Manifest { // Feed proposer slashings to the BeaconChain. if let Some(ref slashings) = self.config.proposer_slashings { - for (slot, slashing) in slashings { + for (slot, validator_index) in slashings { if *slot == slot_height { info!( - "Including proposer slashing at slot height {}.", - slot_height + "Including proposer slashing at slot height {} for validator #{}.", + slot_height, validator_index ); - harness.add_proposer_slashing(slashing.clone()); + let slashing = build_proposer_slashing(&harness, *validator_index); + harness.add_proposer_slashing(slashing); } } } @@ -115,8 +118,8 @@ impl Manifest { for (slot, validator_indices) in slashings { if *slot == slot_height { info!( - "Including attester slashing at slot height {}.", - slot_height + "Including attester slashing at slot height {} for validators {:?}.", + slot_height, validator_indices ); let slashing = build_double_vote_attester_slashing(&harness, &validator_indices[..]); @@ -205,8 +208,18 @@ fn build_double_vote_attester_slashing( AttesterSlashingBuilder::double_vote(validator_indices, signer, &harness.spec) } +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) +} + pub type DepositTuple = (u64, Deposit, Keypair); -pub type ProposerSlashingTuple = (u64, ProposerSlashing); +pub type ProposerSlashingTuple = (u64, u64); pub type AttesterSlashingTuple = (u64, Vec); struct ExecutionResult { @@ -272,40 +285,11 @@ fn parse_proposer_slashings(yaml: &Yaml) -> Option> { let mut slashings = vec![]; for slashing in yaml["proposer_slashings"].as_vec()? { - let slot = as_u64(slashing, "slot").expect("Incomplete slashing"); + let slot = as_u64(slashing, "slot").expect("Incomplete proposer slashing (slot)_"); + let validator_index = as_u64(slashing, "validator_index") + .expect("Incomplete proposer slashing (validator_index)"); - // Builds a ProposerSlashing object from YAML fields. - // - // Rustfmt make this look rather ugly, however it is just a simple struct - // instantiation. - let slashing = ProposerSlashing { - proposer_index: as_u64(slashing, "proposer_index") - .expect("Incomplete slashing (proposer_index)"), - proposal_data_1: ProposalSignedData { - slot: Slot::from( - as_u64(slashing, "proposal_1_slot") - .expect("Incomplete slashing (proposal_1_slot)."), - ), - shard: as_u64(slashing, "proposal_1_shard") - .expect("Incomplete slashing (proposal_1_shard)."), - block_root: as_hash256(slashing, "proposal_1_root") - .expect("Incomplete slashing (proposal_1_root)."), - }, - proposal_signature_1: Signature::empty_signature(), // Will be replaced with real signature at runtime. - proposal_data_2: ProposalSignedData { - slot: Slot::from( - as_u64(slashing, "proposal_2_slot") - .expect("Incomplete slashing (proposal_2_slot)."), - ), - shard: as_u64(slashing, "proposal_2_shard") - .expect("Incomplete slashing (proposal_2_shard)."), - block_root: as_hash256(slashing, "proposal_2_root") - .expect("Incomplete slashing (proposal_2_root)."), - }, - proposal_signature_2: Signature::empty_signature(), // Will be replaced with real signature at runtime. - }; - - slashings.push((slot, slashing)); + slashings.push((slot, validator_index)); } Some(slashings) From c975d49ead55a9535dc15e778161d0f8b690869f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 18:39:52 +1100 Subject: [PATCH 21/41] Copy SlashableVote.. tests to SlashableAttestation SlashableVoteData tests were just copied directly across --- eth2/types/src/slashable_attestation.rs | 87 +++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/eth2/types/src/slashable_attestation.rs b/eth2/types/src/slashable_attestation.rs index d24c5dde42..8ad582ce64 100644 --- a/eth2/types/src/slashable_attestation.rs +++ b/eth2/types/src/slashable_attestation.rs @@ -36,9 +36,83 @@ impl SlashableAttestation { #[cfg(test)] mod tests { use super::*; + use crate::chain_spec::ChainSpec; + use crate::slot_epoch::{Epoch, Slot}; use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; use ssz::{ssz_encode, Decodable, TreeHash}; + #[test] + pub fn test_is_double_vote_true() { + let spec = ChainSpec::foundation(); + let slashable_vote_first = create_slashable_attestation(1, 1, &spec); + let slashable_vote_second = create_slashable_attestation(1, 1, &spec); + + assert_eq!( + slashable_vote_first.is_double_vote(&slashable_vote_second, &spec), + true + ) + } + + #[test] + pub fn test_is_double_vote_false() { + let spec = ChainSpec::foundation(); + let slashable_vote_first = create_slashable_attestation(1, 1, &spec); + let slashable_vote_second = create_slashable_attestation(2, 1, &spec); + + assert_eq!( + slashable_vote_first.is_double_vote(&slashable_vote_second, &spec), + false + ); + } + + #[test] + pub fn test_is_surround_vote_true() { + let spec = ChainSpec::foundation(); + let slashable_vote_first = create_slashable_attestation(2, 1, &spec); + let slashable_vote_second = create_slashable_attestation(1, 2, &spec); + + assert_eq!( + slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), + true + ); + } + + #[test] + pub fn test_is_surround_vote_true_realistic() { + let spec = ChainSpec::foundation(); + let slashable_vote_first = create_slashable_attestation(4, 1, &spec); + let slashable_vote_second = create_slashable_attestation(3, 2, &spec); + + assert_eq!( + slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), + true + ); + } + + #[test] + pub fn test_is_surround_vote_false_source_epoch_fails() { + let spec = ChainSpec::foundation(); + let slashable_vote_first = create_slashable_attestation(2, 2, &spec); + let slashable_vote_second = create_slashable_attestation(1, 1, &spec); + + assert_eq!( + slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), + false + ); + } + + #[test] + pub fn test_is_surround_vote_false_target_epoch_fails() { + let spec = ChainSpec::foundation(); + let slashable_vote_first = create_slashable_attestation(1, 1, &spec); + let slashable_vote_second = create_slashable_attestation(2, 2, &spec); + + assert_eq!( + slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), + false + ); + } + #[test] pub fn test_ssz_round_trip() { let mut rng = XorShiftRng::from_seed([42; 16]); @@ -61,4 +135,17 @@ mod tests { // TODO: Add further tests // https://github.com/sigp/lighthouse/issues/170 } + + fn create_slashable_attestation( + slot_factor: u64, + justified_epoch: u64, + spec: &ChainSpec, + ) -> SlashableAttestation { + let mut rng = XorShiftRng::from_seed([42; 16]); + let mut slashable_vote = SlashableAttestation::random_for_test(&mut rng); + + slashable_vote.data.slot = Slot::new(slot_factor * spec.epoch_length); + slashable_vote.data.justified_epoch = Epoch::new(justified_epoch); + slashable_vote + } } From db28cc1b9270895580c651869dfe2f4a8d5d7ce6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 18:43:27 +1100 Subject: [PATCH 22/41] Fix warnings in test_harness/src/bin.rs --- beacon_node/beacon_chain/test_harness/src/bin.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index 7c525c768e..1820214403 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -5,7 +5,6 @@ use bls::create_proof_of_possession; use clap::{App, Arg}; use env_logger::{Builder, Env}; use log::{info, warn}; -use ssz::TreeHash; use std::{fs::File, io::prelude::*}; use types::*; use types::{ @@ -331,12 +330,6 @@ fn as_u64(yaml: &Yaml, key: &str) -> Option { yaml[key].as_i64().and_then(|n| Some(n as u64)) } -fn as_hash256(yaml: &Yaml, key: &str) -> Option { - yaml[key] - .as_str() - .and_then(|s| Some(Hash256::from(s.as_bytes()))) -} - fn as_vec_u64(yaml: &Yaml, key: &str) -> Option> { yaml[key].clone().into_vec().and_then(|vec| { Some( From f5614381e122900d21ee4bfae3f3b69a9736a998 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 18:59:47 +1100 Subject: [PATCH 23/41] Re-organise test_harness binary Moves manifest and components into separate files. --- .../beacon_chain/test_harness/src/bin.rs | 305 +----------------- .../test_harness/src/manifest/config.rs | 89 +++++ .../test_harness/src/manifest/mod.rs | 185 +++++++++++ .../test_harness/src/manifest/results.rs | 18 ++ .../test_harness/src/manifest/yaml_helpers.rs | 19 ++ 5 files changed, 316 insertions(+), 300 deletions(-) create mode 100644 beacon_node/beacon_chain/test_harness/src/manifest/config.rs create mode 100644 beacon_node/beacon_chain/test_harness/src/manifest/mod.rs create mode 100644 beacon_node/beacon_chain/test_harness/src/manifest/results.rs create mode 100644 beacon_node/beacon_chain/test_harness/src/manifest/yaml_helpers.rs diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index 1820214403..b6a0529c7d 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -1,20 +1,15 @@ -use self::beacon_chain_harness::BeaconChainHarness; -use self::validator_harness::ValidatorHarness; -use beacon_chain::CheckPoint; -use bls::create_proof_of_possession; use clap::{App, Arg}; use env_logger::{Builder, Env}; -use log::{info, warn}; +use manifest::Manifest; use std::{fs::File, io::prelude::*}; -use types::*; -use types::{ - attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder, -}; -use yaml_rust::{Yaml, YamlLoader}; +use yaml_rust::YamlLoader; mod beacon_chain_harness; +mod manifest; mod validator_harness; +use validator_harness::ValidatorHarness; + fn main() { let matches = App::new("Lighthouse Test Harness Runner") .version("0.0.1") @@ -49,293 +44,3 @@ fn main() { } } } - -struct Manifest { - pub results: Results, - pub config: Config, -} - -impl Manifest { - pub fn from_yaml(test_case: &Yaml) -> Self { - Self { - results: Results::from_yaml(&test_case["results"]), - config: Config::from_yaml(&test_case["config"]), - } - } - - fn spec(&self) -> ChainSpec { - let mut spec = ChainSpec::foundation(); - - if let Some(n) = self.config.epoch_length { - spec.epoch_length = n; - } - - spec - } - - 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); - - for slot_height in 0..slots { - // 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!"); - - ExecutionResult { - chain: harness.chain_dump().expect("Chain dump failed."), - } - } - - pub fn assert_result_valid(&self, result: ExecutionResult) { - info!("Verifying test results..."); - - let skipped_slots = self - .config - .skip_slots - .clone() - .and_then(|slots| Some(slots.len())) - .unwrap_or_else(|| 0); - let expected_blocks = self.config.num_slots as usize + 1 - skipped_slots; - - assert_eq!(result.chain.len(), expected_blocks); - - info!( - "OK: Chain length is {} ({} skipped slots).", - result.chain.len(), - skipped_slots - ); - - if let Some(ref skip_slots) = self.config.skip_slots { - for checkpoint in &result.chain { - let block_slot = checkpoint.beacon_block.slot.as_u64(); - assert!( - !skip_slots.contains(&block_slot), - "Slot {} was not skipped.", - block_slot - ); - } - info!("OK: Skipped slots not present in chain."); - } - - if let Some(ref deposits) = self.config.deposits { - let latest_state = &result.chain.last().expect("Empty chain.").beacon_state; - assert_eq!( - latest_state.validator_registry.len(), - self.config.deposits_for_chain_start + deposits.len() - ); - info!( - "OK: Validator registry has {} more validators.", - deposits.len() - ); - } - } -} - -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) -} - -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) -} - -pub type DepositTuple = (u64, Deposit, Keypair); -pub type ProposerSlashingTuple = (u64, u64); -pub type AttesterSlashingTuple = (u64, Vec); - -struct ExecutionResult { - pub chain: Vec, -} - -struct Results { - pub num_validators: Option, - pub slashed_validators: Option>, - pub exited_validators: Option>, -} - -impl Results { - pub fn from_yaml(yaml: &Yaml) -> Self { - Self { - num_validators: as_usize(&yaml, "num_validators"), - slashed_validators: as_vec_u64(&yaml, "slashed_validators"), - exited_validators: as_vec_u64(&yaml, "exited_validators"), - } - } -} - -struct Config { - pub deposits_for_chain_start: usize, - pub epoch_length: Option, - pub num_slots: u64, - pub skip_slots: Option>, - pub deposits: Option>, - pub proposer_slashings: Option>, - pub attester_slashings: Option>, -} - -impl Config { - pub fn from_yaml(yaml: &Yaml) -> Self { - Self { - deposits_for_chain_start: as_usize(&yaml, "deposits_for_chain_start") - .expect("Must specify validator count"), - epoch_length: as_u64(&yaml, "epoch_length"), - num_slots: as_u64(&yaml, "num_slots").expect("Must specify `config.num_slots`"), - skip_slots: as_vec_u64(yaml, "skip_slots"), - deposits: parse_deposits(&yaml), - proposer_slashings: parse_proposer_slashings(&yaml), - attester_slashings: parse_attester_slashings(&yaml), - } - } -} - -fn parse_attester_slashings(yaml: &Yaml) -> Option> { - let mut slashings = vec![]; - - for slashing in yaml["attester_slashings"].as_vec()? { - let slot = as_u64(slashing, "slot").expect("Incomplete attester_slashing (slot)"); - let validator_indices = as_vec_u64(slashing, "validator_indices") - .expect("Incomplete attester_slashing (validator_indices)"); - - slashings.push((slot, validator_indices)); - } - - Some(slashings) -} - -fn parse_proposer_slashings(yaml: &Yaml) -> Option> { - let mut slashings = vec![]; - - for slashing in yaml["proposer_slashings"].as_vec()? { - let slot = as_u64(slashing, "slot").expect("Incomplete proposer slashing (slot)_"); - let validator_index = as_u64(slashing, "validator_index") - .expect("Incomplete proposer slashing (validator_index)"); - - slashings.push((slot, validator_index)); - } - - Some(slashings) -} - -fn parse_deposits(yaml: &Yaml) -> Option> { - let mut deposits = vec![]; - - for deposit in yaml["deposits"].as_vec()? { - let keypair = Keypair::random(); - let proof_of_possession = create_proof_of_possession(&keypair); - - let slot = as_u64(deposit, "slot").expect("Incomplete deposit"); - let deposit = Deposit { - branch: vec![], - index: as_u64(deposit, "merkle_index").unwrap(), - deposit_data: DepositData { - amount: 32_000_000_000, - timestamp: 1, - deposit_input: DepositInput { - pubkey: keypair.pk.clone(), - withdrawal_credentials: Hash256::zero(), - proof_of_possession, - }, - }, - }; - - deposits.push((slot, deposit, keypair)); - } - - Some(deposits) -} - -fn as_usize(yaml: &Yaml, key: &str) -> Option { - yaml[key].as_i64().and_then(|n| Some(n as usize)) -} - -fn as_u64(yaml: &Yaml, key: &str) -> Option { - yaml[key].as_i64().and_then(|n| Some(n as u64)) -} - -fn as_vec_u64(yaml: &Yaml, key: &str) -> Option> { - yaml[key].clone().into_vec().and_then(|vec| { - Some( - vec.iter() - .map(|item| item.as_i64().unwrap() as u64) - .collect(), - ) - }) -} diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/config.rs b/beacon_node/beacon_chain/test_harness/src/manifest/config.rs new file mode 100644 index 0000000000..f960a0bb84 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/manifest/config.rs @@ -0,0 +1,89 @@ +use super::yaml_helpers::{as_u64, as_usize, as_vec_u64}; +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); + +pub struct Config { + pub deposits_for_chain_start: usize, + pub epoch_length: Option, + pub num_slots: u64, + pub skip_slots: Option>, + pub deposits: Option>, + pub proposer_slashings: Option>, + pub attester_slashings: Option>, +} + +impl Config { + pub fn from_yaml(yaml: &Yaml) -> Self { + Self { + deposits_for_chain_start: as_usize(&yaml, "deposits_for_chain_start") + .expect("Must specify validator count"), + epoch_length: as_u64(&yaml, "epoch_length"), + num_slots: as_u64(&yaml, "num_slots").expect("Must specify `config.num_slots`"), + skip_slots: as_vec_u64(yaml, "skip_slots"), + deposits: parse_deposits(&yaml), + proposer_slashings: parse_proposer_slashings(&yaml), + attester_slashings: parse_attester_slashings(&yaml), + } + } +} + +fn parse_attester_slashings(yaml: &Yaml) -> Option> { + let mut slashings = vec![]; + + for slashing in yaml["attester_slashings"].as_vec()? { + let slot = as_u64(slashing, "slot").expect("Incomplete attester_slashing (slot)"); + let validator_indices = as_vec_u64(slashing, "validator_indices") + .expect("Incomplete attester_slashing (validator_indices)"); + + slashings.push((slot, validator_indices)); + } + + Some(slashings) +} + +fn parse_proposer_slashings(yaml: &Yaml) -> Option> { + let mut slashings = vec![]; + + for slashing in yaml["proposer_slashings"].as_vec()? { + let slot = as_u64(slashing, "slot").expect("Incomplete proposer slashing (slot)_"); + let validator_index = as_u64(slashing, "validator_index") + .expect("Incomplete proposer slashing (validator_index)"); + + slashings.push((slot, validator_index)); + } + + Some(slashings) +} + +fn parse_deposits(yaml: &Yaml) -> Option> { + let mut deposits = vec![]; + + for deposit in yaml["deposits"].as_vec()? { + let keypair = Keypair::random(); + let proof_of_possession = create_proof_of_possession(&keypair); + + let slot = as_u64(deposit, "slot").expect("Incomplete deposit"); + let deposit = Deposit { + branch: vec![], + index: as_u64(deposit, "merkle_index").unwrap(), + deposit_data: DepositData { + amount: 32_000_000_000, + timestamp: 1, + deposit_input: DepositInput { + pubkey: keypair.pk.clone(), + withdrawal_credentials: Hash256::zero(), + proof_of_possession, + }, + }, + }; + + deposits.push((slot, deposit, keypair)); + } + + Some(deposits) +} diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs b/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs new file mode 100644 index 0000000000..3149a7d169 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs @@ -0,0 +1,185 @@ +use self::config::Config; +use self::results::Results; +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 yaml_helpers; + +pub struct Manifest { + pub results: Results, + pub config: Config, +} + +pub struct ExecutionResult { + pub chain: Vec, +} + +impl Manifest { + pub fn from_yaml(test_case: &Yaml) -> Self { + Self { + results: Results::from_yaml(&test_case["results"]), + config: Config::from_yaml(&test_case["config"]), + } + } + + fn spec(&self) -> ChainSpec { + let mut spec = ChainSpec::foundation(); + + if let Some(n) = self.config.epoch_length { + spec.epoch_length = n; + } + + spec + } + + 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); + + for slot_height in 0..slots { + // 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!"); + + ExecutionResult { + chain: harness.chain_dump().expect("Chain dump failed."), + } + } + + pub fn assert_result_valid(&self, result: ExecutionResult) { + info!("Verifying test results..."); + + let skipped_slots = self + .config + .skip_slots + .clone() + .and_then(|slots| Some(slots.len())) + .unwrap_or_else(|| 0); + let expected_blocks = self.config.num_slots as usize + 1 - skipped_slots; + + assert_eq!(result.chain.len(), expected_blocks); + + info!( + "OK: Chain length is {} ({} skipped slots).", + result.chain.len(), + skipped_slots + ); + + if let Some(ref skip_slots) = self.config.skip_slots { + for checkpoint in &result.chain { + let block_slot = checkpoint.beacon_block.slot.as_u64(); + assert!( + !skip_slots.contains(&block_slot), + "Slot {} was not skipped.", + block_slot + ); + } + info!("OK: Skipped slots not present in chain."); + } + + if let Some(ref deposits) = self.config.deposits { + let latest_state = &result.chain.last().expect("Empty chain.").beacon_state; + assert_eq!( + latest_state.validator_registry.len(), + self.config.deposits_for_chain_start + deposits.len() + ); + info!( + "OK: Validator registry has {} more validators.", + deposits.len() + ); + } + } +} + +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) +} + +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) +} diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/results.rs b/beacon_node/beacon_chain/test_harness/src/manifest/results.rs new file mode 100644 index 0000000000..2d84e12dcc --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/manifest/results.rs @@ -0,0 +1,18 @@ +use super::yaml_helpers::{as_usize, as_vec_u64}; +use yaml_rust::Yaml; + +pub struct Results { + pub num_validators: Option, + pub slashed_validators: Option>, + pub exited_validators: Option>, +} + +impl Results { + pub fn from_yaml(yaml: &Yaml) -> Self { + Self { + num_validators: as_usize(&yaml, "num_validators"), + slashed_validators: as_vec_u64(&yaml, "slashed_validators"), + exited_validators: as_vec_u64(&yaml, "exited_validators"), + } + } +} diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/yaml_helpers.rs b/beacon_node/beacon_chain/test_harness/src/manifest/yaml_helpers.rs new file mode 100644 index 0000000000..c499b3c0f9 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/manifest/yaml_helpers.rs @@ -0,0 +1,19 @@ +use yaml_rust::Yaml; + +pub fn as_usize(yaml: &Yaml, key: &str) -> Option { + yaml[key].as_i64().and_then(|n| Some(n as usize)) +} + +pub fn as_u64(yaml: &Yaml, key: &str) -> Option { + yaml[key].as_i64().and_then(|n| Some(n as u64)) +} + +pub fn as_vec_u64(yaml: &Yaml, key: &str) -> Option> { + yaml[key].clone().into_vec().and_then(|vec| { + Some( + vec.iter() + .map(|item| item.as_i64().unwrap() as u64) + .collect(), + ) + }) +} From 4db2f082e133697493794194bf29750a371d429d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 20:17:14 +1100 Subject: [PATCH 24/41] Add state-checks to test_harness YAML Runs tests against a state at some slot --- .../test_harness/examples/chain.yaml | 11 ++- .../test_harness/src/manifest/config.rs | 1 + .../test_harness/src/manifest/mod.rs | 66 +++++++-------- .../test_harness/src/manifest/results.rs | 24 ++++-- .../test_harness/src/manifest/state_check.rs | 84 +++++++++++++++++++ eth2/types/src/validator.rs | 12 ++- 6 files changed, 151 insertions(+), 47 deletions(-) create mode 100644 beacon_node/beacon_chain/test_harness/src/manifest/state_check.rs diff --git a/beacon_node/beacon_chain/test_harness/examples/chain.yaml b/beacon_node/beacon_chain/test_harness/examples/chain.yaml index 9e00f4569e..f22d0874e4 100644 --- a/beacon_node/beacon_chain/test_harness/examples/chain.yaml +++ b/beacon_node/beacon_chain/test_harness/examples/chain.yaml @@ -7,7 +7,7 @@ test_cases: - config: epoch_length: 64 deposits_for_chain_start: 1000 - num_slots: 65 + num_slots: 64 skip_slots: [2, 3] deposits: - slot: 1 @@ -34,4 +34,11 @@ test_cases: - slot: 5 validator_indices: [14] results: - num_validators: 1000 + num_skipped_slots: 2 + states: + - slot: 63 + num_validators: 1003 + # slashed_validators: [11, 12, 13, 14, 42] + slashed_validators: [13, 42] # This line is incorrect, our implementation isn't processing attester_slashings. + exited_validators: [] + diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/config.rs b/beacon_node/beacon_chain/test_harness/src/manifest/config.rs index f960a0bb84..0e66a120be 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/config.rs +++ b/beacon_node/beacon_chain/test_harness/src/manifest/config.rs @@ -7,6 +7,7 @@ pub type DepositTuple = (u64, Deposit, Keypair); pub type ProposerSlashingTuple = (u64, u64); pub type AttesterSlashingTuple = (u64, Vec); +#[derive(Debug)] pub struct Config { pub deposits_for_chain_start: usize, pub epoch_length: Option, diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs b/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs index 3149a7d169..d169122055 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs +++ b/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs @@ -11,8 +11,10 @@ use yaml_rust::Yaml; mod config; mod results; +mod state_check; mod yaml_helpers; +#[derive(Debug)] pub struct Manifest { pub results: Results, pub config: Config, @@ -20,6 +22,7 @@ pub struct Manifest { pub struct ExecutionResult { pub chain: Vec, + pub spec: ChainSpec, } impl Manifest { @@ -54,7 +57,8 @@ impl Manifest { info!("Starting simulation across {} slots...", slots); - for slot_height in 0..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 { @@ -113,51 +117,41 @@ impl Manifest { ExecutionResult { chain: harness.chain_dump().expect("Chain dump failed."), + spec: (*harness.spec).clone(), } } - pub fn assert_result_valid(&self, result: ExecutionResult) { + pub fn assert_result_valid(&self, execution_result: ExecutionResult) { info!("Verifying test results..."); + let spec = &execution_result.spec; - let skipped_slots = self - .config - .skip_slots - .clone() - .and_then(|slots| Some(slots.len())) - .unwrap_or_else(|| 0); - let expected_blocks = self.config.num_slots as usize + 1 - skipped_slots; - - assert_eq!(result.chain.len(), expected_blocks); - - info!( - "OK: Chain length is {} ({} skipped slots).", - result.chain.len(), - skipped_slots - ); - - if let Some(ref skip_slots) = self.config.skip_slots { - for checkpoint in &result.chain { - let block_slot = checkpoint.beacon_block.slot.as_u64(); - assert!( - !skip_slots.contains(&block_slot), - "Slot {} was not skipped.", - block_slot - ); - } - info!("OK: Skipped slots not present in chain."); - } - - if let Some(ref deposits) = self.config.deposits { - let latest_state = &result.chain.last().expect("Empty chain.").beacon_state; + if let Some(num_skipped_slots) = self.results.num_skipped_slots { assert_eq!( - latest_state.validator_registry.len(), - self.config.deposits_for_chain_start + deposits.len() + execution_result.chain.len(), + self.config.num_slots as usize - num_skipped_slots, + "actual skipped slots != expected." ); info!( - "OK: Validator registry has {} more validators.", - deposits.len() + "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.epoch_length); + + if state_check.slot == adjusted_state_slot { + state_check.assert_valid(state, spec); + } + } + } + } } } diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/results.rs b/beacon_node/beacon_chain/test_harness/src/manifest/results.rs index 2d84e12dcc..8446959103 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/results.rs +++ b/beacon_node/beacon_chain/test_harness/src/manifest/results.rs @@ -1,18 +1,28 @@ -use super::yaml_helpers::{as_usize, as_vec_u64}; +use super::state_check::StateCheck; +use super::yaml_helpers::as_usize; use yaml_rust::Yaml; +#[derive(Debug)] pub struct Results { - pub num_validators: Option, - pub slashed_validators: Option>, - pub exited_validators: Option>, + pub num_skipped_slots: Option, + pub state_checks: Option>, } impl Results { pub fn from_yaml(yaml: &Yaml) -> Self { Self { - num_validators: as_usize(&yaml, "num_validators"), - slashed_validators: as_vec_u64(&yaml, "slashed_validators"), - exited_validators: as_vec_u64(&yaml, "exited_validators"), + num_skipped_slots: as_usize(yaml, "num_skipped_slots"), + state_checks: parse_state_checks(yaml), } } } + +fn parse_state_checks(yaml: &Yaml) -> Option> { + let mut states = vec![]; + + for state_yaml in yaml["states"].as_vec()? { + states.push(StateCheck::from_yaml(state_yaml)); + } + + Some(states) +} diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/state_check.rs b/beacon_node/beacon_chain/test_harness/src/manifest/state_check.rs new file mode 100644 index 0000000000..0415d4896b --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/manifest/state_check.rs @@ -0,0 +1,84 @@ +use super::yaml_helpers::{as_u64, as_usize, as_vec_u64}; +use log::info; +use types::*; +use yaml_rust::Yaml; + +#[derive(Debug)] +pub struct StateCheck { + pub slot: Slot, + pub num_validators: Option, + pub slashed_validators: Option>, + pub exited_validators: Option>, +} + +impl StateCheck { + pub fn from_yaml(yaml: &Yaml) -> Self { + Self { + slot: Slot::from(as_u64(&yaml, "slot").expect("State must specify slot")), + num_validators: as_usize(&yaml, "num_validators"), + slashed_validators: as_vec_u64(&yaml, "slashed_validators"), + exited_validators: as_vec_u64(&yaml, "exited_validators"), + } + } + + pub fn assert_valid(&self, state: &BeaconState, spec: &ChainSpec) { + let state_epoch = state.slot.epoch(spec.epoch_length); + + info!("Running state check for slot height {}.", self.slot); + + assert_eq!( + self.slot, + state.slot - spec.genesis_epoch.start_slot(spec.epoch_length), + "State slot is invalid." + ); + + if let Some(num_validators) = self.num_validators { + assert_eq!( + state.validator_registry.len(), + num_validators, + "State validator count != expected." + ); + info!("OK: num_validators = {}.", num_validators); + } + + if let Some(ref slashed_validators) = self.slashed_validators { + let actually_slashed_validators: Vec = state + .validator_registry + .iter() + .enumerate() + .filter_map(|(i, validator)| { + if validator.is_penalized_at(state_epoch) { + Some(i as u64) + } else { + None + } + }) + .collect(); + assert_eq!( + actually_slashed_validators, *slashed_validators, + "Slashed validators != expected." + ); + info!("OK: slashed_validators = {:?}.", slashed_validators); + } + + if let Some(ref exited_validators) = self.exited_validators { + let actually_exited_validators: Vec = state + .validator_registry + .iter() + .enumerate() + .filter_map(|(i, validator)| { + if validator.is_exited_at(state_epoch) { + Some(i as u64) + } else { + None + } + }) + .collect(); + assert_eq!( + actually_exited_validators, *exited_validators, + "Exited validators != expected." + ); + info!("OK: exited_validators = {:?}.", exited_validators); + } + } +} diff --git a/eth2/types/src/validator.rs b/eth2/types/src/validator.rs index b832283a0d..bc8d467ec7 100644 --- a/eth2/types/src/validator.rs +++ b/eth2/types/src/validator.rs @@ -55,8 +55,16 @@ pub struct Validator { impl Validator { /// This predicate indicates if the validator represented by this record is considered "active" at `slot`. - pub fn is_active_at(&self, slot: Epoch) -> bool { - self.activation_epoch <= slot && slot < self.exit_epoch + pub fn is_active_at(&self, epoch: Epoch) -> bool { + self.activation_epoch <= epoch && epoch < self.exit_epoch + } + + pub fn is_exited_at(&self, epoch: Epoch) -> bool { + self.exit_epoch <= epoch + } + + pub fn is_penalized_at(&self, epoch: Epoch) -> bool { + self.penalized_epoch <= epoch } } From 9156aa220388bc6e734c41df805dc1d255827542 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 2 Mar 2019 20:20:06 +1100 Subject: [PATCH 25/41] Add info log when building test_harness chain dump It helps people know why they're waiting --- beacon_node/beacon_chain/test_harness/src/manifest/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs b/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs index d169122055..4b8385035e 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs +++ b/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs @@ -115,6 +115,8 @@ impl Manifest { info!("Test execution complete!"); + info!("Building chain dump for analysis..."); + ExecutionResult { chain: harness.chain_dump().expect("Chain dump failed."), spec: (*harness.spec).clone(), From 35ae1b6745bc2dda69c2d6787cdb09018905b71d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 11:10:38 +1100 Subject: [PATCH 26/41] Add agg_pub to bls, add agg_sig.verify_multiple - Adds a new-type wrapper for `AggregatePublicKey`, just like all the other types. - Adds the `verify_multiple` method to the `AggregateSignature` newtype, as was introduced in a recent version of signature-schemes. --- eth2/utils/bls/src/aggregate_public_key.rs | 24 ++++++++++++++ eth2/utils/bls/src/aggregate_signature.rs | 37 ++++++++++++++++++++-- eth2/utils/bls/src/lib.rs | 4 +-- 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 eth2/utils/bls/src/aggregate_public_key.rs diff --git a/eth2/utils/bls/src/aggregate_public_key.rs b/eth2/utils/bls/src/aggregate_public_key.rs new file mode 100644 index 0000000000..dcb08126c9 --- /dev/null +++ b/eth2/utils/bls/src/aggregate_public_key.rs @@ -0,0 +1,24 @@ +use super::PublicKey; +use bls_aggregates::AggregatePublicKey as RawAggregatePublicKey; + +/// A single BLS signature. +/// +/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ +/// serialization). +#[derive(Debug, Clone)] +pub struct AggregatePublicKey(RawAggregatePublicKey); + +impl AggregatePublicKey { + pub fn new() -> Self { + AggregatePublicKey(RawAggregatePublicKey::new()) + } + + pub fn add(&mut self, public_key: &PublicKey) { + self.0.add(public_key.as_raw()) + } + + /// Returns the underlying signature. + pub fn as_raw(&self) -> &RawAggregatePublicKey { + &self.0 + } +} diff --git a/eth2/utils/bls/src/aggregate_signature.rs b/eth2/utils/bls/src/aggregate_signature.rs index 4ee79d0aa8..2d8776353f 100644 --- a/eth2/utils/bls/src/aggregate_signature.rs +++ b/eth2/utils/bls/src/aggregate_signature.rs @@ -1,5 +1,7 @@ use super::{AggregatePublicKey, Signature}; -use bls_aggregates::AggregateSignature as RawAggregateSignature; +use bls_aggregates::{ + AggregatePublicKey as RawAggregatePublicKey, AggregateSignature as RawAggregateSignature, +}; use serde::ser::{Serialize, Serializer}; use ssz::{ decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash, @@ -33,7 +35,38 @@ impl AggregateSignature { domain: u64, aggregate_public_key: &AggregatePublicKey, ) -> bool { - self.0.verify(msg, domain, aggregate_public_key) + self.0.verify(msg, domain, aggregate_public_key.as_raw()) + } + + /// Verify this AggregateSignature against multiple AggregatePublickeys with multiple Messages. + /// + /// All PublicKeys related to a Message should be aggregated into one AggregatePublicKey. + /// Each AggregatePublicKey has a 1:1 ratio with a 32 byte Message. + pub fn verify_multiple( + &self, + messages: &[&[u8]], + domain: u64, + aggregate_public_keys: &[&AggregatePublicKey], + ) -> bool { + // TODO: the API for `RawAggregatePublicKey` shoudn't need to take an owned + // `AggregatePublicKey`. There is an issue to fix this, but in the meantime we need to + // clone. + // + // https://github.com/sigp/signature-schemes/issues/10 + let aggregate_public_keys: Vec = aggregate_public_keys + .iter() + .map(|pk| pk.as_raw()) + .cloned() + .collect(); + + // Messages are concatenated into one long message. + let mut msg: Vec = vec![]; + for message in messages { + msg.extend_from_slice(message); + } + + self.0 + .verify_multiple(&msg[..], domain, &aggregate_public_keys[..]) } } diff --git a/eth2/utils/bls/src/lib.rs b/eth2/utils/bls/src/lib.rs index 8f2e9fac03..865b8d82d2 100644 --- a/eth2/utils/bls/src/lib.rs +++ b/eth2/utils/bls/src/lib.rs @@ -1,20 +1,20 @@ extern crate bls_aggregates; extern crate ssz; +mod aggregate_public_key; mod aggregate_signature; mod keypair; mod public_key; mod secret_key; mod signature; +pub use crate::aggregate_public_key::AggregatePublicKey; pub use crate::aggregate_signature::AggregateSignature; pub use crate::keypair::Keypair; pub use crate::public_key::PublicKey; pub use crate::secret_key::SecretKey; pub use crate::signature::Signature; -pub use self::bls_aggregates::AggregatePublicKey; - pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96; use ssz::ssz_encode; From 3561d44cbe38b13b5faa485aa38c706df71c38cd Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 11:12:18 +1100 Subject: [PATCH 27/41] Update per-block processing for new AggPub wrapper AggregatePublicKey newtype was introduced in previous commit --- eth2/state_processing/src/block_processable.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/eth2/state_processing/src/block_processable.rs b/eth2/state_processing/src/block_processable.rs index 4083d17d8c..e434b0c832 100644 --- a/eth2/state_processing/src/block_processable.rs +++ b/eth2/state_processing/src/block_processable.rs @@ -383,11 +383,7 @@ fn validate_attestation_signature_optional( ); let mut group_public_key = AggregatePublicKey::new(); for participant in participants { - group_public_key.add( - state.validator_registry[participant as usize] - .pubkey - .as_raw(), - ) + group_public_key.add(&state.validator_registry[participant as usize].pubkey) } ensure!( attestation.verify_signature( From 59128f842af60fcc42f9d2a6449ec44e00409d5e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 11:16:59 +1100 Subject: [PATCH 28/41] Add `verify_slashable_attestation` spec method As per v0.2.0 spec --- eth2/types/src/beacon_state.rs | 108 +++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 5 deletions(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 6dfbf78eed..505d4d9def 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1,10 +1,6 @@ use self::epoch_cache::EpochCache; use crate::test_utils::TestRandom; -use crate::{ - validator::StatusFlags, validator_registry::get_active_validator_indices, AttestationData, - Bitfield, ChainSpec, Crosslink, Deposit, DepositData, DepositInput, Epoch, Eth1Data, - Eth1DataVote, Fork, Hash256, PendingAttestation, PublicKey, Signature, Slot, Validator, -}; +use crate::{validator::StatusFlags, validator_registry::get_active_validator_indices, *}; use bls::verify_proof_of_possession; use honey_badger_split::SplitExt; use log::{debug, error, trace}; @@ -1145,6 +1141,108 @@ impl BeaconState { ) } + pub fn verify_bitfield(&self, bitfield: &Bitfield, committee_size: usize) -> bool { + if bitfield.num_bytes() != ((committee_size + 7) / 8) { + return false; + } + + for i in committee_size..(bitfield.num_bytes() * 8) { + match bitfield.get(i) { + Ok(bit) => { + if bit { + return false; + } + } + Err(_) => unreachable!(), + } + } + + true + } + + pub fn verify_slashable_attestation( + &self, + slashable_attestation: &SlashableAttestation, + spec: &ChainSpec, + ) -> bool { + if slashable_attestation.custody_bitfield.num_set_bits() > 0 { + return false; + } + + if slashable_attestation.validator_indices.is_empty() { + return false; + } + + for i in 0..(slashable_attestation.validator_indices.len() - 1) { + if slashable_attestation.validator_indices[i] + >= slashable_attestation.validator_indices[i + 1] + { + return false; + } + } + + if !self.verify_bitfield( + &slashable_attestation.custody_bitfield, + slashable_attestation.validator_indices.len(), + ) { + return false; + } + + if slashable_attestation.validator_indices.len() + > spec.max_indices_per_slashable_vote as usize + { + return false; + } + + let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2]; + let mut message_exists = vec![false; 2]; + + for (i, v) in slashable_attestation.validator_indices.iter().enumerate() { + let custody_bit = match slashable_attestation.custody_bitfield.get(i) { + Ok(bit) => bit, + Err(_) => unreachable!(), + }; + + message_exists[custody_bit as usize] = true; + + match self.validator_registry.get(*v as usize) { + Some(validator) => { + aggregate_pubs[custody_bit as usize].add(&validator.pubkey); + } + None => return false, + }; + } + + let message_0 = AttestationDataAndCustodyBit { + data: slashable_attestation.data.clone(), + custody_bit: false, + } + .hash_tree_root(); + let message_1 = AttestationDataAndCustodyBit { + data: slashable_attestation.data.clone(), + custody_bit: true, + } + .hash_tree_root(); + + let mut messages = vec![]; + let mut keys = vec![]; + + if message_exists[0] { + messages.push(&message_0[..]); + keys.push(&aggregate_pubs[0]); + } + if message_exists[1] { + messages.push(&message_1[..]); + keys.push(&aggregate_pubs[1]); + } + + slashable_attestation.aggregate_signature.verify_multiple( + &messages[..], + spec.domain_attestation, + &keys[..], + ) + } + /// Return the block root at a recent `slot`. /// /// Spec v0.2.0 From 76a0ba2d6c452bc9472f5eb3cbb0063bf9e2f7cf Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 11:18:12 +1100 Subject: [PATCH 29/41] Add attester slashing support to block processing At spec v0.2.0 --- .../state_processing/src/block_processable.rs | 18 +++++- .../verify_slashable_attestation.rs | 63 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 eth2/state_processing/src/block_processable/verify_slashable_attestation.rs diff --git a/eth2/state_processing/src/block_processable.rs b/eth2/state_processing/src/block_processable.rs index e434b0c832..32327aad3b 100644 --- a/eth2/state_processing/src/block_processable.rs +++ b/eth2/state_processing/src/block_processable.rs @@ -1,3 +1,4 @@ +use self::verify_slashable_attestation::verify_slashable_attestation; use crate::SlotProcessingError; use hashing::hash; use int_to_bytes::int_to_bytes32; @@ -5,6 +6,8 @@ use log::{debug, trace}; use ssz::{ssz_encode, TreeHash}; use types::*; +mod verify_slashable_attestation; + const PHASE_0_CUSTODY_BIT: bool = false; #[derive(Debug, PartialEq)] @@ -23,7 +26,9 @@ pub enum Error { BadRandaoSignature, MaxProposerSlashingsExceeded, BadProposerSlashing, + MaxAttesterSlashingsExceed, MaxAttestationsExceeded, + BadAttesterSlashing, InvalidAttestation(AttestationValidationError), NoBlockRoot, MaxDepositsExceeded, @@ -82,7 +87,7 @@ impl BlockProcessable for BeaconState { } fn per_block_processing_signature_optional( - state: &mut BeaconState, + mut state: &mut BeaconState, block: &BeaconBlock, verify_block_signature: bool, spec: &ChainSpec, @@ -205,6 +210,17 @@ fn per_block_processing_signature_optional( state.penalize_validator(proposer_slashing.proposer_index as usize, spec)?; } + /* + * Attester slashings + */ + ensure!( + block.body.attester_slashings.len() as u64 <= spec.max_attester_slashings, + Error::MaxAttesterSlashingsExceed + ); + for attester_slashing in &block.body.attester_slashings { + verify_slashable_attestation(&mut state, &attester_slashing, spec)?; + } + /* * Attestations */ diff --git a/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs b/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs new file mode 100644 index 0000000000..dddc2eb1f8 --- /dev/null +++ b/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs @@ -0,0 +1,63 @@ +use super::Error; +use log::error; +use types::*; + +macro_rules! ensure { + ($condition: expr, $result: expr) => { + if !$condition { + return Err($result); + } + }; +} + +pub fn verify_slashable_attestation( + state: &mut BeaconState, + attester_slashing: &AttesterSlashing, + spec: &ChainSpec, +) -> Result<(), Error> { + let slashable_attestation_1 = &attester_slashing.slashable_attestation_1; + let slashable_attestation_2 = &attester_slashing.slashable_attestation_2; + + ensure!( + slashable_attestation_1.data != slashable_attestation_2.data, + Error::BadAttesterSlashing + ); + ensure!( + slashable_attestation_1.is_double_vote(slashable_attestation_2, spec) + | slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec), + Error::BadAttesterSlashing + ); + error!("this a"); + ensure!( + state.verify_slashable_attestation(&slashable_attestation_1, spec), + Error::BadAttesterSlashing + ); + error!("this b"); + ensure!( + state.verify_slashable_attestation(&slashable_attestation_2, spec), + Error::BadAttesterSlashing + ); + error!("this c"); + + let mut slashable_indices = vec![]; + for i in &slashable_attestation_1.validator_indices { + let validator = state + .validator_registry + .get(*i as usize) + .ok_or_else(|| Error::BadAttesterSlashing)?; + + if slashable_attestation_1.validator_indices.contains(&i) + & !validator.is_penalized_at(state.current_epoch(spec)) + { + slashable_indices.push(i); + } + } + + ensure!(slashable_indices.len() >= 1, Error::BadAttesterSlashing); + + for i in slashable_indices { + state.penalize_validator(*i as usize, spec)?; + } + + Ok(()) +} From a8c3b5fdd8b46652931a681e0c224e271f12178e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 11:19:27 +1100 Subject: [PATCH 30/41] Update test_harness yaml - Checks for attester slashing, now it is included in the chain. - Renames suite to be more specific, use normal Eth2.0 naming --- .../beacon_chain/test_harness/examples/chain.yaml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/examples/chain.yaml b/beacon_node/beacon_chain/test_harness/examples/chain.yaml index f22d0874e4..dd5aa7cc9b 100644 --- a/beacon_node/beacon_chain/test_harness/examples/chain.yaml +++ b/beacon_node/beacon_chain/test_harness/examples/chain.yaml @@ -1,7 +1,7 @@ -title: Sample Ethereum Serenity State Transition Tests -summary: Testing full state transition block processing -test_suite: prysm -fork: sapphire +title: Validator Registry Tests +summary: Tests deposit and slashing effects on validator registry. +test_suite: validator_registry +fork: tchaikovsky version: 1.0 test_cases: - config: @@ -38,7 +38,6 @@ test_cases: states: - slot: 63 num_validators: 1003 - # slashed_validators: [11, 12, 13, 14, 42] - slashed_validators: [13, 42] # This line is incorrect, our implementation isn't processing attester_slashings. + slashed_validators: [11, 12, 13, 14, 42] exited_validators: [] From 87feeea1fd328c3e0bc7f72fae40c271a668728f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 11:31:08 +1100 Subject: [PATCH 31/41] Remove merkle_index from test_harness YAML IMO, this is an implementation detail that shouldn't be covered in these tests. --- .../beacon_chain/test_harness/examples/chain.yaml | 9 ++++----- .../beacon_chain/test_harness/src/manifest/config.rs | 11 ++++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/examples/chain.yaml b/beacon_node/beacon_chain/test_harness/examples/chain.yaml index dd5aa7cc9b..b7fdda9bf7 100644 --- a/beacon_node/beacon_chain/test_harness/examples/chain.yaml +++ b/beacon_node/beacon_chain/test_harness/examples/chain.yaml @@ -10,27 +10,26 @@ test_cases: num_slots: 64 skip_slots: [2, 3] deposits: + # At slot 1, create a new validator deposit of 32 ETH. - slot: 1 amount: 32 - merkle_index: 0 + # Trigger more deposits... - slot: 3 amount: 32 - merkle_index: 1 - slot: 5 amount: 32 - merkle_index: 2 proposer_slashings: # At slot 2, trigger a proposer slashing for validator #42. - slot: 2 validator_index: 42 - # At slot 8, trigger a proposer slashing for validator #13. + # Trigger another slashing... - slot: 8 validator_index: 13 attester_slashings: # At slot 2, trigger an attester slashing for validators #11 and #12. - slot: 2 validator_indices: [11, 12] - # At slot 5, trigger an attester slashing for validator #14. + # Trigger another slashing... - slot: 5 validator_indices: [14] results: diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/config.rs b/beacon_node/beacon_chain/test_harness/src/manifest/config.rs index 0e66a120be..be01d48d82 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/config.rs +++ b/beacon_node/beacon_chain/test_harness/src/manifest/config.rs @@ -68,12 +68,17 @@ fn parse_deposits(yaml: &Yaml) -> Option> { let keypair = Keypair::random(); let proof_of_possession = create_proof_of_possession(&keypair); - let slot = as_u64(deposit, "slot").expect("Incomplete deposit"); + let slot = as_u64(deposit, "slot").expect("Incomplete deposit (slot)"); + let amount = + as_u64(deposit, "amount").expect("Incomplete deposit (amount)") * 1_000_000_000; + let deposit = Deposit { + // Note: `branch` and `index` will need to be updated once the spec defines their + // validity. branch: vec![], - index: as_u64(deposit, "merkle_index").unwrap(), + index: 0, deposit_data: DepositData { - amount: 32_000_000_000, + amount, timestamp: 1, deposit_input: DepositInput { pubkey: keypair.pk.clone(), From ede5685bc298313d564b38a24a5bbfcbe2a38339 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 11:47:09 +1100 Subject: [PATCH 32/41] Fix warnings and clippy lints --- .../test_harness/src/beacon_chain_harness.rs | 11 ----------- .../src/validator_harness/local_signer.rs | 13 +------------ .../verify_slashable_attestation.rs | 6 +----- eth2/types/src/attester_slashing/builder.rs | 4 ++-- eth2/types/src/beacon_state.rs | 3 +-- eth2/types/src/proposer_slashing/builder.rs | 4 ++-- eth2/utils/bls/src/aggregate_public_key.rs | 2 +- 7 files changed, 8 insertions(+), 35 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 9060c4ec19..d6608cd396 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -10,10 +10,7 @@ use fork_choice::BitwiseLMDGhost; use log::debug; use rayon::prelude::*; use slot_clock::TestingSlotClock; -use ssz::TreeHash; use std::collections::HashSet; -use std::fs::File; -use std::io::prelude::*; use std::iter::FromIterator; use std::sync::Arc; use types::*; @@ -291,12 +288,4 @@ impl BeaconChainHarness { pub fn chain_dump(&self) -> Result, BeaconChainError> { self.beacon_chain.chain_dump() } - - /// Write the output of `chain_dump` to a JSON file. - pub fn dump_to_file(&self, filename: String, chain_dump: &[CheckPoint]) { - let json = serde_json::to_string(chain_dump).unwrap(); - let mut file = File::create(filename).unwrap(); - file.write_all(json.as_bytes()) - .expect("Failed writing dump to file."); - } } diff --git a/beacon_node/beacon_chain/test_harness/src/validator_harness/local_signer.rs b/beacon_node/beacon_chain/test_harness/src/validator_harness/local_signer.rs index 3f249cb199..803af5045d 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator_harness/local_signer.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator_harness/local_signer.rs @@ -1,27 +1,16 @@ use attester::Signer as AttesterSigner; use block_proposer::Signer as BlockProposerSigner; -use std::sync::RwLock; use types::{Keypair, Signature}; /// A test-only struct used to perform signing for a proposer or attester. pub struct LocalSigner { keypair: Keypair, - should_sign: RwLock, } impl LocalSigner { /// Produce a new TestSigner with signing enabled by default. pub fn new(keypair: Keypair) -> Self { - Self { - keypair, - should_sign: RwLock::new(true), - } - } - - /// If set to `false`, the service will refuse to sign all messages. Otherwise, all messages - /// will be signed. - pub fn enable_signing(&self, enabled: bool) { - *self.should_sign.write().unwrap() = enabled; + Self { keypair } } /// Sign some message. diff --git a/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs b/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs index dddc2eb1f8..35ad67df0e 100644 --- a/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs +++ b/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs @@ -1,5 +1,4 @@ use super::Error; -use log::error; use types::*; macro_rules! ensure { @@ -27,17 +26,14 @@ pub fn verify_slashable_attestation( | slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec), Error::BadAttesterSlashing ); - error!("this a"); ensure!( state.verify_slashable_attestation(&slashable_attestation_1, spec), Error::BadAttesterSlashing ); - error!("this b"); ensure!( state.verify_slashable_attestation(&slashable_attestation_2, spec), Error::BadAttesterSlashing ); - error!("this c"); let mut slashable_indices = vec![]; for i in &slashable_attestation_1.validator_indices { @@ -53,7 +49,7 @@ pub fn verify_slashable_attestation( } } - ensure!(slashable_indices.len() >= 1, Error::BadAttesterSlashing); + ensure!(!slashable_indices.is_empty(), Error::BadAttesterSlashing); for i in slashable_indices { state.penalize_validator(*i as usize, spec)?; diff --git a/eth2/types/src/attester_slashing/builder.rs b/eth2/types/src/attester_slashing/builder.rs index d382cb64c2..e537061929 100644 --- a/eth2/types/src/attester_slashing/builder.rs +++ b/eth2/types/src/attester_slashing/builder.rs @@ -16,8 +16,8 @@ impl AttesterSlashingBuilder { let shard = 0; let justified_epoch = Epoch::new(0); let epoch = Epoch::new(0); - let hash_1 = Hash256::from("1".as_bytes()); - let hash_2 = Hash256::from("2".as_bytes()); + let hash_1 = Hash256::from(&[1][..]); + let hash_2 = Hash256::from(&[2][..]); let mut slashable_attestation_1 = SlashableAttestation { validator_indices: validator_indices.to_vec(), diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 505d4d9def..932233445a 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -419,8 +419,7 @@ impl BeaconState { committees_per_epoch ); - let active_validator_indices: Vec = - active_validator_indices.iter().cloned().collect(); + let active_validator_indices: Vec = active_validator_indices.to_vec(); let shuffled_active_validator_indices = shuffle_list( active_validator_indices, diff --git a/eth2/types/src/proposer_slashing/builder.rs b/eth2/types/src/proposer_slashing/builder.rs index a43a73ff0d..363155a14d 100644 --- a/eth2/types/src/proposer_slashing/builder.rs +++ b/eth2/types/src/proposer_slashing/builder.rs @@ -14,13 +14,13 @@ impl ProposerSlashingBuilder { let proposal_data_1 = ProposalSignedData { slot, shard, - block_root: Hash256::from("one".as_bytes()), + block_root: Hash256::from(&[1][..]), }; let proposal_data_2 = ProposalSignedData { slot, shard, - block_root: Hash256::from("two".as_bytes()), + block_root: Hash256::from(&[2][..]), }; let proposal_signature_1 = { diff --git a/eth2/utils/bls/src/aggregate_public_key.rs b/eth2/utils/bls/src/aggregate_public_key.rs index dcb08126c9..2174a43cb0 100644 --- a/eth2/utils/bls/src/aggregate_public_key.rs +++ b/eth2/utils/bls/src/aggregate_public_key.rs @@ -5,7 +5,7 @@ use bls_aggregates::AggregatePublicKey as RawAggregatePublicKey; /// /// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ /// serialization). -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct AggregatePublicKey(RawAggregatePublicKey); impl AggregatePublicKey { From f5e4fe29d7e54fb6ad66c79f2581f947af70db56 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 11:54:51 +1100 Subject: [PATCH 33/41] Add comments to new `BeaconChain` methods - Adds comments - Also drops a message from `warn` down to `debug`. It was giving warnings even on an Ok result. --- beacon_node/beacon_chain/src/beacon_chain.rs | 22 ++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 625a891970..e6fd2a1342 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -5,7 +5,7 @@ use db::{ ClientDB, DBError, }; use fork_choice::{ForkChoice, ForkChoiceError}; -use log::{debug, trace, warn}; +use log::{debug, trace}; use parking_lot::{RwLock, RwLockReadGuard}; use slot_clock::SlotClock; use ssz::ssz_encode; @@ -368,16 +368,22 @@ where Ok(aggregation_outcome) } + /// Accept some deposit and queue it for inclusion in an appropriate block. pub fn receive_deposit_for_inclusion(&self, deposit: Deposit) { // TODO: deposits are not check for validity; check them. self.deposits_for_inclusion.write().push(deposit); } + /// Return a vec of deposits suitable for inclusion in some block. pub fn get_deposits_for_block(&self) -> Vec { // TODO: deposits are indiscriminately included; check them for validity. self.deposits_for_inclusion.read().clone() } + /// Takes a list of `Deposits` that were included in recent blocks and removes them from the + /// inclusion queue. + /// + /// This ensures that `Deposits` are not included twice in successive blocks. pub fn set_deposits_as_included(&self, included_deposits: &[Deposit]) { // TODO: method does not take forks into account; consider this. let mut indices_to_delete = vec![]; @@ -396,6 +402,7 @@ where } } + /// Accept some proposer slashing and queue it for inclusion in an appropriate block. pub fn receive_proposer_slashing_for_inclusion(&self, proposer_slashing: ProposerSlashing) { // TODO: proposer_slashings are not check for validity; check them. self.proposer_slashings_for_inclusion @@ -403,11 +410,16 @@ where .push(proposer_slashing); } + /// Return a vec of proposer slashings suitable for inclusion in some block. pub fn get_proposer_slashings_for_block(&self) -> Vec { // TODO: proposer_slashings are indiscriminately included; check them for validity. self.proposer_slashings_for_inclusion.read().clone() } + /// Takes a list of `ProposerSlashings` that were included in recent blocks and removes them + /// from the inclusion queue. + /// + /// This ensures that `ProposerSlashings` are not included twice in successive blocks. pub fn set_proposer_slashings_as_included( &self, included_proposer_slashings: &[ProposerSlashing], @@ -434,6 +446,7 @@ where } } + /// Accept some attester slashing and queue it for inclusion in an appropriate block. pub fn receive_attester_slashing_for_inclusion(&self, attester_slashing: AttesterSlashing) { // TODO: attester_slashings are not check for validity; check them. self.attester_slashings_for_inclusion @@ -441,11 +454,16 @@ where .push(attester_slashing); } + /// Return a vec of attester slashings suitable for inclusion in some block. pub fn get_attester_slashings_for_block(&self) -> Vec { // TODO: attester_slashings are indiscriminately included; check them for validity. self.attester_slashings_for_inclusion.read().clone() } + /// Takes a list of `AttesterSlashings` that were included in recent blocks and removes them + /// from the inclusion queue. + /// + /// This ensures that `AttesterSlashings` are not included twice in successive blocks. pub fn set_attester_slashings_as_included( &self, included_attester_slashings: &[AttesterSlashing], @@ -668,7 +686,7 @@ where let result = state.per_block_processing_without_verifying_block_signature(&block, &self.spec); - warn!( + debug!( "BeaconNode::produce_block: state processing result: {:?}", result ); From 1703508385801427e86f7731a177b8a69dd18e87 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 12:02:58 +1100 Subject: [PATCH 34/41] Add comments to new `BeaconChainHarness` methods. --- .../test_harness/src/beacon_chain_harness.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index d6608cd396..2f375f7faf 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -240,6 +240,11 @@ impl BeaconChainHarness { debug!("Free attestations processed."); } + /// Signs a message using some validators secret key with the `Fork` info from the latest state + /// of the `BeaconChain`. + /// + /// Useful for producing slashable messages and other objects that `BeaconChainHarness` does + /// not produce naturally. pub fn validator_sign( &self, validator_index: usize, @@ -259,6 +264,11 @@ impl BeaconChainHarness { Some(Signature::new(message, domain, &validator.keypair.sk)) } + /// Submit a deposit to the `BeaconChain` and, if given a keypair, create a new + /// `ValidatorHarness` instance for this validator. + /// + /// If a new `ValidatorHarness` was created, the validator should become fully operational as + /// if the validator were created during `BeaconChainHarness` instantiation. pub fn add_deposit(&mut self, deposit: Deposit, keypair: Option) { self.beacon_chain.receive_deposit_for_inclusion(deposit); @@ -270,16 +280,19 @@ impl BeaconChainHarness { } } + /// Submit a proposer slashing to the `BeaconChain` for inclusion in some block. pub fn add_proposer_slashing(&mut self, proposer_slashing: ProposerSlashing) { self.beacon_chain .receive_proposer_slashing_for_inclusion(proposer_slashing); } + /// Submit an attester slashing to the `BeaconChain` for inclusion in some block. pub fn add_attester_slashing(&mut self, attester_slashing: AttesterSlashing) { self.beacon_chain .receive_attester_slashing_for_inclusion(attester_slashing); } + /// Executes the fork choice rule on the `BeaconChain`, selecting a new canonical head. pub fn run_fork_choice(&mut self) { self.beacon_chain.fork_choice().unwrap() } From 7b729349435bd104a1c4d31310621199b3fa11c2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 15:07:54 +1100 Subject: [PATCH 35/41] Add comments to test_harness::Manifest --- .../beacon_chain/test_harness/src/bin.rs | 12 ++++++ .../beacon_chain/test_harness/src/lib.rs | 28 +++++++++++++ .../test_harness/src/manifest/config.rs | 14 +++++++ .../test_harness/src/manifest/mod.rs | 40 +++++++++++++++++-- .../test_harness/src/manifest/results.rs | 6 +++ .../test_harness/src/manifest/state_check.rs | 14 +++++++ 6 files changed, 111 insertions(+), 3 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index b6a0529c7d..5411efc4a3 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -37,6 +37,18 @@ fn main() { }; for doc in &docs { + // For each `test_cases` YAML in the document, build a `Manifest`, execute it and + // assert that the execution result matches the manifest description. + // + // In effect, for each `test_case` a new `BeaconChainHarness` is created from genesis + // and a new `BeaconChain` is built as per the manifest. + // + // After the `BeaconChain` has been built out as per the manifest, a dump of all blocks + // and states in the chain is obtained and checked against the `results` specified in + // the `test_case`. + // + // If any of the expectations in the results are not met, the process + // panics with a message. for test_case in doc["test_cases"].as_vec().unwrap() { let manifest = Manifest::from_yaml(test_case); manifest.assert_result_valid(manifest.execute()) diff --git a/beacon_node/beacon_chain/test_harness/src/lib.rs b/beacon_node/beacon_chain/test_harness/src/lib.rs index b04fc69963..a7ada24335 100644 --- a/beacon_node/beacon_chain/test_harness/src/lib.rs +++ b/beacon_node/beacon_chain/test_harness/src/lib.rs @@ -1,4 +1,32 @@ +//! Provides a testing environment for the `BeaconChain`, `Attester` and `BlockProposer` objects. +//! +//! This environment bypasses networking client runtimes and connects the `Attester` and `Proposer` +//! directly to the `BeaconChain` via an `Arc`. +//! +//! The `BeaconChainHarness` contains a single `BeaconChain` instance and many `ValidatorHarness` +//! instances. All of the `ValidatorHarness` instances work to advance the `BeaconChain` by +//! producing blocks and attestations. +//! +//! Example: +//! ``` +//! use test_harness::BeaconChainHarness; +//! use types::ChainSpec; +//! +//! let validator_count = 8; +//! let spec = ChainSpec::few_validators(); +//! +//! let mut harness = BeaconChainHarness::new(spec, validator_count); +//! +//! harness.advance_chain_with_block(); +//! +//! let chain = harness.chain_dump().unwrap(); +//! +//! // One block should have been built on top of the genesis block. +//! assert_eq!(chain.len(), 2); +//! ``` + mod beacon_chain_harness; +pub mod manifest; mod validator_harness; pub use self::beacon_chain_harness::BeaconChainHarness; diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/config.rs b/beacon_node/beacon_chain/test_harness/src/manifest/config.rs index be01d48d82..8c88ee5d19 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/config.rs +++ b/beacon_node/beacon_chain/test_harness/src/manifest/config.rs @@ -7,18 +7,29 @@ pub type DepositTuple = (u64, Deposit, Keypair); pub type ProposerSlashingTuple = (u64, u64); pub type AttesterSlashingTuple = (u64, Vec); +/// Defines the execution of a `BeaconStateHarness` across a series of slots. #[derive(Debug)] pub struct Config { + /// Initial validators. pub deposits_for_chain_start: usize, + /// Number of slots in an epoch. pub epoch_length: Option, + /// Number of slots to build before ending execution. pub num_slots: u64, + /// Number of slots that should be skipped due to inactive validator. pub skip_slots: Option>, + /// Deposits to be included during execution. pub deposits: Option>, + /// Proposer slashings to be included during execution. pub proposer_slashings: Option>, + /// Attester slashings to be including during execution. pub attester_slashings: Option>, } impl Config { + /// Load from a YAML document. + /// + /// Expects to receive the `config` section of the document. pub fn from_yaml(yaml: &Yaml) -> Self { Self { deposits_for_chain_start: as_usize(&yaml, "deposits_for_chain_start") @@ -33,6 +44,7 @@ impl Config { } } +/// Parse the `attester_slashings` section of the YAML document. fn parse_attester_slashings(yaml: &Yaml) -> Option> { let mut slashings = vec![]; @@ -47,6 +59,7 @@ fn parse_attester_slashings(yaml: &Yaml) -> Option> { Some(slashings) } +/// Parse the `proposer_slashings` section of the YAML document. fn parse_proposer_slashings(yaml: &Yaml) -> Option> { let mut slashings = vec![]; @@ -61,6 +74,7 @@ fn parse_proposer_slashings(yaml: &Yaml) -> Option> { Some(slashings) } +/// Parse the `deposits` section of the YAML document. fn parse_deposits(yaml: &Yaml) -> Option> { let mut deposits = vec![]; diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs b/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs index 4b8385035e..7b9c9cb027 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs +++ b/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs @@ -1,5 +1,6 @@ -use self::config::Config; -use self::results::Results; +//! 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}; @@ -14,18 +15,36 @@ 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 `Manifest` from YAML: `let manifest = Manifest::from_yaml(&my_yaml);` +/// 2. Execute the manifest: `let result = manifest.execute();` +/// 3. Test the results against the manifest: `manifest.assert_result_valid(result);` #[derive(Debug)] pub struct Manifest { - pub results: Results, + /// Defines the execution. pub config: Config, + /// Defines tests to run against the execution result. + pub results: Results, } +/// The result of executing a `Manifest`. +/// pub struct ExecutionResult { + /// The canonical beacon chain generated from the execution. pub chain: Vec, + /// The spec used for execution. pub spec: ChainSpec, } impl Manifest { + /// Load the manifest from a YAML document. pub fn from_yaml(test_case: &Yaml) -> Self { Self { results: Results::from_yaml(&test_case["results"]), @@ -33,6 +52,9 @@ impl Manifest { } } + /// Return a `ChainSpec::foundation()`. + /// + /// If specified in `config`, returns it with a modified `epoch_length`. fn spec(&self) -> ChainSpec { let mut spec = ChainSpec::foundation(); @@ -43,6 +65,7 @@ impl Manifest { spec } + /// Executes the manifest, returning an `ExecutionResult`. pub fn execute(&self) -> ExecutionResult { let spec = self.spec(); let validator_count = self.config.deposits_for_chain_start; @@ -123,6 +146,11 @@ impl Manifest { } } + /// 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; @@ -157,6 +185,9 @@ impl Manifest { } } +/// Builds an `AttesterSlashing` for some `validator_indices`. +/// +/// Signs the message using a `BeaconChainHarness`. fn build_double_vote_attester_slashing( harness: &BeaconChainHarness, validator_indices: &[u64], @@ -170,6 +201,9 @@ fn build_double_vote_attester_slashing( 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 diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/results.rs b/beacon_node/beacon_chain/test_harness/src/manifest/results.rs index 8446959103..d16c918740 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/results.rs +++ b/beacon_node/beacon_chain/test_harness/src/manifest/results.rs @@ -2,6 +2,8 @@ use super::state_check::StateCheck; use super::yaml_helpers::as_usize; use yaml_rust::Yaml; +/// A series of tests to be carried out upon an `ExecutionResult`, returned from executing a +/// `Manifest`. #[derive(Debug)] pub struct Results { pub num_skipped_slots: Option, @@ -9,6 +11,9 @@ pub struct Results { } impl Results { + /// Load from a YAML document. + /// + /// Expects the `results` section of the YAML document. pub fn from_yaml(yaml: &Yaml) -> Self { Self { num_skipped_slots: as_usize(yaml, "num_skipped_slots"), @@ -17,6 +22,7 @@ impl Results { } } +/// Parse the `state_checks` section of the YAML document. fn parse_state_checks(yaml: &Yaml) -> Option> { let mut states = vec![]; diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/state_check.rs b/beacon_node/beacon_chain/test_harness/src/manifest/state_check.rs index 0415d4896b..a729811d56 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/state_check.rs +++ b/beacon_node/beacon_chain/test_harness/src/manifest/state_check.rs @@ -3,15 +3,24 @@ use log::info; use types::*; use yaml_rust::Yaml; +/// Tests to be conducted upon a `BeaconState` object generated during the execution of a +/// `Manifest`. #[derive(Debug)] pub struct StateCheck { + /// Checked against `beacon_state.slot`. pub slot: Slot, + /// Checked against `beacon_state.validator_registry.len()`. pub num_validators: Option, + /// A list of validator indices which have been penalized. Must be in ascending order. pub slashed_validators: Option>, + /// A list of validator indices which have been exited. Must be in ascending order. pub exited_validators: Option>, } impl StateCheck { + /// Load from a YAML document. + /// + /// Expects the `state_check` section of the YAML document. pub fn from_yaml(yaml: &Yaml) -> Self { Self { slot: Slot::from(as_u64(&yaml, "slot").expect("State must specify slot")), @@ -21,6 +30,11 @@ impl StateCheck { } } + /// Performs all checks against a `BeaconState` + /// + /// # Panics + /// + /// Panics with an error message if any test fails. pub fn assert_valid(&self, state: &BeaconState, spec: &ChainSpec) { let state_epoch = state.slot.epoch(spec.epoch_length); From 48fc7091096eede7bccfb41a542d0aa290b73512 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 15:08:13 +1100 Subject: [PATCH 36/41] Fix failing test --- beacon_node/beacon_chain/test_harness/tests/chain.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/tests/chain.rs b/beacon_node/beacon_chain/test_harness/tests/chain.rs index 1b29a412fe..238e567ad2 100644 --- a/beacon_node/beacon_chain/test_harness/tests/chain.rs +++ b/beacon_node/beacon_chain/test_harness/tests/chain.rs @@ -41,6 +41,4 @@ fn it_can_produce_past_first_epoch_boundary() { let dump = harness.chain_dump().expect("Chain dump failed."); assert_eq!(dump.len() as u64, blocks + 1); // + 1 for genesis block. - - harness.dump_to_file("/tmp/chaindump.json".to_string(), &dump); } From a29eca57a1c047828ce38026a8b65326396129b3 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 15:12:19 +1100 Subject: [PATCH 37/41] Rename test_harness::manifest to test_case I thing `TestCase` is better than manifest -- a manifest is more of a list of items than a series of steps and checks. Plus it conflicts with a Cargo manifest. --- .../beacon_chain/test_harness/src/bin.rs | 20 +++++++++---------- .../beacon_chain/test_harness/src/lib.rs | 2 +- .../src/{manifest => test_case}/config.rs | 0 .../src/{manifest => test_case}/mod.rs | 16 +++++++-------- .../src/{manifest => test_case}/results.rs | 2 +- .../{manifest => test_case}/state_check.rs | 2 +- .../{manifest => test_case}/yaml_helpers.rs | 0 7 files changed, 21 insertions(+), 21 deletions(-) rename beacon_node/beacon_chain/test_harness/src/{manifest => test_case}/config.rs (100%) rename beacon_node/beacon_chain/test_harness/src/{manifest => test_case}/mod.rs (94%) rename beacon_node/beacon_chain/test_harness/src/{manifest => test_case}/results.rs (98%) rename beacon_node/beacon_chain/test_harness/src/{manifest => test_case}/state_check.rs (99%) rename beacon_node/beacon_chain/test_harness/src/{manifest => test_case}/yaml_helpers.rs (100%) diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index 5411efc4a3..b4a2cf05ef 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -1,11 +1,11 @@ use clap::{App, Arg}; use env_logger::{Builder, Env}; -use manifest::Manifest; use std::{fs::File, io::prelude::*}; +use test_case::TestCase; use yaml_rust::YamlLoader; mod beacon_chain_harness; -mod manifest; +mod test_case; mod validator_harness; use validator_harness::ValidatorHarness; @@ -14,12 +14,12 @@ fn main() { let matches = App::new("Lighthouse Test Harness Runner") .version("0.0.1") .author("Sigma Prime ") - .about("Runs `test_harness` using a YAML manifest.") + .about("Runs `test_harness` using a YAML test_case.") .arg( Arg::with_name("yaml") .long("yaml") .value_name("FILE") - .help("YAML file manifest.") + .help("YAML file test_case.") .required(true), ) .get_matches(); @@ -37,21 +37,21 @@ fn main() { }; for doc in &docs { - // For each `test_cases` YAML in the document, build a `Manifest`, execute it and - // assert that the execution result matches the manifest description. + // For each `test_cases` YAML in the document, build a `TestCase`, execute it and + // assert that the execution result matches the test_case description. // // In effect, for each `test_case` a new `BeaconChainHarness` is created from genesis - // and a new `BeaconChain` is built as per the manifest. + // and a new `BeaconChain` is built as per the test_case. // - // After the `BeaconChain` has been built out as per the manifest, a dump of all blocks + // After the `BeaconChain` has been built out as per the test_case, a dump of all blocks // and states in the chain is obtained and checked against the `results` specified in // the `test_case`. // // If any of the expectations in the results are not met, the process // panics with a message. for test_case in doc["test_cases"].as_vec().unwrap() { - let manifest = Manifest::from_yaml(test_case); - manifest.assert_result_valid(manifest.execute()) + let test_case = TestCase::from_yaml(test_case); + test_case.assert_result_valid(test_case.execute()) } } } diff --git a/beacon_node/beacon_chain/test_harness/src/lib.rs b/beacon_node/beacon_chain/test_harness/src/lib.rs index a7ada24335..2303dc0643 100644 --- a/beacon_node/beacon_chain/test_harness/src/lib.rs +++ b/beacon_node/beacon_chain/test_harness/src/lib.rs @@ -26,7 +26,7 @@ //! ``` mod beacon_chain_harness; -pub mod manifest; +pub mod test_case; mod validator_harness; pub use self::beacon_chain_harness::BeaconChainHarness; diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/config.rs b/beacon_node/beacon_chain/test_harness/src/test_case/config.rs similarity index 100% rename from beacon_node/beacon_chain/test_harness/src/manifest/config.rs rename to beacon_node/beacon_chain/test_harness/src/test_case/config.rs diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs b/beacon_node/beacon_chain/test_harness/src/test_case/mod.rs similarity index 94% rename from beacon_node/beacon_chain/test_harness/src/manifest/mod.rs rename to beacon_node/beacon_chain/test_harness/src/test_case/mod.rs index 7b9c9cb027..f6d8d42e8d 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/mod.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case/mod.rs @@ -23,18 +23,18 @@ pub use state_check::StateCheck; /// /// Typical workflow is: /// -/// 1. Instantiate the `Manifest` from YAML: `let manifest = Manifest::from_yaml(&my_yaml);` -/// 2. Execute the manifest: `let result = manifest.execute();` -/// 3. Test the results against the manifest: `manifest.assert_result_valid(result);` +/// 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 Manifest { +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 `Manifest`. +/// The result of executing a `TestCase`. /// pub struct ExecutionResult { /// The canonical beacon chain generated from the execution. @@ -43,8 +43,8 @@ pub struct ExecutionResult { pub spec: ChainSpec, } -impl Manifest { - /// Load the manifest from a YAML document. +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"]), @@ -65,7 +65,7 @@ impl Manifest { spec } - /// Executes the manifest, returning an `ExecutionResult`. + /// 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; diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/results.rs b/beacon_node/beacon_chain/test_harness/src/test_case/results.rs similarity index 98% rename from beacon_node/beacon_chain/test_harness/src/manifest/results.rs rename to beacon_node/beacon_chain/test_harness/src/test_case/results.rs index d16c918740..596418c0fd 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/results.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case/results.rs @@ -3,7 +3,7 @@ use super::yaml_helpers::as_usize; use yaml_rust::Yaml; /// A series of tests to be carried out upon an `ExecutionResult`, returned from executing a -/// `Manifest`. +/// `TestCase`. #[derive(Debug)] pub struct Results { pub num_skipped_slots: Option, diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/state_check.rs b/beacon_node/beacon_chain/test_harness/src/test_case/state_check.rs similarity index 99% rename from beacon_node/beacon_chain/test_harness/src/manifest/state_check.rs rename to beacon_node/beacon_chain/test_harness/src/test_case/state_check.rs index a729811d56..90c6228947 100644 --- a/beacon_node/beacon_chain/test_harness/src/manifest/state_check.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case/state_check.rs @@ -4,7 +4,7 @@ use types::*; use yaml_rust::Yaml; /// Tests to be conducted upon a `BeaconState` object generated during the execution of a -/// `Manifest`. +/// `TestCase`. #[derive(Debug)] pub struct StateCheck { /// Checked against `beacon_state.slot`. diff --git a/beacon_node/beacon_chain/test_harness/src/manifest/yaml_helpers.rs b/beacon_node/beacon_chain/test_harness/src/test_case/yaml_helpers.rs similarity index 100% rename from beacon_node/beacon_chain/test_harness/src/manifest/yaml_helpers.rs rename to beacon_node/beacon_chain/test_harness/src/test_case/yaml_helpers.rs From ec0e13b764b12fe10390c4b84be7f8f31fede8d0 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 15:32:44 +1100 Subject: [PATCH 38/41] Add comments to new functions --- .../block_processable/verify_slashable_attestation.rs | 2 ++ eth2/types/src/attester_slashing/builder.rs | 11 +++++++++++ eth2/types/src/beacon_state.rs | 6 ++++++ eth2/types/src/proposer_slashing/builder.rs | 11 +++++++++++ eth2/types/src/validator.rs | 4 +++- 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs b/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs index 35ad67df0e..a406af24ee 100644 --- a/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs +++ b/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs @@ -9,6 +9,8 @@ macro_rules! ensure { }; } +/// Returns `Ok(())` if some `AttesterSlashing` is valid to be included in some `BeaconState`, +/// otherwise returns an `Err`. pub fn verify_slashable_attestation( state: &mut BeaconState, attester_slashing: &AttesterSlashing, diff --git a/eth2/types/src/attester_slashing/builder.rs b/eth2/types/src/attester_slashing/builder.rs index e537061929..ed203d6e1f 100644 --- a/eth2/types/src/attester_slashing/builder.rs +++ b/eth2/types/src/attester_slashing/builder.rs @@ -1,9 +1,20 @@ use crate::*; use ssz::TreeHash; +/// Builds an `AttesterSlashing`. pub struct AttesterSlashingBuilder(); impl AttesterSlashingBuilder { + /// Builds an `AttesterSlashing` that is a double vote. + /// + /// The `signer` function is used to sign the double-vote and accepts: + /// + /// - `validator_index: u64` + /// - `message: &[u8]` + /// - `epoch: Epoch` + /// - `domain: u64` + /// + /// Where domain is a domain "constant" (e.g., `spec.domain_attestation`). pub fn double_vote( validator_indices: &[u64], signer: F, diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 932233445a..a9e2f2673c 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1140,6 +1140,9 @@ impl BeaconState { ) } + /// Verify ``bitfield`` against the ``committee_size``. + /// + /// Spec v0.2.0 pub fn verify_bitfield(&self, bitfield: &Bitfield, committee_size: usize) -> bool { if bitfield.num_bytes() != ((committee_size + 7) / 8) { return false; @@ -1159,6 +1162,9 @@ impl BeaconState { true } + /// Verify validity of ``slashable_attestation`` fields. + /// + /// Spec v0.2.0 pub fn verify_slashable_attestation( &self, slashable_attestation: &SlashableAttestation, diff --git a/eth2/types/src/proposer_slashing/builder.rs b/eth2/types/src/proposer_slashing/builder.rs index 363155a14d..7923ff74db 100644 --- a/eth2/types/src/proposer_slashing/builder.rs +++ b/eth2/types/src/proposer_slashing/builder.rs @@ -1,9 +1,20 @@ use crate::*; use ssz::TreeHash; +/// Builds a `ProposerSlashing`. pub struct ProposerSlashingBuilder(); impl ProposerSlashingBuilder { + /// Builds a `ProposerSlashing` that is a double vote. + /// + /// The `signer` function is used to sign the double-vote and accepts: + /// + /// - `validator_index: u64` + /// - `message: &[u8]` + /// - `epoch: Epoch` + /// - `domain: u64` + /// + /// Where domain is a domain "constant" (e.g., `spec.domain_attestation`). pub fn double_vote(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing where F: Fn(u64, &[u8], Epoch, u64) -> Signature, diff --git a/eth2/types/src/validator.rs b/eth2/types/src/validator.rs index bc8d467ec7..587a48a1f3 100644 --- a/eth2/types/src/validator.rs +++ b/eth2/types/src/validator.rs @@ -54,15 +54,17 @@ pub struct Validator { } impl Validator { - /// This predicate indicates if the validator represented by this record is considered "active" at `slot`. + /// Returns `true` if the validator is considered active at some epoch. pub fn is_active_at(&self, epoch: Epoch) -> bool { self.activation_epoch <= epoch && epoch < self.exit_epoch } + /// Returns `true` if the validator is considered exited at some epoch. pub fn is_exited_at(&self, epoch: Epoch) -> bool { self.exit_epoch <= epoch } + /// Returns `true` if the validator is considered penalized at some epoch. pub fn is_penalized_at(&self, epoch: Epoch) -> bool { self.penalized_epoch <= epoch } From 58002f68e1952dc83dbe4a5a7e4605e13c72138d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 16:14:40 +1100 Subject: [PATCH 39/41] Move test_harness yaml file --- .../{examples/chain.yaml => specs/validator_registry.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename beacon_node/beacon_chain/test_harness/{examples/chain.yaml => specs/validator_registry.yaml} (100%) diff --git a/beacon_node/beacon_chain/test_harness/examples/chain.yaml b/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml similarity index 100% rename from beacon_node/beacon_chain/test_harness/examples/chain.yaml rename to beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml From 697d1ef62657f0825246b7ef3e404f5efa4550c5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 16:15:00 +1100 Subject: [PATCH 40/41] Add CLI option for log-level to test_harness --- beacon_node/beacon_chain/test_harness/src/bin.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index b4a2cf05ef..283cb0dfa3 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -22,9 +22,20 @@ fn main() { .help("YAML file test_case.") .required(true), ) + .arg( + Arg::with_name("log") + .long("log-level") + .value_name("LOG_LEVEL") + .help("Logging level.") + .possible_values(&["error", "warn", "info", "debug", "trace"]) + .default_value("debug") + .required(true), + ) .get_matches(); - Builder::from_env(Env::default().default_filter_or("debug")).init(); + if let Some(log_level) = matches.value_of("log") { + Builder::from_env(Env::default().default_filter_or(log_level)).init(); + } if let Some(yaml_file) = matches.value_of("yaml") { let docs = { From 9d77f2b1a8b7971e3d5ab3928ae7a85ccbd45ff6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 3 Mar 2019 16:38:25 +1100 Subject: [PATCH 41/41] Add README to test_harness --- .../beacon_chain/test_harness/README.md | 150 ++++++++++++++++++ .../beacon_chain/test_harness/src/lib.rs | 2 +- 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 beacon_node/beacon_chain/test_harness/README.md diff --git a/beacon_node/beacon_chain/test_harness/README.md b/beacon_node/beacon_chain/test_harness/README.md new file mode 100644 index 0000000000..12cbbe0627 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/README.md @@ -0,0 +1,150 @@ +# Test Harness + +Provides a testing environment for the `BeaconChain`, `Attester` and `BlockProposer` objects. + +This environment bypasses networking and client run-times and connects the `Attester` and `Proposer` +directly to the `BeaconChain` via an `Arc`. + +The `BeaconChainHarness` contains a single `BeaconChain` instance and many `ValidatorHarness` +instances. All of the `ValidatorHarness` instances work to advance the `BeaconChain` by +producing blocks and attestations. + +The crate consists of a library and binary, examples for using both are +described below. + +## YAML + +Both the library and the binary are capable of parsing tests from a YAML file, +in fact this is the sole purpose of the binary. + +You can find YAML test cases [here](specs/). An example is included below: + +```yaml +title: Validator Registry Tests +summary: Tests deposit and slashing effects on validator registry. +test_suite: validator_registry +fork: tchaikovsky +version: 1.0 +test_cases: + - config: + epoch_length: 64 + deposits_for_chain_start: 1000 + num_slots: 64 + skip_slots: [2, 3] + deposits: + # At slot 1, create a new validator deposit of 32 ETH. + - slot: 1 + amount: 32 + # Trigger more deposits... + - slot: 3 + amount: 32 + - slot: 5 + amount: 32 + proposer_slashings: + # At slot 2, trigger a proposer slashing for validator #42. + - slot: 2 + validator_index: 42 + # Trigger another slashing... + - slot: 8 + validator_index: 13 + attester_slashings: + # At slot 2, trigger an attester slashing for validators #11 and #12. + - slot: 2 + validator_indices: [11, 12] + # Trigger another slashing... + - slot: 5 + validator_indices: [14] + results: + num_skipped_slots: 2 + states: + - slot: 63 + num_validators: 1003 + slashed_validators: [11, 12, 13, 14, 42] + exited_validators: [] + +``` + +Thanks to [prsym](http://github.com/prysmaticlabs/prysm) for coming up with the +base YAML format. + +### Notes + +Wherever `slot` is used, it is actually the "slot height", or slots since +genesis. This allows the tests to disregard the `GENESIS_EPOCH`. + +### Differences from Prysmatic's format + +1. The detail for `deposits`, `proposer_slashings` and `attester_slashings` is + ommitted from the test specification. It assumed they should be valid + objects. +2. There is a `states` list in `results` that runs checks against any state + specified by a `slot` number. This is in contrast to the variables in + `results` that assume the last (highest) state should be inspected. + +#### Reasoning + +Respective reasonings for above changes: + +1. This removes the concerns of the actual object structure from the tests. + This allows for more variation in the deposits/slashings objects without + needing to update the tests. Also, it makes it makes it easier to create + tests. +2. This gives more fine-grained control over the tests. It allows for checking + that certain events happened at certain times whilst making the tests only + slightly more verbose. + +_Notes: it may be useful to add an extra field to each slashing type to +indicate if it should be valid or not. It also may be useful to add an option +for double-vote/surround-vote attester slashings. The `amount` field was left +on `deposits` as it changes the behaviour of state significantly._ + +## Binary Usage Example + +Follow these steps to run as a binary: + +1. Navigate to the root of this crate (where this readme is located) +2. Run `$ cargo run --release -- --yaml examples/validator_registry.yaml` + +_Note: the `--release` flag builds the binary without all the debugging +instrumentation. The test is much faster built using `--release`. As is +customary in cargo, the flags before `--` are passed to cargo and the flags +after are passed to the binary._ + +### CLI Options + +``` +Lighthouse Test Harness Runner 0.0.1 +Sigma Prime +Runs `test_harness` using a YAML test_case. + +USAGE: + test_harness --log-level --yaml + +FLAGS: + -h, --help Prints help information + -V, --version Prints version information + +OPTIONS: + --log-level Logging level. [default: debug] [possible values: error, warn, info, debug, trace] + --yaml YAML file test_case. +``` + + +## Library Usage Example + +```rust +use test_harness::BeaconChainHarness; +use types::ChainSpec; + +let validator_count = 8; +let spec = ChainSpec::few_validators(); + +let mut harness = BeaconChainHarness::new(spec, validator_count); + +harness.advance_chain_with_block(); + +let chain = harness.chain_dump().unwrap(); + +// One block should have been built on top of the genesis block. +assert_eq!(chain.len(), 2); +``` diff --git a/beacon_node/beacon_chain/test_harness/src/lib.rs b/beacon_node/beacon_chain/test_harness/src/lib.rs index 2303dc0643..0703fd4a5a 100644 --- a/beacon_node/beacon_chain/test_harness/src/lib.rs +++ b/beacon_node/beacon_chain/test_harness/src/lib.rs @@ -1,6 +1,6 @@ //! Provides a testing environment for the `BeaconChain`, `Attester` and `BlockProposer` objects. //! -//! This environment bypasses networking client runtimes and connects the `Attester` and `Proposer` +//! This environment bypasses networking and client run-times and connects the `Attester` and `Proposer` //! directly to the `BeaconChain` via an `Arc`. //! //! The `BeaconChainHarness` contains a single `BeaconChain` instance and many `ValidatorHarness`