diff --git a/Cargo.toml b/Cargo.toml index 24a379b3cb..6c9b8886dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ members = [ "beacon_chain/utils/ssz_helpers", "beacon_chain/utils/vec_shuffle", "beacon_chain/validation", + "beacon_chain/validator_change", "beacon_chain/validator_induction", "beacon_chain/validator_shuffling", "lighthouse/db", diff --git a/beacon_chain/validator_change/Cargo.toml b/beacon_chain/validator_change/Cargo.toml new file mode 100644 index 0000000000..88e78c9f85 --- /dev/null +++ b/beacon_chain/validator_change/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "validator_change" +version = "0.1.0" +authors = ["Paul Hauner "] + +[dependencies] +active-validators = { path = "../utils/active-validators" } +bytes = "0.4.10" +hashing = { path = "../utils/hashing" } +types = { path = "../types" } diff --git a/beacon_chain/validator_change/src/lib.rs b/beacon_chain/validator_change/src/lib.rs new file mode 100644 index 0000000000..d913a4be0b --- /dev/null +++ b/beacon_chain/validator_change/src/lib.rs @@ -0,0 +1,148 @@ +extern crate active_validators; +extern crate bytes; +extern crate hashing; +extern crate types; + +use active_validators::validator_is_active; +use bytes::{ + BytesMut, + BufMut, +}; +use hashing::canonical_hash; +use std::cmp::max; +use types::{ + Hash256, + ValidatorRecord, + ValidatorStatus, +}; + +pub enum UpdateValidatorSetError { + ArithmeticOverflow, +} + +const VALIDATOR_FLAG_ENTRY: u8 = 0; +const VALIDATOR_FLAG_EXIT: u8 = 1; + +pub fn update_validator_set( + validators: &mut Vec, + hash_chain: Hash256, + present_slot: u64, + deposit_size_gwei: u64, + max_validator_churn_quotient: u64) + -> Result<(), UpdateValidatorSetError> +{ + /* + * Total balance of all active validators. + * + * Return an error if an overflow occurs. + */ + let total_balance = { + let mut bal: u64 = 0; + for v in validators.iter() { + if validator_is_active(&v) { + bal = bal.checked_add(v.balance) + .ok_or(UpdateValidatorSetError::ArithmeticOverflow)?; + } + } + bal + }; + + /* + * Note: this is not the maximum allowable change, it can actually be higher. + */ + let max_allowable_change = { + let double_deposit_size = deposit_size_gwei.checked_mul(2) + .ok_or(UpdateValidatorSetError::ArithmeticOverflow)?; + max(double_deposit_size, total_balance / max_validator_churn_quotient) + }; + + let mut hasher = ValidatorChangeHashChain { + bytes: hash_chain.to_vec(), + }; + let mut total_changed: u64 = 0; + for (i, v) in validators.iter_mut().enumerate() { + match v.status { + /* + * Validator is pending activiation. + */ + x if x == ValidatorStatus::PendingActivation as u8 => { + let new_total_changed = total_changed.checked_add(deposit_size_gwei) + .ok_or(UpdateValidatorSetError::ArithmeticOverflow)?; + /* + * If entering this validator would not exceed the max balance delta, + * activate the validator. + */ + if new_total_changed <= max_allowable_change { + v.status = ValidatorStatus::Active as u8; + hasher.extend(i, &v.pubkey.as_bytes(), VALIDATOR_FLAG_ENTRY); + total_changed = new_total_changed; + } else { + // Entering the validator would exceed the balance delta. + break; + } + } + /* + * Validator is pending exit. + */ + x if x == ValidatorStatus::PendingExit as u8 => { + let new_total_changed = total_changed.checked_add(v.balance) + .ok_or(UpdateValidatorSetError::ArithmeticOverflow)?; + /* + * If exiting this validator would not exceed the max balance delta, + * exit the validator + */ + if new_total_changed <= max_allowable_change { + v.status = ValidatorStatus::PendingWithdraw as u8; + v.exit_slot = present_slot; + hasher.extend(i, &v.pubkey.as_bytes(), VALIDATOR_FLAG_EXIT); + total_changed = new_total_changed; + } else { + // Exiting the validator would exceed the balance delta. + break; + } + } + _ => () + }; + if total_changed >= max_allowable_change { + break; + } + } + Ok(()) +} + +pub struct ValidatorChangeHashChain { + bytes: Vec, +} + +impl ValidatorChangeHashChain { + pub fn extend(&mut self, index: usize, pubkey: &Vec, flag: u8) + { + let mut message = self.bytes.clone(); + message.append(&mut serialize_validator_change_record(index, pubkey, flag)); + self.bytes = canonical_hash(&message); + } +} + +fn serialize_validator_change_record(index: usize, pubkey: &Vec, flag: u8) + -> Vec +{ + let mut buf = BytesMut::with_capacity(68); + buf.put_u8(flag); + let index_bytes = { + let mut buf = BytesMut::with_capacity(8); + buf.put_u64_be(index as u64); + buf.take()[8 - 3..8].to_vec() + }; + buf.put(index_bytes); + buf.put(pubkey); + buf.take().to_vec() +} + + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +}