Add validator induction functionality
This commit is contained in:
parent
d5675062c1
commit
b4566a776a
@ -42,5 +42,6 @@ members = [
|
||||
"beacon_chain/utils/ssz",
|
||||
"beacon_chain/utils/ssz_helpers",
|
||||
"beacon_chain/validation",
|
||||
"beacon_chain/validator_induction",
|
||||
"lighthouse/db",
|
||||
]
|
||||
|
9
beacon_chain/validator_induction/Cargo.toml
Normal file
9
beacon_chain/validator_induction/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "validator_induction"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
|
||||
[dependencies]
|
||||
bls = { path = "../utils/bls" }
|
||||
hashing = { path = "../utils/hashing" }
|
||||
types = { path = "../types" }
|
256
beacon_chain/validator_induction/src/inductor.rs
Normal file
256
beacon_chain/validator_induction/src/inductor.rs
Normal file
@ -0,0 +1,256 @@
|
||||
use types::{
|
||||
ValidatorRecord,
|
||||
ValidatorStatus,
|
||||
};
|
||||
|
||||
use super::proof_of_possession::verify_proof_of_possession;
|
||||
use super::registration::ValidatorRegistration;
|
||||
|
||||
/// 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<'a> {
|
||||
pub current_slot: u64,
|
||||
pub shard_count: u16,
|
||||
validators: &'a mut Vec<ValidatorRecord>,
|
||||
empty_validator_start: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ValidatorInductionError {
|
||||
InvalidShard,
|
||||
InvaidProofOfPossession,
|
||||
}
|
||||
|
||||
impl<'a> ValidatorInductor<'a> {
|
||||
/// Attempt to induct a validator into the CrystallizedState.
|
||||
///
|
||||
/// 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)
|
||||
-> Result<usize, ValidatorInductionError>
|
||||
{
|
||||
let v = self.process_registration(rego)?;
|
||||
Ok(self.add_validator(v))
|
||||
}
|
||||
|
||||
/// Verify a `ValidatorRegistration` and return a `ValidatorRecord` if valid.
|
||||
fn process_registration(&self, r: &ValidatorRegistration)
|
||||
-> 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)
|
||||
}
|
||||
|
||||
Ok(ValidatorRecord {
|
||||
pubkey: r.pubkey.clone(),
|
||||
withdrawal_shard: r.withdrawal_shard,
|
||||
withdrawal_address: r.withdrawal_address,
|
||||
randao_commitment: r.randao_commitment,
|
||||
randao_last_change: self.current_slot,
|
||||
balance: DEPOSIT_GWEI,
|
||||
status: ValidatorStatus::PendingActivation as u8,
|
||||
exit_slot: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the index of the first `ValidatorRecord` in the `CrystallizedState` where
|
||||
/// `validator.status == Withdrawn`. If no such record exists, `None` is returned.
|
||||
fn first_withdrawn_validator(&mut self)
|
||||
-> Option<usize>
|
||||
{
|
||||
for i in self.empty_validator_start..self.validators.len() {
|
||||
if self.validators[i].status == ValidatorStatus::Withdrawn as u8 {
|
||||
self.empty_validator_start = i + 1;
|
||||
return Some(i)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use bls::{
|
||||
Keypair,
|
||||
Signature,
|
||||
};
|
||||
use types::{
|
||||
Address,
|
||||
Hash256,
|
||||
};
|
||||
use hashing::proof_of_possession_hash;
|
||||
|
||||
/// Generate a proof of possession for some keypair.
|
||||
fn get_proof_of_possession(kp: &Keypair) -> Signature {
|
||||
let pop_message = proof_of_possession_hash(&kp.pk.as_bytes());
|
||||
Signature::new_hashed(&pop_message, &kp.sk)
|
||||
}
|
||||
|
||||
/// Generate a basic working ValidatorRegistration for use in tests.
|
||||
fn get_registration() -> ValidatorRegistration {
|
||||
let kp = Keypair::random();
|
||||
ValidatorRegistration {
|
||||
pubkey: kp.pk.clone(),
|
||||
withdrawal_shard: 0,
|
||||
withdrawal_address: Address::zero(),
|
||||
randao_commitment: Hash256::zero(),
|
||||
proof_of_possession: get_proof_of_possession(&kp),
|
||||
}
|
||||
}
|
||||
|
||||
/// Induct a validator using the ValidatorInductor, return the result.
|
||||
fn do_induction(validator_rego: &ValidatorRegistration,
|
||||
validators: &mut Vec<ValidatorRecord>,
|
||||
current_slot: u64,
|
||||
shard_count: u16)
|
||||
-> Result<usize, ValidatorInductionError>
|
||||
{
|
||||
let mut inductor = ValidatorInductor {
|
||||
current_slot,
|
||||
shard_count,
|
||||
empty_validator_start: 0,
|
||||
validators,
|
||||
};
|
||||
inductor.induct(&validator_rego)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validator_inductor_valid_empty_validators() {
|
||||
let mut validators = vec![];
|
||||
|
||||
let r = get_registration();
|
||||
|
||||
let result = do_induction(&r, &mut validators, 0, 1024);
|
||||
|
||||
assert_eq!(result.unwrap(), 0);
|
||||
assert_eq!(r, validators[0]);
|
||||
assert_eq!(validators.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validator_inductor_valid_all_active_validators() {
|
||||
let mut validators = vec![];
|
||||
for _ in 0..5 {
|
||||
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
|
||||
v.status = ValidatorStatus::Active as u8;
|
||||
validators.push(v);
|
||||
}
|
||||
|
||||
let r = get_registration();
|
||||
|
||||
let result = do_induction(&r, &mut validators, 0, 1024);
|
||||
|
||||
assert_eq!(result.unwrap(), 5);
|
||||
assert_eq!(r, validators[5]);
|
||||
assert_eq!(validators.len(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validator_inductor_valid_all_second_validator_withdrawn() {
|
||||
let mut validators = vec![];
|
||||
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
|
||||
v.status = ValidatorStatus::Active as u8;
|
||||
validators.push(v);
|
||||
for _ in 0..4 {
|
||||
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
|
||||
v.status = ValidatorStatus::Withdrawn as u8;
|
||||
validators.push(v);
|
||||
}
|
||||
|
||||
let r = get_registration();
|
||||
|
||||
let result = do_induction(&r, &mut validators, 0, 1024);
|
||||
|
||||
assert_eq!(result.unwrap(), 1);
|
||||
assert_eq!(r, validators[1]);
|
||||
assert_eq!(validators.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validator_inductor_valid_all_withdrawn_validators() {
|
||||
let mut validators = vec![];
|
||||
for _ in 0..5 {
|
||||
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
|
||||
v.status = ValidatorStatus::Withdrawn as u8;
|
||||
validators.push(v);
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure the first validator gets the 0'th slot
|
||||
*/
|
||||
let r = get_registration();
|
||||
let result = do_induction(&r, &mut validators, 0, 1024);
|
||||
assert_eq!(result.unwrap(), 0);
|
||||
assert_eq!(r, validators[0]);
|
||||
assert_eq!(validators.len(), 5);
|
||||
|
||||
/*
|
||||
* Ensure the second validator gets the 1'st slot
|
||||
*/
|
||||
let r_two = get_registration();
|
||||
let result = do_induction(&r_two, &mut validators, 0, 1024);
|
||||
assert_eq!(result.unwrap(), 1);
|
||||
assert_eq!(r_two, validators[1]);
|
||||
assert_eq!(validators.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validator_inductor_shard_too_high() {
|
||||
let mut validators = vec![];
|
||||
|
||||
let mut r = get_registration();
|
||||
r.withdrawal_shard = 1025;
|
||||
|
||||
let result = do_induction(&r, &mut validators, 0, 1024);
|
||||
|
||||
assert_eq!(result, Err(ValidatorInductionError::InvalidShard));
|
||||
assert_eq!(validators.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validator_inductor_shard_proof_of_possession_failure() {
|
||||
let mut validators = vec![];
|
||||
|
||||
let mut r = get_registration();
|
||||
let kp = Keypair::random();
|
||||
r.proof_of_possession = get_proof_of_possession(&kp);
|
||||
|
||||
let result = do_induction(&r, &mut validators, 0, 1024);
|
||||
|
||||
assert_eq!(result, Err(ValidatorInductionError::InvaidProofOfPossession));
|
||||
assert_eq!(validators.len(), 0);
|
||||
}
|
||||
}
|
12
beacon_chain/validator_induction/src/lib.rs
Normal file
12
beacon_chain/validator_induction/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
extern crate bls;
|
||||
extern crate hashing;
|
||||
extern crate types;
|
||||
|
||||
mod inductor;
|
||||
mod proof_of_possession;
|
||||
mod registration;
|
||||
|
||||
pub use inductor::{
|
||||
ValidatorInductor,
|
||||
ValidatorInductionError,
|
||||
};
|
14
beacon_chain/validator_induction/src/proof_of_possession.rs
Normal file
14
beacon_chain/validator_induction/src/proof_of_possession.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use bls::{
|
||||
Signature,
|
||||
PublicKey,
|
||||
};
|
||||
use hashing::proof_of_possession_hash;
|
||||
|
||||
/// For some signature and public key, ensure that the signature message was the public key and it
|
||||
/// was signed by the secret key that corresponds to that public key.
|
||||
pub fn verify_proof_of_possession(sig: &Signature, pubkey: &PublicKey)
|
||||
-> bool
|
||||
{
|
||||
let hash = proof_of_possession_hash(&pubkey.as_bytes());
|
||||
sig.verify_hashed(&hash, &pubkey)
|
||||
}
|
105
beacon_chain/validator_induction/src/registration.rs
Normal file
105
beacon_chain/validator_induction/src/registration.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use bls::{
|
||||
PublicKey,
|
||||
Signature,
|
||||
};
|
||||
use types::{
|
||||
Address,
|
||||
Hash256,
|
||||
ValidatorRecord,
|
||||
};
|
||||
|
||||
use super::proof_of_possession::verify_proof_of_possession;
|
||||
|
||||
|
||||
/// The information gathered from the PoW chain validator registration function.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ValidatorRegistration {
|
||||
pub pubkey: PublicKey,
|
||||
pub withdrawal_shard: u16,
|
||||
pub withdrawal_address: Address,
|
||||
pub randao_commitment: Hash256,
|
||||
pub proof_of_possession: Signature,
|
||||
}
|
||||
|
||||
impl PartialEq<ValidatorRecord> for ValidatorRegistration {
|
||||
fn eq(&self, v: &ValidatorRecord) -> bool {
|
||||
(self.pubkey == v.pubkey) &
|
||||
(self.withdrawal_shard == v.withdrawal_shard) &
|
||||
(self.withdrawal_address == v.withdrawal_address) &
|
||||
(self.randao_commitment == v.randao_commitment) &
|
||||
(verify_proof_of_possession(&self.proof_of_possession, &v.pubkey))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use bls::{
|
||||
Keypair,
|
||||
Signature,
|
||||
};
|
||||
use types::{
|
||||
Address,
|
||||
Hash256,
|
||||
ValidatorRecord,
|
||||
};
|
||||
use hashing::proof_of_possession_hash;
|
||||
|
||||
fn get_proof_of_possession(kp: &Keypair) -> Signature {
|
||||
let pop_message = proof_of_possession_hash(&kp.pk.as_bytes());
|
||||
Signature::new_hashed(&pop_message, &kp.sk)
|
||||
}
|
||||
|
||||
fn get_equal_validator_registrations_and_records()
|
||||
-> (ValidatorRegistration, ValidatorRecord)
|
||||
{
|
||||
let kp = Keypair::random();
|
||||
let rego = ValidatorRegistration {
|
||||
pubkey: kp.pk.clone(),
|
||||
withdrawal_shard: 0,
|
||||
withdrawal_address: Address::zero(),
|
||||
randao_commitment: Hash256::zero(),
|
||||
proof_of_possession: get_proof_of_possession(&kp),
|
||||
};
|
||||
let record = ValidatorRecord {
|
||||
pubkey: rego.pubkey.clone(),
|
||||
withdrawal_shard: rego.withdrawal_shard,
|
||||
withdrawal_address: rego.withdrawal_address.clone(),
|
||||
randao_commitment: rego.randao_commitment.clone(),
|
||||
randao_last_change: 0,
|
||||
balance: 0,
|
||||
status: 0,
|
||||
exit_slot: 0,
|
||||
};
|
||||
(rego, record)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validator_registration_and_record_partial_eq() {
|
||||
let (rego, record) = get_equal_validator_registrations_and_records();
|
||||
assert!(rego == record);
|
||||
|
||||
let (mut rego, record) = get_equal_validator_registrations_and_records();
|
||||
let kp = Keypair::random();
|
||||
rego.pubkey = kp.pk.clone();
|
||||
assert!(rego != record);
|
||||
|
||||
let (mut rego, record) = get_equal_validator_registrations_and_records();
|
||||
rego.withdrawal_shard = record.withdrawal_shard + 1;
|
||||
assert!(rego != record);
|
||||
|
||||
let (mut rego, record) = get_equal_validator_registrations_and_records();
|
||||
rego.withdrawal_address = Address::from(42);
|
||||
assert!(rego != record);
|
||||
|
||||
let (mut rego, record) = get_equal_validator_registrations_and_records();
|
||||
rego.randao_commitment = Hash256::from(42);
|
||||
assert!(rego != record);
|
||||
|
||||
let (mut rego, record) = get_equal_validator_registrations_and_records();
|
||||
let kp = Keypair::random();
|
||||
rego.proof_of_possession = get_proof_of_possession(&kp);
|
||||
assert!(rego != record);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user