op-pool: implement attester slashings

This commit is contained in:
Michael Sproul 2019-03-25 16:58:20 +11:00
parent bde7ab79c8
commit 518359e898
No known key found for this signature in database
GPG Key ID: 77B1309D2E54E914
3 changed files with 122 additions and 22 deletions

View File

@ -2,12 +2,13 @@ use int_to_bytes::int_to_bytes8;
use itertools::Itertools; use itertools::Itertools;
use ssz::ssz_encode; use ssz::ssz_encode;
use state_processing::per_block_processing::errors::{ use state_processing::per_block_processing::errors::{
AttestationValidationError, DepositValidationError, ExitValidationError, AttestationValidationError, AttesterSlashingValidationError, DepositValidationError,
ProposerSlashingValidationError, TransferValidationError, ExitValidationError, ProposerSlashingValidationError, TransferValidationError,
}; };
use state_processing::per_block_processing::{ use state_processing::per_block_processing::{
validate_attestation, validate_attestation_time_independent_only, verify_deposit, verify_exit, gather_attester_slashing_indices_modular, validate_attestation,
verify_exit_time_independent_only, verify_proposer_slashing, verify_transfer, validate_attestation_time_independent_only, verify_attester_slashing, verify_deposit,
verify_exit, verify_exit_time_independent_only, verify_proposer_slashing, verify_transfer,
verify_transfer_time_independent_only, verify_transfer_time_independent_only,
}; };
use std::collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet}; use std::collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet};
@ -32,8 +33,8 @@ pub struct OperationPool {
// and the spec doesn't seem to accomodate for re-orgs on a time-frame // and the spec doesn't seem to accomodate for re-orgs on a time-frame
// longer than an epoch // longer than an epoch
deposits: BTreeMap<u64, Deposit>, deposits: BTreeMap<u64, Deposit>,
/// Map from attester index to slashing. /// Map from two attestation IDs to a slashing for those IDs.
attester_slashings: HashMap<u64, AttesterSlashing>, attester_slashings: HashMap<(AttestationId, AttestationId), AttesterSlashing>,
/// Map from proposer index to slashing. /// Map from proposer index to slashing.
proposer_slashings: HashMap<u64, ProposerSlashing>, proposer_slashings: HashMap<u64, ProposerSlashing>,
/// Map from exiting validator to their exit data. /// Map from exiting validator to their exit data.
@ -250,18 +251,44 @@ impl OperationPool {
Ok(()) Ok(())
} }
/// Only check whether the implicated validator has already been slashed, because /// Compute the tuple ID that is used to identify an attester slashing.
/// all slashings in the pool were validated upon insertion. ///
// TODO: we need a mechanism to avoid including a proposer slashing and an attester /// Depends on the fork field of the state, but not on the state's epoch.
// slashing for the same validator in the same block fn attester_slashing_id(
pub fn get_proposer_slashings( slashing: &AttesterSlashing,
state: &BeaconState,
spec: &ChainSpec,
) -> (AttestationId, AttestationId) {
(
AttestationId::from_data(&slashing.slashable_attestation_1.data, state, spec),
AttestationId::from_data(&slashing.slashable_attestation_2.data, state, spec),
)
}
/// Insert an attester slashing into the pool.
pub fn insert_attester_slashing(
&mut self,
slashing: AttesterSlashing,
state: &BeaconState,
spec: &ChainSpec,
) -> Result<(), AttesterSlashingValidationError> {
verify_attester_slashing(state, &slashing, true, spec)?;
let id = Self::attester_slashing_id(&slashing, state, spec);
self.attester_slashings.insert(id, slashing);
Ok(())
}
/// Get proposer and attester slashings for inclusion in a block.
///
/// This function computes both types of slashings together, because
/// attester slashings may be invalidated by proposer slashings included
/// earlier in the block.
pub fn get_slashings(
&self, &self,
state: &BeaconState, state: &BeaconState,
spec: &ChainSpec, spec: &ChainSpec,
) -> Vec<ProposerSlashing> { ) -> (Vec<ProposerSlashing>, Vec<AttesterSlashing>) {
// We sort by validator index, which is safe, because a validator can only supply let proposer_slashings = filter_limit_operations(
// so many valid slashings for lower-indexed validators (and even that is unlikely)
filter_limit_operations(
self.proposer_slashings.values(), self.proposer_slashings.values(),
|slashing| { |slashing| {
state state
@ -270,10 +297,48 @@ impl OperationPool {
.map_or(false, |validator| !validator.slashed) .map_or(false, |validator| !validator.slashed)
}, },
spec.max_proposer_slashings, spec.max_proposer_slashings,
) );
// Set of validators to be slashed, so we don't attempt to construct invalid attester
// slashings.
let mut to_be_slashed = proposer_slashings
.iter()
.map(|s| s.proposer_index)
.collect::<HashSet<_>>();
let attester_slashings = self
.attester_slashings
.iter()
.filter(|(id, slashing)| {
// Check the fork.
Self::attester_slashing_id(slashing, state, spec) == **id
})
.filter(|(_, slashing)| {
// Take all slashings that will slash 1 or more validators.
let slashed_validators = gather_attester_slashing_indices_modular(
state,
slashing,
|index, validator| validator.slashed || to_be_slashed.contains(&index),
spec,
);
// Extend the `to_be_slashed` set so subsequent iterations don't try to include
// useless slashings.
if let Ok(validators) = slashed_validators {
to_be_slashed.extend(validators);
true
} else {
false
}
})
.take(spec.max_attester_slashings as usize)
.map(|(_, slashing)| slashing.clone())
.collect();
(proposer_slashings, attester_slashings)
} }
/// Prune slashings for all slashed or withdrawn validators. /// Prune proposer slashings for all slashed or withdrawn validators.
pub fn prune_proposer_slashings(&mut self, finalized_state: &BeaconState, spec: &ChainSpec) { pub fn prune_proposer_slashings(&mut self, finalized_state: &BeaconState, spec: &ChainSpec) {
prune_validator_hash_map( prune_validator_hash_map(
&mut self.proposer_slashings, &mut self.proposer_slashings,
@ -285,7 +350,22 @@ impl OperationPool {
); );
} }
// FIXME: copy ProposerSlashing code for AttesterSlashing /// Prune attester slashings for all slashed or withdrawn validators, or attestations on another
/// fork.
pub fn prune_attester_slashings(&mut self, finalized_state: &BeaconState, spec: &ChainSpec) {
self.attester_slashings.retain(|id, slashing| {
let fork_ok = &Self::attester_slashing_id(slashing, finalized_state, spec) == id;
let curr_epoch = finalized_state.current_epoch(spec);
let slashing_ok = gather_attester_slashing_indices_modular(
finalized_state,
slashing,
|_, validator| validator.slashed || validator.is_withdrawable_at(curr_epoch),
spec,
)
.is_ok();
fork_ok && slashing_ok
});
}
/// Insert a voluntary exit, validating it almost-entirely (future exits are permitted). /// Insert a voluntary exit, validating it almost-entirely (future exits are permitted).
pub fn insert_voluntary_exit( pub fn insert_voluntary_exit(
@ -359,13 +439,13 @@ impl OperationPool {
self.prune_attestations(finalized_state, spec); self.prune_attestations(finalized_state, spec);
self.prune_deposits(finalized_state); self.prune_deposits(finalized_state);
self.prune_proposer_slashings(finalized_state, spec); self.prune_proposer_slashings(finalized_state, spec);
// FIXME: add attester slashings self.prune_attester_slashings(finalized_state, spec);
self.prune_voluntary_exits(finalized_state, spec); self.prune_voluntary_exits(finalized_state, spec);
self.prune_transfers(finalized_state); self.prune_transfers(finalized_state);
} }
} }
/// Filter up to a maximum number of operations out of a slice. /// Filter up to a maximum number of operations out of an iterator.
fn filter_limit_operations<'a, T: 'a, I, F>(operations: I, filter: F, limit: u64) -> Vec<T> fn filter_limit_operations<'a, T: 'a, I, F>(operations: I, filter: F, limit: u64) -> Vec<T>
where where
I: IntoIterator<Item = &'a T>, I: IntoIterator<Item = &'a T>,

