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 {