Operation pool: refactor verify_deposit/exit

This commit is contained in:
Michael Sproul 2019-03-20 15:57:41 +11:00
parent 03c01c8a8d
commit b2fe14e12c
No known key found for this signature in database
GPG Key ID: 77B1309D2E54E914
5 changed files with 118 additions and 61 deletions

View File

@ -3,8 +3,8 @@ use itertools::Itertools;
use ssz::ssz_encode; use ssz::ssz_encode;
use state_processing::per_block_processing::errors::ProposerSlashingValidationError; use state_processing::per_block_processing::errors::ProposerSlashingValidationError;
use state_processing::per_block_processing::{ use state_processing::per_block_processing::{
validate_attestation, verify_deposit_merkle_proof, verify_exit, verify_proposer_slashing, validate_attestation, verify_deposit, verify_exit, verify_exit_time_independent_only,
verify_transfer, verify_transfer_partial, verify_proposer_slashing, verify_transfer, verify_transfer_partial,
}; };
use std::collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet}; use std::collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet};
use types::chain_spec::Domain; use types::chain_spec::Domain;
@ -179,19 +179,27 @@ impl OperationPool {
/// Add a deposit to the pool. /// Add a deposit to the pool.
/// ///
/// No two distinct deposits should be added with the same index. /// No two distinct deposits should be added with the same index.
pub fn insert_deposit(&mut self, deposit: Deposit) -> DepositInsertStatus { pub fn insert_deposit(
&mut self,
deposit: Deposit,
state: &BeaconState,
spec: &ChainSpec,
) -> Result<DepositInsertStatus, ()> {
use DepositInsertStatus::*; use DepositInsertStatus::*;
match self.deposits.entry(deposit.index) { match self.deposits.entry(deposit.index) {
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
// FIXME: error prop
verify_deposit(state, &deposit, VERIFY_DEPOSIT_PROOFS, spec).map_err(|_| ())?;
entry.insert(deposit); entry.insert(deposit);
Fresh Ok(Fresh)
} }
Entry::Occupied(mut entry) => { Entry::Occupied(mut entry) => {
if entry.get() == &deposit { if entry.get() == &deposit {
Duplicate Ok(Duplicate)
} else { } else {
Replaced(Box::new(entry.insert(deposit))) verify_deposit(state, &deposit, VERIFY_DEPOSIT_PROOFS, spec).map_err(|_| ())?;
Ok(Replaced(Box::new(entry.insert(deposit))))
} }
} }
} }
@ -204,14 +212,7 @@ impl OperationPool {
let start_idx = state.deposit_index; let start_idx = state.deposit_index;
(start_idx..start_idx + spec.max_deposits) (start_idx..start_idx + spec.max_deposits)
.map(|idx| self.deposits.get(&idx)) .map(|idx| self.deposits.get(&idx))
.take_while(|deposit| { .take_while(Option::is_some)
// NOTE: we don't use verify_deposit, because it requires the
// deposit's index to match the state's, and we would like to return
// a batch with increasing indices
deposit.map_or(false, |deposit| {
!VERIFY_DEPOSIT_PROOFS || verify_deposit_merkle_proof(state, deposit, spec)
})
})
.flatten() .flatten()
.cloned() .cloned()
.collect() .collect()
@ -287,7 +288,7 @@ impl OperationPool {
state: &BeaconState, state: &BeaconState,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<(), ()> { ) -> Result<(), ()> {
verify_exit(state, &exit, spec, false).map_err(|_| ())?; verify_exit_time_independent_only(state, &exit, spec).map_err(|_| ())?;
self.voluntary_exits.insert(exit.validator_index, exit); self.voluntary_exits.insert(exit.validator_index, exit);
Ok(()) Ok(())
} }
@ -297,7 +298,7 @@ impl OperationPool {
pub fn get_voluntary_exits(&self, state: &BeaconState, spec: &ChainSpec) -> Vec<VoluntaryExit> { pub fn get_voluntary_exits(&self, state: &BeaconState, spec: &ChainSpec) -> Vec<VoluntaryExit> {
filter_limit_operations( filter_limit_operations(
self.voluntary_exits.values(), self.voluntary_exits.values(),
|exit| verify_exit(state, exit, spec, true).is_ok(), |exit| verify_exit(state, exit, spec).is_ok(),
spec.max_voluntary_exits, spec.max_voluntary_exits,
) )
} }
@ -398,53 +399,51 @@ mod tests {
use super::DepositInsertStatus::*; use super::DepositInsertStatus::*;
use super::*; use super::*;
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng}; use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use types::*;
#[test] #[test]
fn insert_deposit() { fn insert_deposit() {
let mut rng = XorShiftRng::from_seed([42; 16]); let rng = &mut XorShiftRng::from_seed([42; 16]);
let (ref spec, ref state) = test_state(rng);
let mut op_pool = OperationPool::new(); let mut op_pool = OperationPool::new();
let deposit1 = Deposit::random_for_test(&mut rng); let deposit1 = make_deposit(rng, state, spec);
let mut deposit2 = Deposit::random_for_test(&mut rng); let mut deposit2 = make_deposit(rng, state, spec);
deposit2.index = deposit1.index; deposit2.index = deposit1.index;
assert_eq!(op_pool.insert_deposit(deposit1.clone()), Fresh);
assert_eq!(op_pool.insert_deposit(deposit1.clone()), Duplicate);
assert_eq!( assert_eq!(
op_pool.insert_deposit(deposit2), op_pool.insert_deposit(deposit1.clone(), state, spec),
Replaced(Box::new(deposit1)) Ok(Fresh)
);
assert_eq!(
op_pool.insert_deposit(deposit1.clone(), state, spec),
Ok(Duplicate)
);
assert_eq!(
op_pool.insert_deposit(deposit2, state, spec),
Ok(Replaced(Box::new(deposit1)))
); );
}
// Create `count` dummy deposits with sequential deposit IDs beginning from `start`.
fn dummy_deposits(rng: &mut XorShiftRng, start: u64, count: u64) -> Vec<Deposit> {
let proto_deposit = Deposit::random_for_test(rng);
(start..start + count)
.map(|index| {
let mut deposit = proto_deposit.clone();
deposit.index = index;
deposit
})
.collect()
} }
#[test] #[test]
fn get_deposits_max() { fn get_deposits_max() {
let mut rng = XorShiftRng::from_seed([42; 16]); let rng = &mut XorShiftRng::from_seed([42; 16]);
let (spec, mut state) = test_state(rng);
let mut op_pool = OperationPool::new(); let mut op_pool = OperationPool::new();
let spec = ChainSpec::foundation();
let start = 10000; let start = 10000;
let max_deposits = spec.max_deposits; let max_deposits = spec.max_deposits;
let extra = 5; let extra = 5;
let offset = 1; let offset = 1;
assert!(offset <= extra); assert!(offset <= extra);
let deposits = dummy_deposits(&mut rng, start, max_deposits + extra); let deposits = dummy_deposits(rng, &state, &spec, start, max_deposits + extra);
for deposit in &deposits { for deposit in &deposits {
assert_eq!(op_pool.insert_deposit(deposit.clone()), Fresh); assert_eq!(
op_pool.insert_deposit(deposit.clone(), &state, &spec),
Ok(Fresh)
);
} }
let mut state = BeaconState::random_for_test(&mut rng);
state.deposit_index = start + offset; state.deposit_index = start + offset;
let deposits_for_block = op_pool.get_deposits(&state, &spec); let deposits_for_block = op_pool.get_deposits(&state, &spec);
@ -458,18 +457,20 @@ mod tests {
#[test] #[test]
fn prune_deposits() { fn prune_deposits() {
let rng = &mut XorShiftRng::from_seed([42; 16]); let rng = &mut XorShiftRng::from_seed([42; 16]);
let (spec, state) = test_state(rng);
let mut op_pool = OperationPool::new(); let mut op_pool = OperationPool::new();
let start1 = 100; let start1 = 100;
let count = 100; // test is super slow in debug mode if this parameter is too high
let count = 5;
let gap = 25; let gap = 25;
let start2 = start1 + count + gap; let start2 = start1 + count + gap;
let deposits1 = dummy_deposits(rng, start1, count); let deposits1 = dummy_deposits(rng, &state, &spec, start1, count);
let deposits2 = dummy_deposits(rng, start2, count); let deposits2 = dummy_deposits(rng, &state, &spec, start2, count);
for d in deposits1.into_iter().chain(deposits2) { for d in deposits1.into_iter().chain(deposits2) {
op_pool.insert_deposit(d); assert!(op_pool.insert_deposit(d, &state, &spec).is_ok());
} }
assert_eq!(op_pool.num_deposits(), 2 * count as usize); assert_eq!(op_pool.num_deposits(), 2 * count as usize);
@ -504,5 +505,50 @@ mod tests {
assert_eq!(op_pool.num_deposits(), 0); assert_eq!(op_pool.num_deposits(), 0);
} }
// Create a random deposit (with a valid proof of posession)
fn make_deposit(rng: &mut XorShiftRng, state: &BeaconState, spec: &ChainSpec) -> Deposit {
let keypair = Keypair::random();
let mut deposit = Deposit::random_for_test(rng);
let mut deposit_input = DepositInput {
pubkey: keypair.pk.clone(),
withdrawal_credentials: Hash256::zero(),
proof_of_possession: Signature::empty_signature(),
};
deposit_input.proof_of_possession = deposit_input.create_proof_of_possession(
&keypair.sk,
state.slot.epoch(spec.slots_per_epoch),
&state.fork,
spec,
);
deposit.deposit_data.deposit_input = deposit_input;
deposit
}
// Create `count` dummy deposits with sequential deposit IDs beginning from `start`.
fn dummy_deposits(
rng: &mut XorShiftRng,
state: &BeaconState,
spec: &ChainSpec,
start: u64,
count: u64,
) -> Vec<Deposit> {
let proto_deposit = make_deposit(rng, state, spec);
(start..start + count)
.map(|index| {
let mut deposit = proto_deposit.clone();
deposit.index = index;
deposit
})
.collect()
}
fn test_state(rng: &mut XorShiftRng) -> (ChainSpec, BeaconState) {
let spec = ChainSpec::foundation();
let mut state = BeaconState::random_for_test(rng);
state.fork = Fork::genesis(&spec);
(spec, state)
}
// TODO: more tests // TODO: more tests
} }

View File

@ -11,9 +11,8 @@ pub use self::verify_proposer_slashing::verify_proposer_slashing;
pub use validate_attestation::{validate_attestation, validate_attestation_without_signature}; pub use validate_attestation::{validate_attestation, validate_attestation_without_signature};
pub use verify_deposit::{ pub use verify_deposit::{
get_existing_validator_index, verify_deposit, verify_deposit_index, get_existing_validator_index, verify_deposit, verify_deposit_index,
verify_deposit_merkle_proof,
}; };
pub use verify_exit::verify_exit; pub use verify_exit::{verify_exit, verify_exit_time_independent_only};
pub use verify_slashable_attestation::verify_slashable_attestation; pub use verify_slashable_attestation::verify_slashable_attestation;
pub use verify_transfer::{execute_transfer, verify_transfer, verify_transfer_partial}; pub use verify_transfer::{execute_transfer, verify_transfer, verify_transfer_partial};
@ -429,7 +428,7 @@ pub fn process_exits(
.par_iter() .par_iter()
.enumerate() .enumerate()
.try_for_each(|(i, exit)| { .try_for_each(|(i, exit)| {
verify_exit(&state, exit, spec, true).map_err(|e| e.into_with_index(i)) verify_exit(&state, exit, spec).map_err(|e| e.into_with_index(i))
})?; })?;
// Update the state in series. // Update the state in series.

View File

@ -89,11 +89,7 @@ pub fn get_existing_validator_index(
/// Verify that a deposit is included in the state's eth1 deposit root. /// Verify that a deposit is included in the state's eth1 deposit root.
/// ///
/// Spec v0.5.0 /// Spec v0.5.0
pub fn verify_deposit_merkle_proof( fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &ChainSpec) -> bool {
state: &BeaconState,
deposit: &Deposit,
spec: &ChainSpec,
) -> bool {
let leaf = hash(&get_serialized_deposit_data(deposit)); let leaf = hash(&get_serialized_deposit_data(deposit));
verify_merkle_proof( verify_merkle_proof(
Hash256::from_slice(&leaf), Hash256::from_slice(&leaf),

View File

@ -7,17 +7,30 @@ use types::*;
/// ///
/// Returns `Ok(())` if the `Exit` is valid, otherwise indicates the reason for invalidity. /// Returns `Ok(())` if the `Exit` is valid, otherwise indicates the reason for invalidity.
/// ///
/// The `check_future_epoch` argument determines whether the exit's epoch should be checked
/// against the state's current epoch to ensure it doesn't occur in the future.
/// It should ordinarily be set to true, except for operations stored for
/// some time (such as in the OperationPool).
///
/// Spec v0.5.0 /// Spec v0.5.0
pub fn verify_exit( pub fn verify_exit(
state: &BeaconState, state: &BeaconState,
exit: &VoluntaryExit, exit: &VoluntaryExit,
spec: &ChainSpec, spec: &ChainSpec,
check_future_epoch: bool, ) -> Result<(), Error> {
verify_exit_parametric(state, exit, spec, false)
}
/// Like `verify_exit` but doesn't run checks which may become true in future states.
pub fn verify_exit_time_independent_only(
state: &BeaconState,
exit: &VoluntaryExit,
spec: &ChainSpec,
) -> Result<(), Error> {
verify_exit_parametric(state, exit, spec, true)
}
/// Parametric version of `verify_exit` that skips some checks if `time_independent_only` is true.
fn verify_exit_parametric(
state: &BeaconState,
exit: &VoluntaryExit,
spec: &ChainSpec,
time_independent_only: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
let validator = state let validator = state
.validator_registry .validator_registry
@ -38,7 +51,7 @@ pub fn verify_exit(
// Exits must specify an epoch when they become valid; they are not valid before then. // Exits must specify an epoch when they become valid; they are not valid before then.
verify!( verify!(
!check_future_epoch || state.current_epoch(spec) >= exit.epoch, time_independent_only || state.current_epoch(spec) >= exit.epoch,
Invalid::FutureEpoch { Invalid::FutureEpoch {
state: state.current_epoch(spec), state: state.current_epoch(spec),
exit: exit.epoch exit: exit.epoch

View File

@ -31,7 +31,9 @@ pub struct Attestation {
impl Attestation { impl Attestation {
/// Are the aggregation bitfields of these attestations disjoint? /// Are the aggregation bitfields of these attestations disjoint?
pub fn signers_disjoint_from(&self, other: &Attestation) -> bool { pub fn signers_disjoint_from(&self, other: &Attestation) -> bool {
self.aggregation_bitfield.intersection(&other.aggregation_bitfield).is_zero() self.aggregation_bitfield
.intersection(&other.aggregation_bitfield)
.is_zero()
} }
/// Aggregate another Attestation into this one. /// Aggregate another Attestation into this one.
@ -41,7 +43,8 @@ impl Attestation {
debug_assert_eq!(self.data, other.data); debug_assert_eq!(self.data, other.data);
debug_assert!(self.signers_disjoint_from(other)); debug_assert!(self.signers_disjoint_from(other));
self.aggregation_bitfield.union_inplace(&other.aggregation_bitfield); self.aggregation_bitfield
.union_inplace(&other.aggregation_bitfield);
self.custody_bitfield.union_inplace(&other.custody_bitfield); self.custody_bitfield.union_inplace(&other.custody_bitfield);
// FIXME: signature aggregation once our BLS library wraps it // FIXME: signature aggregation once our BLS library wraps it
} }