View File

@ -5,7 +5,8 @@ use ssz::{SignedRoot, TreeHash};
use types::*; use types::*;
pub use self::verify_attester_slashing::{ pub use self::verify_attester_slashing::{
gather_attester_slashing_indices, verify_attester_slashing, gather_attester_slashing_indices, gather_attester_slashing_indices_modular,
verify_attester_slashing,
}; };
pub use self::verify_proposer_slashing::verify_proposer_slashing; pub use self::verify_proposer_slashing::verify_proposer_slashing;
pub use validate_attestation::{ pub use validate_attestation::{

View File

@ -47,6 +47,25 @@ pub fn gather_attester_slashing_indices(
attester_slashing: &AttesterSlashing, attester_slashing: &AttesterSlashing,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<Vec<u64>, Error> { ) -> Result<Vec<u64>, Error> {
gather_attester_slashing_indices_modular(
state,
attester_slashing,
|_, validator| validator.slashed,
spec,
)
}
/// Same as `gather_attester_slashing_indices` but allows the caller to specify the criteria
/// for determining whether a given validator should be considered slashed.
pub fn gather_attester_slashing_indices_modular<F>(
state: &BeaconState,
attester_slashing: &AttesterSlashing,
is_slashed: F,
spec: &ChainSpec,
) -> Result<Vec<u64>, Error>
where
F: Fn(u64, &Validator) -> bool,
{
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1; let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2; let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
@ -57,7 +76,7 @@ pub fn gather_attester_slashing_indices(
.get(*i as usize) .get(*i as usize)
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?; .ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?;
if slashable_attestation_2.validator_indices.contains(&i) & !validator.slashed { if slashable_attestation_2.validator_indices.contains(&i) & !is_slashed(*i, validator) {
// TODO: verify that we should reject any slashable attestation which includes a // TODO: verify that we should reject any slashable attestation which includes a
// withdrawn validator. PH has asked the question on gitter, awaiting response. // withdrawn validator. PH has asked the question on gitter, awaiting response.
verify!( verify!(