Implement slasher (#1567)

This is an implementation of a slasher that lives inside the BN and can be enabled via `lighthouse bn --slasher`.

Features included in this PR:

- [x] Detection of attester slashing conditions (double votes, surrounds existing, surrounded by existing)
- [x] Integration into Lighthouse's attestation verification flow
- [x] Detection of proposer slashing conditions
- [x] Extraction of attestations from blocks as they are verified
- [x] Compression of chunks
- [x] Configurable history length
- [x] Pruning of old attestations and blocks
- [x] More tests

Future work:

* Focus on a slice of history separate from the most recent N epochs (e.g. epochs `current - K` to `current - M`)
* Run out-of-process
* Ingest attestations from the chain without a resync

Design notes are here https://hackmd.io/@sproul/HJSEklmPL
This commit is contained in:
Michael Sproul
2020-11-23 03:43:22 +00:00
parent 59b2247ab8
commit 5828ff1204
44 changed files with 3662 additions and 87 deletions

View File

@@ -32,12 +32,13 @@ use futures::channel::mpsc::Sender;
use itertools::process_results;
use operation_pool::{OperationPool, PersistedOperationPool};
use parking_lot::{Mutex, RwLock};
use slasher::Slasher;
use slog::{crit, debug, error, info, trace, warn, Logger};
use slot_clock::SlotClock;
use state_processing::{
common::get_indexed_attestation, per_block_processing,
per_block_processing::errors::AttestationValidationError, per_slot_processing,
BlockSignatureStrategy, SigVerifiedOp,
BlockSignatureStrategy, SigVerifiedOp, VerifyOperation,
};
use std::borrow::Cow;
use std::cmp::Ordering;
@@ -232,6 +233,8 @@ pub struct BeaconChain<T: BeaconChainTypes> {
pub(crate) log: Logger,
/// Arbitrary bytes included in the blocks.
pub(crate) graffiti: Graffiti,
/// Optional slasher.
pub(crate) slasher: Option<Arc<Slasher<T::EthSpec>>>,
}
type BeaconBlockAndState<T> = (BeaconBlock<T>, BeaconState<T>);
@@ -518,10 +521,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
/// Apply a function to the canonical head without cloning it.
pub fn with_head<U>(
pub fn with_head<U, E>(
&self,
f: impl FnOnce(&BeaconSnapshot<T::EthSpec>) -> Result<U, Error>,
) -> Result<U, Error> {
f: impl FnOnce(&BeaconSnapshot<T::EthSpec>) -> Result<U, E>,
) -> Result<U, E>
where
E: From<Error>,
{
let head_lock = self
.canonical_head
.try_read_for(HEAD_LOCK_TIMEOUT)
@@ -1080,6 +1086,63 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(signed_aggregate)
}
/// Move slashings collected by the slasher into the op pool for block inclusion.
fn ingest_slashings_to_op_pool(&self, state: &BeaconState<T::EthSpec>) {
if let Some(slasher) = self.slasher.as_ref() {
let attester_slashings = slasher.get_attester_slashings();
let proposer_slashings = slasher.get_proposer_slashings();
if !attester_slashings.is_empty() || !proposer_slashings.is_empty() {
debug!(
self.log,
"Ingesting slashings";
"num_attester_slashings" => attester_slashings.len(),
"num_proposer_slashings" => proposer_slashings.len(),
);
}
for slashing in attester_slashings {
let verified_slashing = match slashing.clone().validate(state, &self.spec) {
Ok(verified) => verified,
Err(e) => {
error!(
self.log,
"Attester slashing from slasher failed verification";
"error" => format!("{:?}", e),
"slashing" => format!("{:?}", slashing),
);
continue;
}
};
if let Err(e) = self.import_attester_slashing(verified_slashing) {
error!(
self.log,
"Attester slashing from slasher is invalid";
"error" => format!("{:?}", e),
"slashing" => format!("{:?}", slashing),
);
}
}
for slashing in proposer_slashings {
let verified_slashing = match slashing.clone().validate(state, &self.spec) {
Ok(verified) => verified,
Err(e) => {
error!(
self.log,
"Proposer slashing from slasher failed verification";
"error" => format!("{:?}", e),
"slashing" => format!("{:?}", slashing),
);
continue;
}
};
self.import_proposer_slashing(verified_slashing);
}
}
}
/// Check that the shuffling at `block_root` is equal to one of the shufflings of `state`.
///
/// The `target_epoch` argument determines which shuffling to check compatibility with, it
@@ -1525,6 +1588,18 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
metrics::stop_timer(attestation_observation_timer);
// If a slasher is configured, provide the attestations from the block.
if let Some(slasher) = self.slasher.as_ref() {
for attestation in &signed_block.message.body.attestations {
let committee =
state.get_beacon_committee(attestation.data.slot, attestation.data.index)?;
let indexed_attestation =
get_indexed_attestation(&committee.committee, attestation)
.map_err(|e| BlockError::BeaconChainError(e.into()))?;
slasher.accept_attestation(indexed_attestation);
}
}
// If there are new validators in this block, update our pubkey cache.
//
// We perform this _before_ adding the block to fork choice because the pubkey cache is
@@ -1735,6 +1810,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
state.latest_block_header.canonical_root()
};
self.ingest_slashings_to_op_pool(&state);
let (proposer_slashings, attester_slashings) =
self.op_pool.get_slashings(&state, &self.spec);
@@ -1951,6 +2027,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
{
self.persist_head_and_fork_choice()?;
self.op_pool.prune_attestations(self.epoch()?);
self.ingest_slashings_to_op_pool(&new_head.beacon_state);
self.persist_op_pool()?;
}
let update_head_timer = metrics::start_timer(&metrics::UPDATE_HEAD_TIMES);