Merge pull request #116 from g-r-a-n-t/validator-record-update

Validator record update
This commit is contained in:
Paul Hauner 2019-01-09 09:57:24 +11:00 committed by GitHub
commit 95d3ee554a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 227 additions and 297 deletions

View File

@ -46,7 +46,6 @@ members = [
"beacon_chain/utils/slot-clock", "beacon_chain/utils/slot-clock",
"beacon_chain/utils/ssz", "beacon_chain/utils/ssz",
"beacon_chain/utils/vec_shuffle", "beacon_chain/utils/vec_shuffle",
"beacon_chain/validator_change",
"beacon_chain/validator_induction", "beacon_chain/validator_induction",
"beacon_chain/validator_shuffling", "beacon_chain/validator_shuffling",
"lighthouse/db", "lighthouse/db",

View File

@ -1,6 +1,5 @@
use spec::ChainSpec; use spec::ChainSpec;
use types::{BeaconState, CrosslinkRecord, ForkData, ValidatorStatus}; use types::{BeaconState, CrosslinkRecord, ForkData};
use validator_induction::ValidatorInductor;
use validator_shuffling::{shard_and_committees_for_cycle, ValidatorAssignmentError}; use validator_shuffling::{shard_and_committees_for_cycle, ValidatorAssignmentError};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -11,26 +10,11 @@ pub enum Error {
} }
pub fn genesis_beacon_state(spec: &ChainSpec) -> Result<BeaconState, Error> { pub fn genesis_beacon_state(spec: &ChainSpec) -> Result<BeaconState, Error> {
/*
* Parse the ValidatorRegistrations into ValidatorRecords and induct them.
*
* Ignore any records which fail proof-of-possession or are invalid.
*/
let validators = {
let mut inductor = ValidatorInductor::new(0, spec.shard_count, vec![]);
for registration in &spec.initial_validators {
let _ = inductor.induct(&registration, ValidatorStatus::Active);
}
inductor.to_vec()
};
/* /*
* Assign the validators to shards, using all zeros as the seed. * Assign the validators to shards, using all zeros as the seed.
*
* Crystallizedstate stores two cycles, so we simply repeat the same assignment twice.
*/ */
let _shard_and_committee_for_slots = { let _shard_and_committee_for_slots = {
let mut a = shard_and_committees_for_cycle(&[0; 32], &validators, 0, &spec)?; let mut a = shard_and_committees_for_cycle(&[0; 32], &spec.initial_validators, 0, &spec)?;
let mut b = a.clone(); let mut b = a.clone();
a.append(&mut b); a.append(&mut b);
a a
@ -55,7 +39,8 @@ pub fn genesis_beacon_state(spec: &ChainSpec) -> Result<BeaconState, Error> {
/* /*
* Validator registry * Validator registry
*/ */
validator_registry: validators, validator_registry: spec.initial_validators.clone(),
validator_balances: spec.initial_balances.clone(),
validator_registry_latest_change_slot: spec.initial_slot_number, validator_registry_latest_change_slot: spec.initial_slot_number,
validator_registry_exit_count: 0, validator_registry_exit_count: 0,
validator_registry_delta_chain_tip: spec.zero_hash, validator_registry_delta_chain_tip: spec.zero_hash,
@ -100,7 +85,6 @@ mod tests {
extern crate bls; extern crate bls;
extern crate validator_induction; extern crate validator_induction;
use self::bls::{create_proof_of_possession, Keypair};
use super::*; use super::*;
// TODO: enhance these tests. // TODO: enhance these tests.
@ -117,19 +101,4 @@ mod tests {
spec.initial_validators.len() spec.initial_validators.len()
); );
} }
#[test]
fn test_genesis_bad_validator() {
let mut spec = ChainSpec::foundation();
let random_kp = Keypair::random();
spec.initial_validators[4].proof_of_possession = create_proof_of_possession(&random_kp);
let state = genesis_beacon_state(&spec).unwrap();
assert_eq!(
state.validator_registry.len(),
spec.initial_validators.len() - 1
);
}
} }

View File

@ -1,7 +1,10 @@
use super::ChainSpec; use super::ChainSpec;
use bls::{create_proof_of_possession, Keypair, PublicKey, SecretKey}; use bls::{Keypair, PublicKey, SecretKey};
use types::{Address, Hash256, ValidatorRegistration}; use types::{Address, Hash256, ValidatorRecord};
/// The size of a validators deposit in GWei.
pub const DEPOSIT_GWEI: u64 = 32_000_000_000;
impl ChainSpec { impl ChainSpec {
/// Returns a `ChainSpec` compatible with the specification from Ethereum Foundation. /// Returns a `ChainSpec` compatible with the specification from Ethereum Foundation.
@ -64,14 +67,15 @@ impl ChainSpec {
* Intialization parameters * Intialization parameters
*/ */
initial_validators: initial_validators_for_testing(), initial_validators: initial_validators_for_testing(),
initial_balances: initial_balances_for_testing(),
genesis_time: 1544672897, genesis_time: 1544672897,
processed_pow_receipt_root: Hash256::from("pow_root".as_bytes()), processed_pow_receipt_root: Hash256::from("pow_root".as_bytes()),
} }
} }
} }
/// Generate a set of validator registrations to use with testing until the real chain starts. /// Generate a set of validator records to use with testing until the real chain starts.
fn initial_validators_for_testing() -> Vec<ValidatorRegistration> { fn initial_validators_for_testing() -> Vec<ValidatorRecord> {
// Some dummy private keys to start with. // Some dummy private keys to start with.
let key_strings = vec![ let key_strings = vec![
"jzjxxgjajfjrmgodszzsgqccmhnyvetcuxobhtynojtpdtbj", "jzjxxgjajfjrmgodszzsgqccmhnyvetcuxobhtynojtpdtbj",
@ -94,19 +98,28 @@ fn initial_validators_for_testing() -> Vec<ValidatorRegistration> {
pk: public_key, pk: public_key,
} }
}; };
let validator_registration = ValidatorRegistration { let validator_record = ValidatorRecord {
pubkey: keypair.pk.clone(), pubkey: keypair.pk.clone(),
withdrawal_shard: 0, withdrawal_credentials: Hash256::zero(),
withdrawal_address: Address::random(), randao_commitment: Hash256::zero(),
randao_commitment: Hash256::random(), randao_layers: 0,
proof_of_possession: create_proof_of_possession(&keypair), status: From::from(0),
latest_status_change_slot: 0,
exit_count: 0,
custody_commitment: Hash256::zero(),
latest_custody_reseed_slot: 0,
penultimate_custody_reseed_slot: 0
}; };
initial_validators.push(validator_registration); initial_validators.push(validator_record);
} }
initial_validators initial_validators
} }
fn initial_balances_for_testing() -> Vec<u64> {
vec![DEPOSIT_GWEI; 4]
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -3,7 +3,7 @@ extern crate types;
mod foundation; mod foundation;
use types::{Address, Hash256, ValidatorRegistration}; use types::{Address, Hash256, ValidatorRecord};
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub struct ChainSpec { pub struct ChainSpec {
@ -60,7 +60,8 @@ pub struct ChainSpec {
/* /*
* Intialization parameters * Intialization parameters
*/ */
pub initial_validators: Vec<ValidatorRegistration>, pub initial_validators: Vec<ValidatorRecord>,
pub initial_balances: Vec<u64>,
pub genesis_time: u64, pub genesis_time: u64,
pub processed_pow_receipt_root: Hash256, pub processed_pow_receipt_root: Hash256,
} }

View File

@ -10,7 +10,7 @@ use crate::test_utils::TestRandom;
use rand::RngCore; use rand::RngCore;
use ssz::{Decodable, DecodeError, Encodable, SszStream}; use ssz::{Decodable, DecodeError, Encodable, SszStream};
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone, Default)]
pub struct BeaconState { pub struct BeaconState {
// Misc // Misc
pub slot: u64, pub slot: u64,
@ -19,6 +19,7 @@ pub struct BeaconState {
// Validator registry // Validator registry
pub validator_registry: Vec<ValidatorRecord>, pub validator_registry: Vec<ValidatorRecord>,
pub validator_balances: Vec<u64>,
pub validator_registry_latest_change_slot: u64, pub validator_registry_latest_change_slot: u64,
pub validator_registry_exit_count: u64, pub validator_registry_exit_count: u64,
pub validator_registry_delta_chain_tip: Hash256, pub validator_registry_delta_chain_tip: Hash256,
@ -61,6 +62,7 @@ impl Encodable for BeaconState {
s.append(&self.genesis_time); s.append(&self.genesis_time);
s.append(&self.fork_data); s.append(&self.fork_data);
s.append(&self.validator_registry); s.append(&self.validator_registry);
s.append(&self.validator_balances);
s.append(&self.validator_registry_latest_change_slot); s.append(&self.validator_registry_latest_change_slot);
s.append(&self.validator_registry_exit_count); s.append(&self.validator_registry_exit_count);
s.append(&self.validator_registry_delta_chain_tip); s.append(&self.validator_registry_delta_chain_tip);
@ -88,6 +90,7 @@ impl Decodable for BeaconState {
let (genesis_time, i) = <_>::ssz_decode(bytes, i)?; let (genesis_time, i) = <_>::ssz_decode(bytes, i)?;
let (fork_data, i) = <_>::ssz_decode(bytes, i)?; let (fork_data, i) = <_>::ssz_decode(bytes, i)?;
let (validator_registry, i) = <_>::ssz_decode(bytes, i)?; let (validator_registry, i) = <_>::ssz_decode(bytes, i)?;
let (validator_balances, i) = <_>::ssz_decode(bytes, i)?;
let (validator_registry_latest_change_slot, i) = <_>::ssz_decode(bytes, i)?; let (validator_registry_latest_change_slot, i) = <_>::ssz_decode(bytes, i)?;
let (validator_registry_exit_count, i) = <_>::ssz_decode(bytes, i)?; let (validator_registry_exit_count, i) = <_>::ssz_decode(bytes, i)?;
let (validator_registry_delta_chain_tip, i) = <_>::ssz_decode(bytes, i)?; let (validator_registry_delta_chain_tip, i) = <_>::ssz_decode(bytes, i)?;
@ -113,6 +116,7 @@ impl Decodable for BeaconState {
genesis_time, genesis_time,
fork_data, fork_data,
validator_registry, validator_registry,
validator_balances,
validator_registry_latest_change_slot, validator_registry_latest_change_slot,
validator_registry_exit_count, validator_registry_exit_count,
validator_registry_delta_chain_tip, validator_registry_delta_chain_tip,
@ -144,6 +148,7 @@ impl<T: RngCore> TestRandom<T> for BeaconState {
genesis_time: <_>::random_for_test(rng), genesis_time: <_>::random_for_test(rng),
fork_data: <_>::random_for_test(rng), fork_data: <_>::random_for_test(rng),
validator_registry: <_>::random_for_test(rng), validator_registry: <_>::random_for_test(rng),
validator_balances: <_>::random_for_test(rng),
validator_registry_latest_change_slot: <_>::random_for_test(rng), validator_registry_latest_change_slot: <_>::random_for_test(rng),
validator_registry_exit_count: <_>::random_for_test(rng), validator_registry_exit_count: <_>::random_for_test(rng),
validator_registry_delta_chain_tip: <_>::random_for_test(rng), validator_registry_delta_chain_tip: <_>::random_for_test(rng),

View File

@ -9,6 +9,7 @@ pub struct DepositInput {
pub pubkey: PublicKey, pub pubkey: PublicKey,
pub withdrawal_credentials: Hash256, pub withdrawal_credentials: Hash256,
pub randao_commitment: Hash256, pub randao_commitment: Hash256,
pub custody_commitment: Hash256,
pub proof_of_possession: Signature, pub proof_of_possession: Signature,
} }
@ -17,6 +18,7 @@ impl Encodable for DepositInput {
s.append(&self.pubkey); s.append(&self.pubkey);
s.append(&self.withdrawal_credentials); s.append(&self.withdrawal_credentials);
s.append(&self.randao_commitment); s.append(&self.randao_commitment);
s.append(&self.custody_commitment);
s.append(&self.proof_of_possession); s.append(&self.proof_of_possession);
} }
} }
@ -26,6 +28,7 @@ impl Decodable for DepositInput {
let (pubkey, i) = <_>::ssz_decode(bytes, i)?; let (pubkey, i) = <_>::ssz_decode(bytes, i)?;
let (withdrawal_credentials, i) = <_>::ssz_decode(bytes, i)?; let (withdrawal_credentials, i) = <_>::ssz_decode(bytes, i)?;
let (randao_commitment, i) = <_>::ssz_decode(bytes, i)?; let (randao_commitment, i) = <_>::ssz_decode(bytes, i)?;
let (custody_commitment, i) = <_>::ssz_decode(bytes, i)?;
let (proof_of_possession, i) = <_>::ssz_decode(bytes, i)?; let (proof_of_possession, i) = <_>::ssz_decode(bytes, i)?;
Ok(( Ok((
@ -33,6 +36,7 @@ impl Decodable for DepositInput {
pubkey, pubkey,
withdrawal_credentials, withdrawal_credentials,
randao_commitment, randao_commitment,
custody_commitment,
proof_of_possession, proof_of_possession,
}, },
i, i,
@ -46,6 +50,7 @@ impl<T: RngCore> TestRandom<T> for DepositInput {
pubkey: <_>::random_for_test(rng), pubkey: <_>::random_for_test(rng),
withdrawal_credentials: <_>::random_for_test(rng), withdrawal_credentials: <_>::random_for_test(rng),
randao_commitment: <_>::random_for_test(rng), randao_commitment: <_>::random_for_test(rng),
custody_commitment: <_>::random_for_test(rng),
proof_of_possession: <_>::random_for_test(rng), proof_of_possession: <_>::random_for_test(rng),
} }
} }

View File

@ -2,7 +2,7 @@ use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use crate::test_utils::TestRandom; use crate::test_utils::TestRandom;
use rand::RngCore; use rand::RngCore;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Default)]
pub struct ForkData { pub struct ForkData {
pub pre_fork_version: u64, pub pre_fork_version: u64,
pub post_fork_version: u64, pub post_fork_version: u64,

View File

@ -26,7 +26,6 @@ pub mod shard_reassignment_record;
pub mod special_record; pub mod special_record;
pub mod slashable_vote_data; pub mod slashable_vote_data;
pub mod validator_record; pub mod validator_record;
pub mod validator_registration;
use self::ethereum_types::{H160, H256, U256}; use self::ethereum_types::{H160, H256, U256};
use std::collections::HashMap; use std::collections::HashMap;
@ -50,7 +49,6 @@ pub use crate::slashable_vote_data::SlashableVoteData;
pub use crate::shard_committee::ShardCommittee; pub use crate::shard_committee::ShardCommittee;
pub use crate::special_record::{SpecialRecord, SpecialRecordKind}; pub use crate::special_record::{SpecialRecord, SpecialRecordKind};
pub use crate::validator_record::{ValidatorRecord, ValidatorStatus}; pub use crate::validator_record::{ValidatorRecord, ValidatorStatus};
pub use crate::validator_registration::ValidatorRegistration;
pub type Hash256 = H256; pub type Hash256 = H256;
pub type Address = H160; pub type Address = H160;

View File

@ -1,5 +1,5 @@
use super::bls::PublicKey; use super::bls::PublicKey;
use super::{Address, Hash256}; use super::{Hash256};
use crate::test_utils::TestRandom; use crate::test_utils::TestRandom;
use rand::RngCore; use rand::RngCore;
use ssz::{Decodable, DecodeError, Encodable, SszStream}; use ssz::{Decodable, DecodeError, Encodable, SszStream};
@ -32,13 +32,15 @@ impl convert::From<u8> for ValidatorStatus {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ValidatorRecord { pub struct ValidatorRecord {
pub pubkey: PublicKey, pub pubkey: PublicKey,
pub withdrawal_shard: u64, pub withdrawal_credentials: Hash256,
pub withdrawal_address: Address,
pub randao_commitment: Hash256, pub randao_commitment: Hash256,
pub randao_last_change: u64, pub randao_layers: u64,
pub balance: u64,
pub status: ValidatorStatus, pub status: ValidatorStatus,
pub exit_slot: u64, pub latest_status_change_slot: u64,
pub exit_count: u64,
pub custody_commitment: Hash256,
pub latest_custody_reseed_slot: u64,
pub penultimate_custody_reseed_slot: u64
} }
impl ValidatorRecord { impl ValidatorRecord {
@ -94,37 +96,43 @@ impl<T: RngCore> TestRandom<T> for ValidatorStatus {
impl Encodable for ValidatorRecord { impl Encodable for ValidatorRecord {
fn ssz_append(&self, s: &mut SszStream) { fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.pubkey); s.append(&self.pubkey);
s.append(&self.withdrawal_shard); s.append(&self.withdrawal_credentials);
s.append(&self.withdrawal_address);
s.append(&self.randao_commitment); s.append(&self.randao_commitment);
s.append(&self.randao_last_change); s.append(&self.randao_layers);
s.append(&self.balance);
s.append(&self.status); s.append(&self.status);
s.append(&self.exit_slot); s.append(&self.latest_status_change_slot);
s.append(&self.exit_count);
s.append(&self.custody_commitment);
s.append(&self.latest_custody_reseed_slot);
s.append(&self.penultimate_custody_reseed_slot);
} }
} }
impl Decodable for ValidatorRecord { impl Decodable for ValidatorRecord {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (pubkey, i) = <_>::ssz_decode(bytes, i)?; let (pubkey, i) = <_>::ssz_decode(bytes, i)?;
let (withdrawal_shard, i) = <_>::ssz_decode(bytes, i)?; let (withdrawal_credentials, i) = <_>::ssz_decode(bytes, i)?;
let (withdrawal_address, i) = <_>::ssz_decode(bytes, i)?;
let (randao_commitment, i) = <_>::ssz_decode(bytes, i)?; let (randao_commitment, i) = <_>::ssz_decode(bytes, i)?;
let (randao_last_change, i) = <_>::ssz_decode(bytes, i)?; let (randao_layers, i) = <_>::ssz_decode(bytes, i)?;
let (balance, i) = <_>::ssz_decode(bytes, i)?;
let (status, i) = <_>::ssz_decode(bytes, i)?; let (status, i) = <_>::ssz_decode(bytes, i)?;
let (exit_slot, i) = <_>::ssz_decode(bytes, i)?; let (latest_status_change_slot, i) = <_>::ssz_decode(bytes, i)?;
let (exit_count, i) = <_>::ssz_decode(bytes, i)?;
let (custody_commitment, i) = <_>::ssz_decode(bytes, i)?;
let (latest_custody_reseed_slot, i) = <_>::ssz_decode(bytes, i)?;
let (penultimate_custody_reseed_slot, i) = <_>::ssz_decode(bytes, i)?;
Ok(( Ok((
Self { Self {
pubkey, pubkey,
withdrawal_shard, withdrawal_credentials,
withdrawal_address,
randao_commitment, randao_commitment,
randao_last_change, randao_layers,
balance,
status, status,
exit_slot, latest_status_change_slot,
exit_count,
custody_commitment,
latest_custody_reseed_slot,
penultimate_custody_reseed_slot
}, },
i, i,
)) ))
@ -135,13 +143,15 @@ impl<T: RngCore> TestRandom<T> for ValidatorRecord {
fn random_for_test(rng: &mut T) -> Self { fn random_for_test(rng: &mut T) -> Self {
Self { Self {
pubkey: <_>::random_for_test(rng), pubkey: <_>::random_for_test(rng),
withdrawal_shard: <_>::random_for_test(rng), withdrawal_credentials: <_>::random_for_test(rng),
withdrawal_address: <_>::random_for_test(rng),
randao_commitment: <_>::random_for_test(rng), randao_commitment: <_>::random_for_test(rng),
randao_last_change: <_>::random_for_test(rng), randao_layers: <_>::random_for_test(rng),
balance: <_>::random_for_test(rng),
status: <_>::random_for_test(rng), status: <_>::random_for_test(rng),
exit_slot: <_>::random_for_test(rng), latest_status_change_slot: <_>::random_for_test(rng),
exit_count: <_>::random_for_test(rng),
custody_commitment: <_>::random_for_test(rng),
latest_custody_reseed_slot: <_>::random_for_test(rng),
penultimate_custody_reseed_slot: <_>::random_for_test(rng),
} }
} }
} }

View File

@ -8,3 +8,4 @@ edition = "2018"
bls = { path = "../utils/bls" } bls = { path = "../utils/bls" }
hashing = { path = "../utils/hashing" } hashing = { path = "../utils/hashing" }
types = { path = "../types" } types = { path = "../types" }
spec = { path = "../spec" }

View File

@ -1,278 +1,206 @@
use bls::verify_proof_of_possession; use bls::{verify_proof_of_possession};
use types::{ValidatorRecord, ValidatorRegistration, ValidatorStatus}; use types::{BeaconState, Deposit, ValidatorRecord, ValidatorStatus};
use spec::ChainSpec;
/// The size of a validators deposit in GWei.
pub const DEPOSIT_GWEI: u64 = 32_000_000_000;
/// Inducts validators into a `CrystallizedState`.
pub struct ValidatorInductor {
pub current_slot: u64,
pub shard_count: u64,
validators: Vec<ValidatorRecord>,
empty_validator_start: usize,
}
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum ValidatorInductionError { pub enum ValidatorInductionError {
InvalidShard, InvalidShard,
InvaidProofOfPossession, InvaidProofOfPossession,
InvalidWithdrawalCredentials
} }
impl ValidatorInductor { pub fn process_deposit(
pub fn new(current_slot: u64, shard_count: u64, validators: Vec<ValidatorRecord>) -> Self { state: &mut BeaconState,
Self { deposit: &Deposit,
current_slot, spec: &ChainSpec)
shard_count, -> Result<usize, ValidatorInductionError> {
validators, let deposit_input = &deposit.deposit_data.deposit_input;
empty_validator_start: 0, let deposit_data = &deposit.deposit_data;
}
}
/// Attempt to induct a validator into the CrystallizedState. // TODO: Update the signature validation as defined in the spec once issues #91 and #70 are completed
/// if !verify_proof_of_possession(&deposit_input.proof_of_possession, &deposit_input.pubkey) {
/// Returns an error if the registration is invalid, otherwise returns the index of the
/// validator in `CrystallizedState.validators`.
pub fn induct(
&mut self,
rego: &ValidatorRegistration,
status: ValidatorStatus,
) -> Result<usize, ValidatorInductionError> {
let v = self.process_registration(rego, status)?;
Ok(self.add_validator(v))
}
/// Verify a `ValidatorRegistration` and return a `ValidatorRecord` if valid.
fn process_registration(
&self,
r: &ValidatorRegistration,
status: ValidatorStatus,
) -> Result<ValidatorRecord, ValidatorInductionError> {
/*
* Ensure withdrawal shard is not too high.
*/
if r.withdrawal_shard > self.shard_count {
return Err(ValidatorInductionError::InvalidShard);
}
/*
* Prove validator has knowledge of their secret key.
*/
if !verify_proof_of_possession(&r.proof_of_possession, &r.pubkey) {
return Err(ValidatorInductionError::InvaidProofOfPossession); return Err(ValidatorInductionError::InvaidProofOfPossession);
} }
Ok(ValidatorRecord { let validator_index = state.validator_registry.iter()
pubkey: r.pubkey.clone(), .position(|validator| validator.pubkey == deposit_input.pubkey);
withdrawal_shard: r.withdrawal_shard,
withdrawal_address: r.withdrawal_address, match validator_index {
randao_commitment: r.randao_commitment, Some(i) => {
randao_last_change: self.current_slot, if state.validator_registry[i].withdrawal_credentials == deposit_input.withdrawal_credentials {
balance: DEPOSIT_GWEI, state.validator_balances[i] += deposit_data.value;
status: status, return Ok(i);
exit_slot: 0,
})
} }
/// Returns the index of the first `ValidatorRecord` in the `CrystallizedState` where Err(ValidatorInductionError::InvalidWithdrawalCredentials)
/// `validator.status == Withdrawn`. If no such record exists, `None` is returned. },
fn first_withdrawn_validator(&mut self) -> Option<usize> { None => {
for i in self.empty_validator_start..self.validators.len() { let validator = ValidatorRecord {
if self.validators[i].status == ValidatorStatus::Withdrawn { pubkey: deposit_input.pubkey.clone(),
self.empty_validator_start = i + 1; withdrawal_credentials: deposit_input.withdrawal_credentials,
randao_commitment: deposit_input.randao_commitment,
randao_layers: 0,
status: ValidatorStatus::PendingActivation,
latest_status_change_slot: state.validator_registry_latest_change_slot,
exit_count: 0,
custody_commitment: deposit_input.custody_commitment,
latest_custody_reseed_slot: 0,
penultimate_custody_reseed_slot: 0
};
match min_empty_validator_index(state, spec) {
Some(i) => {
state.validator_registry[i] = validator;
state.validator_balances[i] = deposit_data.value;
Ok(i)
},
None => {
state.validator_registry.push(validator);
state.validator_balances.push(deposit_data.value);
Ok(state.validator_registry.len() - 1)
}
}
}
}
}
fn min_empty_validator_index(
state: &BeaconState,
spec: &ChainSpec
) -> Option<usize> {
for i in 0..state.validator_registry.len() {
if state.validator_balances[i] == 0
&& state.validator_registry[i].latest_status_change_slot
+ spec.zero_balance_validator_ttl <= state.slot {
return Some(i); return Some(i);
} }
} }
None None
} }
/// Adds a `ValidatorRecord` to the `CrystallizedState` by replacing first validator where
/// `validator.status == Withdraw`. If no such withdrawn validator exists, adds the new
/// validator to the end of the list.
fn add_validator(&mut self, v: ValidatorRecord) -> usize {
match self.first_withdrawn_validator() {
Some(i) => {
self.validators[i] = v;
i
}
None => {
self.validators.push(v);
self.validators.len() - 1
}
}
}
pub fn to_vec(self) -> Vec<ValidatorRecord> {
self.validators
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use bls::{create_proof_of_possession, Keypair}; use bls::{create_proof_of_possession, Keypair};
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use types::{Address, Hash256};
fn registration_equals_record(reg: &ValidatorRegistration, rec: &ValidatorRecord) -> bool { /// The size of a validators deposit in GWei.
(reg.pubkey == rec.pubkey) pub const DEPOSIT_GWEI: u64 = 32_000_000_000;
& (reg.withdrawal_shard == rec.withdrawal_shard)
& (reg.withdrawal_address == rec.withdrawal_address) fn get_deposit() -> Deposit {
& (reg.randao_commitment == rec.randao_commitment) let mut rng = XorShiftRng::from_seed([42; 16]);
& (verify_proof_of_possession(&reg.proof_of_possession, &rec.pubkey)) let mut deposit = Deposit::random_for_test(&mut rng);
}
/// Generate a basic working ValidatorRegistration for use in tests.
fn get_registration() -> ValidatorRegistration {
let kp = Keypair::random(); let kp = Keypair::random();
ValidatorRegistration { deposit.deposit_data.deposit_input.pubkey = kp.pk.clone();
pubkey: kp.pk.clone(), deposit.deposit_data.deposit_input.proof_of_possession = create_proof_of_possession(&kp);
withdrawal_shard: 0, deposit
withdrawal_address: Address::zero(),
randao_commitment: Hash256::zero(),
proof_of_possession: create_proof_of_possession(&kp),
} }
fn get_validator() -> ValidatorRecord {
let mut rng = XorShiftRng::from_seed([42; 16]);
ValidatorRecord::random_for_test(&mut rng)
}
fn deposit_equals_record(dep: &Deposit, val: &ValidatorRecord) -> bool {
(dep.deposit_data.deposit_input.pubkey == val.pubkey)
& (dep.deposit_data.deposit_input.withdrawal_credentials == val.withdrawal_credentials)
& (dep.deposit_data.deposit_input.randao_commitment == val.randao_commitment)
& (verify_proof_of_possession(&dep.deposit_data.deposit_input.proof_of_possession, &val.pubkey))
} }
#[test] #[test]
fn test_validator_inductor_valid_empty_validators() { fn test_process_deposit_valid_empty_validators() {
let validators = vec![]; let mut state = BeaconState::default();
let mut deposit = get_deposit();
let spec = ChainSpec::foundation();
deposit.deposit_data.value = DEPOSIT_GWEI;
let r = get_registration(); let result = process_deposit(&mut state, &deposit, &spec);
let mut inductor = ValidatorInductor::new(0, 1024, validators);
let result = inductor.induct(&r, ValidatorStatus::PendingActivation);
let validators = inductor.to_vec();
assert_eq!(result.unwrap(), 0); assert_eq!(result.unwrap(), 0);
assert!(registration_equals_record(&r, &validators[0])); assert!(deposit_equals_record(&deposit, &state.validator_registry[0]));
assert_eq!(validators.len(), 1); assert_eq!(state.validator_registry.len(), 1);
assert_eq!(state.validator_balances.len(), 1);
} }
#[test] #[test]
fn test_validator_inductor_status() { fn test_process_deposits_empty_validators() {
let validators = vec![]; let mut state = BeaconState::default();
let spec = ChainSpec::foundation();
let r = get_registration(); for i in 0..5 {
let mut deposit = get_deposit();
let mut inductor = ValidatorInductor::new(0, 1024, validators); let result = process_deposit(&mut state, &deposit, &spec);
let _ = inductor.induct(&r, ValidatorStatus::PendingActivation); deposit.deposit_data.value = DEPOSIT_GWEI;
let _ = inductor.induct(&r, ValidatorStatus::Active); assert_eq!(result.unwrap(), i);
let validators = inductor.to_vec(); assert!(deposit_equals_record(&deposit, &state.validator_registry[i]));
assert_eq!(state.validator_registry.len(), i + 1);
assert!(validators[0].status == ValidatorStatus::PendingActivation); assert_eq!(state.validator_balances.len(), i + 1);
assert!(validators[1].status == ValidatorStatus::Active); }
assert_eq!(validators.len(), 2);
} }
#[test] #[test]
fn test_validator_inductor_valid_all_active_validators() { fn test_process_deposit_top_out() {
let mut rng = XorShiftRng::from_seed([42; 16]); let mut state = BeaconState::default();
let mut validators = vec![]; let spec = ChainSpec::foundation();
for _ in 0..5 {
let mut v = ValidatorRecord::random_for_test(&mut rng);
v.status = ValidatorStatus::Active;
validators.push(v);
}
let r = get_registration(); let mut deposit = get_deposit();
let mut validator = get_validator();
let mut inductor = ValidatorInductor::new(0, 1024, validators); deposit.deposit_data.value = DEPOSIT_GWEI;
let result = inductor.induct(&r, ValidatorStatus::PendingActivation); validator.pubkey = deposit.deposit_data.deposit_input.pubkey.clone();
let validators = inductor.to_vec(); validator.withdrawal_credentials = deposit.deposit_data.deposit_input.withdrawal_credentials;
validator.randao_commitment = deposit.deposit_data.deposit_input.randao_commitment;
assert_eq!(result.unwrap(), 5); state.validator_registry.push(validator);
assert!(registration_equals_record(&r, &validators[5])); state.validator_balances.push(DEPOSIT_GWEI);
assert_eq!(validators.len(), 6);
}
#[test] let result = process_deposit(&mut state, &deposit, &spec);
fn test_validator_inductor_valid_all_second_validator_withdrawn() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut validators = vec![];
let mut v = ValidatorRecord::random_for_test(&mut rng);
v.status = ValidatorStatus::Active;
validators.push(v);
for _ in 0..4 {
let mut v = ValidatorRecord::random_for_test(&mut rng);
v.status = ValidatorStatus::Withdrawn;
validators.push(v);
}
let r = get_registration();
let mut inductor = ValidatorInductor::new(0, 1024, validators);
let result = inductor.induct(&r, ValidatorStatus::PendingActivation);
let validators = inductor.to_vec();
assert_eq!(result.unwrap(), 1);
assert!(registration_equals_record(&r, &validators[1]));
assert_eq!(validators.len(), 5);
}
#[test]
fn test_validator_inductor_valid_all_withdrawn_validators() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut validators = vec![];
for _ in 0..5 {
let mut v = ValidatorRecord::random_for_test(&mut rng);
v.status = ValidatorStatus::Withdrawn;
validators.push(v);
}
/*
* Ensure the first validator gets the 0'th slot
*/
let r = get_registration();
let mut inductor = ValidatorInductor::new(0, 1024, validators);
let result = inductor.induct(&r, ValidatorStatus::PendingActivation);
let validators = inductor.to_vec();
assert_eq!(result.unwrap(), 0); assert_eq!(result.unwrap(), 0);
assert!(registration_equals_record(&r, &validators[0])); assert!(deposit_equals_record(&deposit, &state.validator_registry[0]));
assert_eq!(state.validator_balances[0], DEPOSIT_GWEI * 2);
/* assert_eq!(state.validator_registry.len(), 1);
* Ensure the second validator gets the 1'st slot assert_eq!(state.validator_balances.len(), 1);
*/
let r_two = get_registration();
let mut inductor = ValidatorInductor::new(0, 1024, validators);
let result = inductor.induct(&r_two, ValidatorStatus::PendingActivation);
let validators = inductor.to_vec();
assert_eq!(result.unwrap(), 1);
assert!(registration_equals_record(&r_two, &validators[1]));
assert_eq!(validators.len(), 5);
} }
#[test] #[test]
fn test_validator_inductor_shard_too_high() { fn test_process_deposit_replace_validator() {
let validators = vec![]; let mut state = BeaconState::default();
let spec = ChainSpec::foundation();
let mut r = get_registration(); let mut validator = get_validator();
r.withdrawal_shard = 1025; validator.latest_status_change_slot = 0;
state.validator_registry.push(validator);
state.validator_balances.push(0);
let mut inductor = ValidatorInductor::new(0, 1024, validators); let mut deposit = get_deposit();
let result = inductor.induct(&r, ValidatorStatus::PendingActivation); deposit.deposit_data.value = DEPOSIT_GWEI;
let validators = inductor.to_vec(); state.slot = spec.zero_balance_validator_ttl;
assert_eq!(result, Err(ValidatorInductionError::InvalidShard)); let result = process_deposit(&mut state, &deposit, &spec);
assert_eq!(validators.len(), 0);
assert_eq!(result.unwrap(), 0);
assert!(deposit_equals_record(&deposit, &state.validator_registry[0]));
assert_eq!(state.validator_balances[0], DEPOSIT_GWEI);
assert_eq!(state.validator_registry.len(), 1);
assert_eq!(state.validator_balances.len(), 1);
} }
#[test] #[test]
fn test_validator_inductor_shard_proof_of_possession_failure() { fn test_process_deposit_invalid_proof_of_possession() {
let validators = vec![]; let mut state = BeaconState::default();
let mut deposit = get_deposit();
let spec = ChainSpec::foundation();
deposit.deposit_data.value = DEPOSIT_GWEI;
deposit.deposit_data.deposit_input.proof_of_possession = create_proof_of_possession(&Keypair::random());
let mut r = get_registration(); let result = process_deposit(&mut state, &deposit, &spec);
let kp = Keypair::random();
r.proof_of_possession = create_proof_of_possession(&kp);
let mut inductor = ValidatorInductor::new(0, 1024, validators); assert_eq!(result, Err(ValidatorInductionError::InvaidProofOfPossession));
let result = inductor.induct(&r, ValidatorStatus::PendingActivation); assert_eq!(state.validator_registry.len(), 0);
let validators = inductor.to_vec(); assert_eq!(state.validator_balances.len(), 0);
assert_eq!(
result,
Err(ValidatorInductionError::InvaidProofOfPossession)
);
assert_eq!(validators.len(), 0);
} }
} }

View File

@ -1,7 +1,8 @@
extern crate bls; extern crate bls;
extern crate hashing; extern crate hashing;
extern crate types; extern crate types;
extern crate spec;
mod inductor; mod inductor;
pub use crate::inductor::{ValidatorInductionError, ValidatorInductor}; pub use crate::inductor::{ValidatorInductionError, process_deposit};