mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-14 10:22:38 +00:00
Implement slashing protection interchange format (#1544)
## Issue Addressed Implements support for importing and exporting the slashing protection DB interchange format described here: https://hackmd.io/@sproul/Bk0Y0qdGD Also closes #1584 ## Proposed Changes * [x] Support for serializing and deserializing the format * [x] Support for importing and exporting Lighthouse's database * [x] CLI commands to invoke import and export * [x] Export to minimal format (required when a minimal format has been previously imported) * [x] Tests for export to minimal (utilising mixed importing and attestation signing?) * [x] Tests for import/export of complete format, and import of minimal format * [x] ~~Prevent attestations with sources less than our max source (Danny's suggestion). Required for the fake attestation that we put in for the minimal format to block attestations from source 0.~~ * [x] Add the concept of a "low watermark" for compatibility with the minimal format Bonus! * [x] A fix to a potentially nasty bug involving validators getting re-registered each time the validator client ran! Thankfully, the ordering of keys meant that the validator IDs used for attestations and blocks remained stable -- otherwise we could have had some slashings on our hands! 😱 * [x] Tests to confirm that this bug is indeed vanquished
This commit is contained in:
151
validator_client/slashing_protection/src/interchange_test.rs
Normal file
151
validator_client/slashing_protection/src/interchange_test.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
use crate::{
|
||||
interchange::Interchange,
|
||||
test_utils::{pubkey, DEFAULT_GENESIS_VALIDATORS_ROOT},
|
||||
SlashingDatabase,
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tempfile::tempdir;
|
||||
use types::{Epoch, Hash256, PublicKey, Slot};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TestCase {
|
||||
pub name: String,
|
||||
pub should_succeed: bool,
|
||||
pub genesis_validators_root: Hash256,
|
||||
pub interchange: Interchange,
|
||||
pub blocks: Vec<TestBlock>,
|
||||
pub attestations: Vec<TestAttestation>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TestBlock {
|
||||
pub pubkey: PublicKey,
|
||||
pub slot: Slot,
|
||||
pub should_succeed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TestAttestation {
|
||||
pub pubkey: PublicKey,
|
||||
pub source_epoch: Epoch,
|
||||
pub target_epoch: Epoch,
|
||||
pub should_succeed: bool,
|
||||
}
|
||||
|
||||
impl TestCase {
|
||||
pub fn new(name: &str, interchange: Interchange) -> Self {
|
||||
TestCase {
|
||||
name: name.into(),
|
||||
should_succeed: true,
|
||||
genesis_validators_root: DEFAULT_GENESIS_VALIDATORS_ROOT,
|
||||
interchange,
|
||||
blocks: vec![],
|
||||
attestations: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gvr(mut self, genesis_validators_root: Hash256) -> Self {
|
||||
self.genesis_validators_root = genesis_validators_root;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn should_fail(mut self) -> Self {
|
||||
self.should_succeed = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_blocks(mut self, blocks: impl IntoIterator<Item = (usize, u64, bool)>) -> Self {
|
||||
self.blocks.extend(
|
||||
blocks
|
||||
.into_iter()
|
||||
.map(|(pk, slot, should_succeed)| TestBlock {
|
||||
pubkey: pubkey(pk),
|
||||
slot: Slot::new(slot),
|
||||
should_succeed,
|
||||
}),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_attestations(
|
||||
mut self,
|
||||
attestations: impl IntoIterator<Item = (usize, u64, u64, bool)>,
|
||||
) -> Self {
|
||||
self.attestations.extend(attestations.into_iter().map(
|
||||
|(pk, source, target, should_succeed)| TestAttestation {
|
||||
pubkey: pubkey(pk),
|
||||
source_epoch: Epoch::new(source),
|
||||
target_epoch: Epoch::new(target),
|
||||
should_succeed,
|
||||
},
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run(&self) {
|
||||
let dir = tempdir().unwrap();
|
||||
let slashing_db_file = dir.path().join("slashing_protection.sqlite");
|
||||
let slashing_db = SlashingDatabase::create(&slashing_db_file).unwrap();
|
||||
|
||||
match slashing_db.import_interchange_info(&self.interchange, self.genesis_validators_root) {
|
||||
Ok(()) if !self.should_succeed => {
|
||||
panic!(
|
||||
"test `{}` succeeded on import when it should have failed",
|
||||
self.name
|
||||
);
|
||||
}
|
||||
Err(e) if self.should_succeed => {
|
||||
panic!(
|
||||
"test `{}` failed on import when it should have succeeded, error: {:?}",
|
||||
self.name, e
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
for (i, block) in self.blocks.iter().enumerate() {
|
||||
match slashing_db.check_and_insert_block_signing_root(
|
||||
&block.pubkey,
|
||||
block.slot,
|
||||
Hash256::random(),
|
||||
) {
|
||||
Ok(safe) if !block.should_succeed => {
|
||||
panic!(
|
||||
"block {} from `{}` succeeded when it should have failed: {:?}",
|
||||
i, self.name, safe
|
||||
);
|
||||
}
|
||||
Err(e) if block.should_succeed => {
|
||||
panic!(
|
||||
"block {} from `{}` failed when it should have succeeded: {:?}",
|
||||
i, self.name, e
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
for (i, att) in self.attestations.iter().enumerate() {
|
||||
match slashing_db.check_and_insert_attestation_signing_root(
|
||||
&att.pubkey,
|
||||
att.source_epoch,
|
||||
att.target_epoch,
|
||||
Hash256::random(),
|
||||
) {
|
||||
Ok(safe) if !att.should_succeed => {
|
||||
panic!(
|
||||
"attestation {} from `{}` succeeded when it should have failed: {:?}",
|
||||
i, self.name, safe
|
||||
);
|
||||
}
|
||||
Err(e) if att.should_succeed => {
|
||||
panic!(
|
||||
"attestation {} from `{}` failed when it should have succeeded: {:?}",
|
||||
i, self.name, e
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user