From 3aaa3ea0243bb355fc9c2ead3cb6a8e939c4eec1 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 4 Mar 2019 09:30:09 +1100 Subject: [PATCH 1/4] Use clearer types in test_harness::Config --- .../test_harness/src/test_case/config.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/test_case/config.rs b/beacon_node/beacon_chain/test_harness/src/test_case/config.rs index 8c88ee5d1..d08e7fe40 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case/config.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case/config.rs @@ -3,9 +3,12 @@ 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 type ValidatorIndex = u64; +pub type ValidatorIndices = Vec; + +pub type DepositTuple = (SlotHeight, Deposit, Keypair); +pub type ProposerSlashingTuple = (SlotHeight, ValidatorIndex); +pub type AttesterSlashingTuple = (SlotHeight, ValidatorIndices); /// Defines the execution of a `BeaconStateHarness` across a series of slots. #[derive(Debug)] @@ -53,7 +56,7 @@ fn parse_attester_slashings(yaml: &Yaml) -> Option> { let validator_indices = as_vec_u64(slashing, "validator_indices") .expect("Incomplete attester_slashing (validator_indices)"); - slashings.push((slot, validator_indices)); + slashings.push((SlotHeight::from(slot), validator_indices)); } Some(slashings) @@ -68,7 +71,7 @@ fn parse_proposer_slashings(yaml: &Yaml) -> Option> { let validator_index = as_u64(slashing, "validator_index") .expect("Incomplete proposer slashing (validator_index)"); - slashings.push((slot, validator_index)); + slashings.push((SlotHeight::from(slot), validator_index)); } Some(slashings) @@ -102,7 +105,7 @@ fn parse_deposits(yaml: &Yaml) -> Option> { }, }; - deposits.push((slot, deposit, keypair)); + deposits.push((SlotHeight::from(slot), deposit, keypair)); } Some(deposits) From bc4acd9a5cb1fc31b1a480ca701721de0b3f8daf Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 4 Mar 2019 09:43:19 +1100 Subject: [PATCH 2/4] Re-work module structure in test_harness Obeys the standard where structs live in files with the same name --- .../test_harness/src/{test_case/mod.rs => test_case.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename beacon_node/beacon_chain/test_harness/src/{test_case/mod.rs => test_case.rs} (100%) diff --git a/beacon_node/beacon_chain/test_harness/src/test_case/mod.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs similarity index 100% rename from beacon_node/beacon_chain/test_harness/src/test_case/mod.rs rename to beacon_node/beacon_chain/test_harness/src/test_case.rs From f4d8b41a0919e6c68c0b0b13fcc4a340c62269fe Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 4 Mar 2019 12:20:59 +1100 Subject: [PATCH 3/4] Add Exit inclusion queue to BeaconChain --- beacon_node/beacon_chain/src/beacon_chain.rs | 66 ++++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index e6fd2a134..81d17f024 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -65,6 +65,7 @@ pub struct BeaconChain { pub slot_clock: U, pub attestation_aggregator: RwLock, pub deposits_for_inclusion: RwLock>, + pub exits_for_inclusion: RwLock>, pub proposer_slashings_for_inclusion: RwLock>, pub attester_slashings_for_inclusion: RwLock>, canonical_head: RwLock, @@ -134,6 +135,7 @@ where slot_clock, attestation_aggregator, deposits_for_inclusion: RwLock::new(vec![]), + exits_for_inclusion: RwLock::new(vec![]), proposer_slashings_for_inclusion: RwLock::new(vec![]), attester_slashings_for_inclusion: RwLock::new(vec![]), state: RwLock::new(genesis_state), @@ -370,13 +372,17 @@ where /// 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. + // TODO: deposits are not checked for validity; check them. + // + // https://github.com/sigp/lighthouse/issues/276 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. + // + // https://github.com/sigp/lighthouse/issues/275 self.deposits_for_inclusion.read().clone() } @@ -386,6 +392,8 @@ where /// 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. + // + // https://github.com/sigp/lighthouse/issues/275 let mut indices_to_delete = vec![]; for included in included_deposits { @@ -402,9 +410,49 @@ where } } + /// Accept some exit and queue it for inclusion in an appropriate block. + pub fn receive_exit_for_inclusion(&self, exit: Exit) { + // TODO: exits are not checked for validity; check them. + // + // https://github.com/sigp/lighthouse/issues/276 + self.exits_for_inclusion.write().push(exit); + } + + /// Return a vec of exits suitable for inclusion in some block. + pub fn get_exits_for_block(&self) -> Vec { + // TODO: exits are indiscriminately included; check them for validity. + // + // https://github.com/sigp/lighthouse/issues/275 + self.exits_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_exits_as_included(&self, included_exits: &[Exit]) { + // TODO: method does not take forks into account; consider this. + let mut indices_to_delete = vec![]; + + for included in included_exits { + for (i, for_inclusion) in self.exits_for_inclusion.read().iter().enumerate() { + if included == for_inclusion { + indices_to_delete.push(i); + } + } + } + + let exits_for_inclusion = &mut self.exits_for_inclusion.write(); + for i in indices_to_delete { + exits_for_inclusion.remove(i); + } + } + /// 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. + // TODO: proposer_slashings are not checked for validity; check them. + // + // https://github.com/sigp/lighthouse/issues/276 self.proposer_slashings_for_inclusion .write() .push(proposer_slashing); @@ -413,6 +461,8 @@ where /// 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. + // + // https://github.com/sigp/lighthouse/issues/275 self.proposer_slashings_for_inclusion.read().clone() } @@ -425,6 +475,8 @@ where included_proposer_slashings: &[ProposerSlashing], ) { // TODO: method does not take forks into account; consider this. + // + // https://github.com/sigp/lighthouse/issues/275 let mut indices_to_delete = vec![]; for included in included_proposer_slashings { @@ -448,7 +500,9 @@ 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. + // TODO: attester_slashings are not checked for validity; check them. + // + // https://github.com/sigp/lighthouse/issues/276 self.attester_slashings_for_inclusion .write() .push(attester_slashing); @@ -457,6 +511,8 @@ where /// 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. + // + // https://github.com/sigp/lighthouse/issues/275 self.attester_slashings_for_inclusion.read().clone() } @@ -469,6 +525,8 @@ where included_attester_slashings: &[AttesterSlashing], ) { // TODO: method does not take forks into account; consider this. + // + // https://github.com/sigp/lighthouse/issues/275 let mut indices_to_delete = vec![]; for included in included_attester_slashings { @@ -678,7 +736,7 @@ where attester_slashings: self.get_attester_slashings_for_block(), attestations, deposits: self.get_deposits_for_block(), - exits: vec![], + exits: self.get_exits_for_block(), }, }; From ef006bfb2cc30bc5906f4e1bb7210847dca42e2c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 4 Mar 2019 12:21:24 +1100 Subject: [PATCH 4/4] Add Exit support to test_harness --- .../specs/validator_registry.yaml | 5 +++ .../test_harness/src/beacon_chain_harness.rs | 9 ++++ .../test_harness/src/test_case.rs | 42 +++++++++++++++++++ .../test_harness/src/test_case/config.rs | 19 +++++++++ .../test_harness/src/test_case/state_check.rs | 32 +++++++++++++- eth2/types/src/validator.rs | 5 +++ 6 files changed, 111 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml b/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml index b7fdda9bf..5851d6d12 100644 --- a/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml +++ b/beacon_node/beacon_chain/test_harness/specs/validator_registry.yaml @@ -18,6 +18,10 @@ test_cases: amount: 32 - slot: 5 amount: 32 + exits: + # At slot 10, submit an exit for validator #50. + - slot: 10 + validator_index: 50 proposer_slashings: # At slot 2, trigger a proposer slashing for validator #42. - slot: 2 @@ -39,4 +43,5 @@ test_cases: num_validators: 1003 slashed_validators: [11, 12, 13, 14, 42] exited_validators: [] + exit_initiated_validators: [50] 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 2f375f7fa..40672c11a 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 @@ -280,6 +280,15 @@ impl BeaconChainHarness { } } + /// Submit an exit to the `BeaconChain` for inclusion in some block. + /// + /// Note: the `ValidatorHarness` for this validator continues to exist. Once it is exited it + /// will stop receiving duties from the beacon chain and just do nothing when prompted to + /// produce/attest. + pub fn add_exit(&mut self, exit: Exit) { + self.beacon_chain.receive_exit_for_inclusion(exit); + } + /// 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 diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index f6d8d42e8..cdac7d3cc 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -4,6 +4,7 @@ use crate::beacon_chain_harness::BeaconChainHarness; use beacon_chain::CheckPoint; use log::{info, warn}; +use ssz::TreeHash; use types::*; use types::{ attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder, @@ -121,6 +122,20 @@ impl TestCase { } } + // Feed exits to the BeaconChain. + if let Some(ref exits) = self.config.exits { + for (slot, validator_index) in exits { + if *slot == slot_height { + info!( + "Including exit at slot height {} for validator {}.", + slot_height, validator_index + ); + let exit = build_exit(&harness, *validator_index); + harness.add_exit(exit); + } + } + } + // Build a block or skip a slot. match self.config.skip_slots { Some(ref skip_slots) if skip_slots.contains(&slot_height) => { @@ -185,6 +200,33 @@ impl TestCase { } } +fn build_exit(harness: &BeaconChainHarness, validator_index: u64) -> Exit { + let epoch = harness + .beacon_chain + .state + .read() + .current_epoch(&harness.spec); + + let mut exit = Exit { + epoch, + validator_index, + signature: Signature::empty_signature(), + }; + + let message = exit.hash_tree_root(); + + exit.signature = harness + .validator_sign( + validator_index as usize, + &message[..], + epoch, + harness.spec.domain_exit, + ) + .expect("Unable to sign Exit"); + + exit +} + /// Builds an `AttesterSlashing` for some `validator_indices`. /// /// Signs the message using a `BeaconChainHarness`. diff --git a/beacon_node/beacon_chain/test_harness/src/test_case/config.rs b/beacon_node/beacon_chain/test_harness/src/test_case/config.rs index d08e7fe40..19bce8e76 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case/config.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case/config.rs @@ -7,6 +7,7 @@ pub type ValidatorIndex = u64; pub type ValidatorIndices = Vec; pub type DepositTuple = (SlotHeight, Deposit, Keypair); +pub type ExitTuple = (SlotHeight, ValidatorIndex); pub type ProposerSlashingTuple = (SlotHeight, ValidatorIndex); pub type AttesterSlashingTuple = (SlotHeight, ValidatorIndices); @@ -27,6 +28,8 @@ pub struct Config { pub proposer_slashings: Option>, /// Attester slashings to be including during execution. pub attester_slashings: Option>, + /// Exits to be including during execution. + pub exits: Option>, } impl Config { @@ -43,10 +46,26 @@ impl Config { deposits: parse_deposits(&yaml), proposer_slashings: parse_proposer_slashings(&yaml), attester_slashings: parse_attester_slashings(&yaml), + exits: parse_exits(&yaml), } } } +/// Parse the `attester_slashings` section of the YAML document. +fn parse_exits(yaml: &Yaml) -> Option> { + let mut tuples = vec![]; + + for exit in yaml["exits"].as_vec()? { + let slot = as_u64(exit, "slot").expect("Incomplete exit (slot)"); + let validator_index = + as_u64(exit, "validator_index").expect("Incomplete exit (validator_index)"); + + tuples.push((SlotHeight::from(slot), validator_index)); + } + + Some(tuples) +} + /// Parse the `attester_slashings` section of the YAML document. fn parse_attester_slashings(yaml: &Yaml) -> Option> { let mut slashings = vec![]; diff --git a/beacon_node/beacon_chain/test_harness/src/test_case/state_check.rs b/beacon_node/beacon_chain/test_harness/src/test_case/state_check.rs index 90c622894..c44992a97 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case/state_check.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case/state_check.rs @@ -13,8 +13,10 @@ pub struct StateCheck { 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. + /// A list of validator indices which have been fully exited. Must be in ascending order. pub exited_validators: Option>, + /// A list of validator indices which have had an exit initiated. Must be in ascending order. + pub exit_initiated_validators: Option>, } impl StateCheck { @@ -27,6 +29,7 @@ impl StateCheck { num_validators: as_usize(&yaml, "num_validators"), slashed_validators: as_vec_u64(&yaml, "slashed_validators"), exited_validators: as_vec_u64(&yaml, "exited_validators"), + exit_initiated_validators: as_vec_u64(&yaml, "exit_initiated_validators"), } } @@ -40,6 +43,7 @@ impl StateCheck { info!("Running state check for slot height {}.", self.slot); + // Check the state slot. assert_eq!( self.slot, state.slot - spec.genesis_epoch.start_slot(spec.epoch_length), @@ -55,6 +59,7 @@ impl StateCheck { info!("OK: num_validators = {}.", num_validators); } + // Check for slashed validators. if let Some(ref slashed_validators) = self.slashed_validators { let actually_slashed_validators: Vec = state .validator_registry @@ -75,6 +80,7 @@ impl StateCheck { info!("OK: slashed_validators = {:?}.", slashed_validators); } + // Check for exited validators. if let Some(ref exited_validators) = self.exited_validators { let actually_exited_validators: Vec = state .validator_registry @@ -94,5 +100,29 @@ impl StateCheck { ); info!("OK: exited_validators = {:?}.", exited_validators); } + + // Check for validators that have initiated exit. + if let Some(ref exit_initiated_validators) = self.exit_initiated_validators { + let actual: Vec = state + .validator_registry + .iter() + .enumerate() + .filter_map(|(i, validator)| { + if validator.has_initiated_exit() { + Some(i as u64) + } else { + None + } + }) + .collect(); + assert_eq!( + actual, *exit_initiated_validators, + "Exit initiated validators != expected." + ); + info!( + "OK: exit_initiated_validators = {:?}.", + exit_initiated_validators + ); + } } } diff --git a/eth2/types/src/validator.rs b/eth2/types/src/validator.rs index 587a48a1f..42a2b31f2 100644 --- a/eth2/types/src/validator.rs +++ b/eth2/types/src/validator.rs @@ -68,6 +68,11 @@ impl Validator { pub fn is_penalized_at(&self, epoch: Epoch) -> bool { self.penalized_epoch <= epoch } + + /// Returns `true` if the validator is considered penalized at some epoch. + pub fn has_initiated_exit(&self) -> bool { + self.status_flags == Some(StatusFlags::InitiatedExit) + } } impl Default for Validator {