diff --git a/book/src/slashing-protection.md b/book/src/slashing-protection.md index f6665abb50..00f0745374 100644 --- a/book/src/slashing-protection.md +++ b/book/src/slashing-protection.md @@ -60,11 +60,12 @@ Examples where it is **ineffective** are: ## Import and Export -Lighthouse supports v4 of the slashing protection interchange format described +Lighthouse supports v5 of the slashing protection interchange format described [here][interchange-spec]. An interchange file is a record of all blocks and attestations signing by a set of validator keys – basically a portable slashing protection database! -You can import a `.json` interchange file from another client using this command: +With your validator client stopped, you can import a `.json` interchange file from another client +using this command: ```bash lighthouse account validator slashing-protection import @@ -85,6 +86,8 @@ You can export Lighthouse's database for use with another client with this comma lighthouse account validator slashing-protection export ``` +The validator client needs to be stopped in order to export. + [interchange-spec]: https://hackmd.io/@sproul/Bk0Y0qdGD ## Troubleshooting diff --git a/validator_client/slashing_protection/Makefile b/validator_client/slashing_protection/Makefile index ecc48b3143..9ce05e595e 100644 --- a/validator_client/slashing_protection/Makefile +++ b/validator_client/slashing_protection/Makefile @@ -1,4 +1,4 @@ -TESTS_TAG := ac393b815b356c95569c028c215232b512df583d +TESTS_TAG := 359085be9da6e5e19644977aa45947bcec5d99de GENERATE_DIR := generated-tests OUTPUT_DIR := interchange-tests TARBALL := $(OUTPUT_DIR)-$(TESTS_TAG).tar.gz diff --git a/validator_client/slashing_protection/src/bin/test_generator.rs b/validator_client/slashing_protection/src/bin/test_generator.rs index 3522adf3fc..0426ff08fd 100644 --- a/validator_client/slashing_protection/src/bin/test_generator.rs +++ b/validator_client/slashing_protection/src/bin/test_generator.rs @@ -1,6 +1,5 @@ use slashing_protection::interchange::{ - CompleteInterchangeData, Interchange, InterchangeFormat, InterchangeMetadata, - SignedAttestation, SignedBlock, + Interchange, InterchangeData, InterchangeMetadata, SignedAttestation, SignedBlock, }; use slashing_protection::interchange_test::TestCase; use slashing_protection::test_utils::{pubkey, DEFAULT_GENESIS_VALIDATORS_ROOT}; @@ -11,31 +10,54 @@ use types::{Epoch, Hash256, Slot}; fn metadata(genesis_validators_root: Hash256) -> InterchangeMetadata { InterchangeMetadata { - interchange_format: InterchangeFormat::Complete, interchange_format_version: SUPPORTED_INTERCHANGE_FORMAT_VERSION, genesis_validators_root, } } -#[allow(clippy::type_complexity)] -fn interchange(data: Vec<(usize, Vec, Vec<(u64, u64)>)>) -> Interchange { +type TestPubkey = usize; +type TestBlocks = Vec; +type TestBlocksWithRoots = Vec<(u64, Option)>; +type TestAttestations = Vec<(u64, u64)>; +type TestAttestationsWithRoots = Vec<(u64, u64, Option)>; + +fn interchange(data: Vec<(TestPubkey, TestBlocks, TestAttestations)>) -> Interchange { let data = data .into_iter() - .map(|(pk, blocks, attestations)| CompleteInterchangeData { + .map(|(pk, blocks, attestations)| { + ( + pk, + blocks.into_iter().map(|slot| (slot, None)).collect(), + attestations + .into_iter() + .map(|(source, target)| (source, target, None)) + .collect(), + ) + }) + .collect(); + interchange_with_signing_roots(data) +} + +fn interchange_with_signing_roots( + data: Vec<(TestPubkey, TestBlocksWithRoots, TestAttestationsWithRoots)>, +) -> Interchange { + let data = data + .into_iter() + .map(|(pk, blocks, attestations)| InterchangeData { pubkey: pubkey(pk), signed_blocks: blocks .into_iter() - .map(|slot| SignedBlock { + .map(|(slot, signing_root)| SignedBlock { slot: Slot::new(slot), - signing_root: None, + signing_root: signing_root.map(Hash256::from_low_u64_be), }) .collect(), signed_attestations: attestations .into_iter() - .map(|(source, target)| SignedAttestation { + .map(|(source, target, signing_root)| SignedAttestation { source_epoch: Epoch::new(source), target_epoch: Epoch::new(target), - signing_root: None, + signing_root: signing_root.map(Hash256::from_low_u64_be), }) .collect(), }) @@ -110,11 +132,56 @@ fn main() { (0, 11, 12, true), (0, 20, 25, true), ]), + TestCase::new( + "single_validator_single_block_and_attestation_signing_root", + interchange_with_signing_roots(vec![(0, vec![(19, Some(1))], vec![(0, 1, Some(2))])]), + ), + TestCase::new( + "multiple_validators_multiple_blocks_and_attestations", + interchange(vec![ + ( + 0, + vec![10, 15, 20], + vec![(0, 1), (0, 2), (1, 3), (2, 4), (4, 5)], + ), + ( + 1, + vec![3, 4, 100], + vec![(0, 0), (0, 1), (1, 2), (2, 5), (5, 6)], + ), + (2, vec![10, 15, 20], vec![(1, 2), (1, 3), (2, 4)]), + ]), + ) + .with_blocks(vec![ + (0, 9, false), + (0, 10, false), + (0, 21, true), + (0, 11, true), + (1, 2, false), + (1, 3, false), + (1, 0, false), + (1, 101, true), + (2, 9, false), + (2, 10, false), + (2, 22, true), + ]) + .with_attestations(vec![ + (0, 0, 5, false), + (0, 3, 6, false), + (0, 4, 6, true), + (0, 5, 7, true), + (0, 6, 8, true), + (1, 1, 7, false), + (1, 1, 4, true), + (1, 5, 7, true), + (2, 0, 0, false), + (2, 0, 1, false), + (2, 2, 5, true), + ]), TestCase::new("wrong_genesis_validators_root", interchange(vec![])) .gvr(Hash256::from_low_u64_be(1)) .should_fail(), ]; - // TODO: multi-validator test let args = std::env::args().collect::>(); let output_dir = Path::new(&args[1]); diff --git a/validator_client/slashing_protection/src/interchange.rs b/validator_client/slashing_protection/src/interchange.rs index 71f678c592..542807d0d7 100644 --- a/validator_client/slashing_protection/src/interchange.rs +++ b/validator_client/slashing_protection/src/interchange.rs @@ -3,16 +3,9 @@ use std::collections::HashSet; use std::iter::FromIterator; use types::{Epoch, Hash256, PublicKey, Slot}; -#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum InterchangeFormat { - Complete, -} - #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct InterchangeMetadata { - pub interchange_format: InterchangeFormat, #[serde(with = "serde_utils::quoted_u64::require_quotes")] pub interchange_format_version: u64, pub genesis_validators_root: Hash256, @@ -20,7 +13,7 @@ pub struct InterchangeMetadata { #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] #[serde(deny_unknown_fields)] -pub struct CompleteInterchangeData { +pub struct InterchangeData { pub pubkey: PublicKey, pub signed_blocks: Vec, pub signed_attestations: Vec, @@ -49,7 +42,7 @@ pub struct SignedAttestation { #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Interchange { pub metadata: InterchangeMetadata, - pub data: Vec, + pub data: Vec, } impl Interchange { diff --git a/validator_client/slashing_protection/src/slashing_database.rs b/validator_client/slashing_protection/src/slashing_database.rs index 1bfdcf60df..7e5a800080 100644 --- a/validator_client/slashing_protection/src/slashing_database.rs +++ b/validator_client/slashing_protection/src/slashing_database.rs @@ -1,6 +1,6 @@ use crate::interchange::{ - CompleteInterchangeData, Interchange, InterchangeFormat, InterchangeMetadata, - SignedAttestation as InterchangeAttestation, SignedBlock as InterchangeBlock, + Interchange, InterchangeData, InterchangeMetadata, SignedAttestation as InterchangeAttestation, + SignedBlock as InterchangeBlock, }; use crate::signed_attestation::InvalidAttestation; use crate::signed_block::InvalidBlock; @@ -25,7 +25,7 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); pub const CONNECTION_TIMEOUT: Duration = Duration::from_millis(100); /// Supported version of the interchange format. -pub const SUPPORTED_INTERCHANGE_FORMAT_VERSION: u64 = 4; +pub const SUPPORTED_INTERCHANGE_FORMAT_VERSION: u64 = 5; #[derive(Debug, Clone)] pub struct SlashingDatabase { @@ -673,7 +673,6 @@ impl SlashingDatabase { .collect::>()?; let metadata = InterchangeMetadata { - interchange_format: InterchangeFormat::Complete, interchange_format_version: SUPPORTED_INTERCHANGE_FORMAT_VERSION, genesis_validators_root, }; @@ -681,7 +680,7 @@ impl SlashingDatabase { let data = data .into_iter() .map(|(pubkey, (signed_blocks, signed_attestations))| { - Ok(CompleteInterchangeData { + Ok(InterchangeData { pubkey: pubkey.parse().map_err(InterchangeError::InvalidPubkey)?, signed_blocks, signed_attestations,