mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-23 14:54:45 +00:00
Implement Slashing Protection (#1116)
* Implement slashing protection Roll-up of #588 with some conflicts resolved * WIP improvements * Require slot uniqueness for blocks (rather than epochs) * Native DB support for Slot and Epoch * Simplify surrounding/surrounded-by queries * Implement unified slashing protection database A single SQL database saves on open file descriptors. * Make slashing protection concurrency safe. Revive tests, add parallel tests. * Some simplifications * Auto-registration, test clean-ups * More tests, clean-ups, hardening * Fix comments in BLS * Optimise bulk validator registration * Delete outdated tests * Use bundled SQLite in slashing protection * Auto-register validators in simulation * Use real signing_root in slashing protection * Update book for --auto-register * Refine log messages and help flags * Correct typo in Cargo.toml authors * Fix merge conflicts * Safer error handling in sqlite slot/epoch * Address review comments * Add attestation test mutating block root Co-authored-by: pscott <scottpiriou@gmail.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
use crate::config::SLASHING_PROTECTION_FILENAME;
|
||||
use crate::fork_service::ForkService;
|
||||
use crate::validator_directory::{ValidatorDirectory, ValidatorDirectoryBuilder};
|
||||
use parking_lot::RwLock;
|
||||
use rayon::prelude::*;
|
||||
use slog::{error, Logger};
|
||||
use slashing_protection::{NotSafe, Safe, SlashingDatabase};
|
||||
use slog::{crit, error, warn, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::read_dir;
|
||||
@@ -19,6 +21,7 @@ use types::{
|
||||
#[derive(Clone)]
|
||||
pub struct ValidatorStore<T, E: EthSpec> {
|
||||
validators: Arc<RwLock<HashMap<PublicKey, ValidatorDirectory>>>,
|
||||
slashing_protection: SlashingDatabase,
|
||||
genesis_validators_root: Hash256,
|
||||
spec: Arc<ChainSpec>,
|
||||
log: Logger,
|
||||
@@ -35,6 +38,15 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
fork_service: ForkService<T, E>,
|
||||
log: Logger,
|
||||
) -> Result<Self, String> {
|
||||
let slashing_db_path = base_dir.join(SLASHING_PROTECTION_FILENAME);
|
||||
let slashing_protection =
|
||||
SlashingDatabase::open_or_create(&slashing_db_path).map_err(|e| {
|
||||
format!(
|
||||
"Failed to open or create slashing protection database: {:?}",
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
let validator_key_values = read_dir(&base_dir)
|
||||
.map_err(|e| format!("Failed to read base directory {:?}: {:?}", base_dir, e))?
|
||||
.collect::<Vec<_>>()
|
||||
@@ -68,6 +80,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
|
||||
Ok(Self {
|
||||
validators: Arc::new(RwLock::new(HashMap::from_par_iter(validator_key_values))),
|
||||
slashing_protection,
|
||||
genesis_validators_root,
|
||||
spec: Arc::new(spec),
|
||||
log,
|
||||
@@ -88,6 +101,10 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
.map_err(|e| format!("Unable to create temp dir: {:?}", e))?;
|
||||
let data_dir = PathBuf::from(temp_dir.path());
|
||||
|
||||
let slashing_db_path = data_dir.join(SLASHING_PROTECTION_FILENAME);
|
||||
let slashing_protection = SlashingDatabase::create(&slashing_db_path)
|
||||
.map_err(|e| format!("Failed to create slashing protection database: {:?}", e))?;
|
||||
|
||||
let validators = validator_indices
|
||||
.par_iter()
|
||||
.map(|index| {
|
||||
@@ -111,6 +128,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
|
||||
Ok(Self {
|
||||
validators: Arc::new(RwLock::new(HashMap::from_iter(validators))),
|
||||
slashing_protection,
|
||||
genesis_validators_root,
|
||||
spec: Arc::new(spec),
|
||||
log,
|
||||
@@ -120,6 +138,16 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Register all known validators with the slashing protection database.
|
||||
///
|
||||
/// Registration is required to protect against a lost or missing slashing database,
|
||||
/// such as when relocating validator keys to a new machine.
|
||||
pub fn register_all_validators_for_slashing_protection(&self) -> Result<(), String> {
|
||||
self.slashing_protection
|
||||
.register_validators(self.validators.read().keys())
|
||||
.map_err(|e| format!("Error while registering validators: {:?}", e))
|
||||
}
|
||||
|
||||
pub fn voting_pubkeys(&self) -> Vec<PublicKey> {
|
||||
self.validators
|
||||
.read()
|
||||
@@ -165,20 +193,73 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
&self,
|
||||
validator_pubkey: &PublicKey,
|
||||
block: BeaconBlock<E>,
|
||||
current_slot: Slot,
|
||||
) -> Option<SignedBeaconBlock<E>> {
|
||||
// TODO: check for slashing.
|
||||
self.validators
|
||||
.read()
|
||||
.get(validator_pubkey)
|
||||
.and_then(|validator_dir| {
|
||||
let voting_keypair = validator_dir.voting_keypair.as_ref()?;
|
||||
// Make sure the block slot is not higher than the current slot to avoid potential attacks.
|
||||
if block.slot > current_slot {
|
||||
warn!(
|
||||
self.log,
|
||||
"Not signing block with slot greater than current slot";
|
||||
"block_slot" => block.slot.as_u64(),
|
||||
"current_slot" => current_slot.as_u64()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Check for slashing conditions.
|
||||
let fork = self.fork()?;
|
||||
let domain = self.spec.get_domain(
|
||||
block.epoch(),
|
||||
Domain::BeaconProposer,
|
||||
&fork,
|
||||
self.genesis_validators_root,
|
||||
);
|
||||
|
||||
let slashing_status = self.slashing_protection.check_and_insert_block_proposal(
|
||||
validator_pubkey,
|
||||
&block.block_header(),
|
||||
domain,
|
||||
);
|
||||
|
||||
match slashing_status {
|
||||
// We can safely sign this block.
|
||||
Ok(Safe::Valid) => {
|
||||
let validators = self.validators.read();
|
||||
let validator = validators.get(validator_pubkey)?;
|
||||
let voting_keypair = validator.voting_keypair.as_ref()?;
|
||||
|
||||
Some(block.sign(
|
||||
&voting_keypair.sk,
|
||||
&self.fork()?,
|
||||
&fork,
|
||||
self.genesis_validators_root,
|
||||
&self.spec,
|
||||
))
|
||||
})
|
||||
}
|
||||
Ok(Safe::SameData) => {
|
||||
warn!(
|
||||
self.log,
|
||||
"Skipping signing of previously signed block";
|
||||
);
|
||||
None
|
||||
}
|
||||
Err(NotSafe::UnregisteredValidator(pk)) => {
|
||||
warn!(
|
||||
self.log,
|
||||
"Not signing block for unregistered validator";
|
||||
"msg" => "Carefully consider running with --auto-register (see --help)",
|
||||
"public_key" => format!("{:?}", pk)
|
||||
);
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
crit!(
|
||||
self.log,
|
||||
"Not signing slashable block";
|
||||
"error" => format!("{:?}", e)
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign_attestation(
|
||||
@@ -186,19 +267,40 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
validator_pubkey: &PublicKey,
|
||||
validator_committee_position: usize,
|
||||
attestation: &mut Attestation<E>,
|
||||
current_epoch: Epoch,
|
||||
) -> Option<()> {
|
||||
// TODO: check for slashing.
|
||||
self.validators
|
||||
.read()
|
||||
.get(validator_pubkey)
|
||||
.and_then(|validator_dir| {
|
||||
let voting_keypair = validator_dir.voting_keypair.as_ref()?;
|
||||
// Make sure the target epoch is not higher than the current epoch to avoid potential attacks.
|
||||
if attestation.data.target.epoch > current_epoch {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Checking for slashing conditions.
|
||||
let fork = self.fork()?;
|
||||
|
||||
let domain = self.spec.get_domain(
|
||||
attestation.data.target.epoch,
|
||||
Domain::BeaconAttester,
|
||||
&fork,
|
||||
self.genesis_validators_root,
|
||||
);
|
||||
let slashing_status = self.slashing_protection.check_and_insert_attestation(
|
||||
validator_pubkey,
|
||||
&attestation.data,
|
||||
domain,
|
||||
);
|
||||
|
||||
match slashing_status {
|
||||
// We can safely sign this attestation.
|
||||
Ok(Safe::Valid) => {
|
||||
let validators = self.validators.read();
|
||||
let validator = validators.get(validator_pubkey)?;
|
||||
let voting_keypair = validator.voting_keypair.as_ref()?;
|
||||
|
||||
attestation
|
||||
.sign(
|
||||
&voting_keypair.sk,
|
||||
validator_committee_position,
|
||||
&self.fork()?,
|
||||
&fork,
|
||||
self.genesis_validators_root,
|
||||
&self.spec,
|
||||
)
|
||||
@@ -212,7 +314,33 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
.ok()?;
|
||||
|
||||
Some(())
|
||||
})
|
||||
}
|
||||
Ok(Safe::SameData) => {
|
||||
warn!(
|
||||
self.log,
|
||||
"Skipping signing of previously signed attestation"
|
||||
);
|
||||
None
|
||||
}
|
||||
Err(NotSafe::UnregisteredValidator(pk)) => {
|
||||
warn!(
|
||||
self.log,
|
||||
"Not signing attestation for unregistered validator";
|
||||
"msg" => "Carefully consider running with --auto-register (see --help)",
|
||||
"public_key" => format!("{:?}", pk)
|
||||
);
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
crit!(
|
||||
self.log,
|
||||
"Not signing slashable attestation";
|
||||
"attestation" => format!("{:?}", attestation.data),
|
||||
"error" => format!("{:?}", e)
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Signs an `AggregateAndProof` for a given validator.
|
||||
|
||||
Reference in New Issue
Block a user