From 79b64b9d582f3ada527e6abd620d30fdc19f3e97 Mon Sep 17 00:00:00 2001 From: Daniel Knopik Date: Wed, 23 Jul 2025 16:39:36 +0200 Subject: [PATCH 1/2] Expose functions to do preliminary slashing checks --- .../src/slashing_database.rs | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/validator_client/slashing_protection/src/slashing_database.rs b/validator_client/slashing_protection/src/slashing_database.rs index f4c844d3140..f97e7be5527 100644 --- a/validator_client/slashing_protection/src/slashing_database.rs +++ b/validator_client/slashing_protection/src/slashing_database.rs @@ -599,6 +599,39 @@ impl SlashingDatabase { Ok(safe) } + /// Check whether a block would be safe to sign if we were to sign it now. + /// + /// The database is not modified, and therefore multiple threads reading the database might get + /// the same result. Therefore: + /// + /// DO NOT USE THIS FUNCTION TO DECIDE IF A BLOCK IS SAFE TO SIGN! + pub fn preliminary_check_block_proposal( + &self, + validator_pubkey: &PublicKeyBytes, + block_header: &BeaconBlockHeader, + domain: Hash256, + ) -> Result { + self.preliminary_check_block_signing_root( + validator_pubkey, + block_header.slot, + block_header.signing_root(domain).into(), + ) + } + + /// As for `preliminary_check_block_proposal` but without requiring the whole `BeaconBlockHeader`. + /// + /// DO NOT USE THIS FUNCTION TO DECIDE IF A BLOCK IS SAFE TO SIGN! + pub fn preliminary_check_block_signing_root( + &self, + validator_pubkey: &PublicKeyBytes, + slot: Slot, + signing_root: SigningRoot, + ) -> Result { + let mut conn = self.conn_pool.get()?; + let txn = conn.transaction_with_behavior(TransactionBehavior::Exclusive)?; + self.check_block_proposal(&txn, validator_pubkey, slot, signing_root) + } + /// Check an attestation for slash safety, and if it is safe, record it in the database. /// /// The checking and inserting happen atomically and exclusively. We enforce exclusivity @@ -670,6 +703,48 @@ impl SlashingDatabase { Ok(safe) } + /// Check whether an attestation would be safe to sign if we were to sign it now. + /// + /// The database is not modified, and therefore multiple threads reading the database might get + /// the same result. Therefore: + /// + /// DO NOT USE THIS FUNCTION TO DECIDE IF AN ATTESTATION IS SAFE TO SIGN! + pub fn preliminary_check_attestation( + &self, + validator_pubkey: &PublicKeyBytes, + attestation: &AttestationData, + domain: Hash256, + ) -> Result { + let attestation_signing_root = attestation.signing_root(domain).into(); + self.preliminary_check_attestation_signing_root( + validator_pubkey, + attestation.source.epoch, + attestation.target.epoch, + attestation_signing_root, + ) + } + + /// As for `preliminary_check_attestation` but without requiring the whole `AttestationData`. + /// + /// DO NOT USE THIS FUNCTION TO DECIDE IF AN ATTESTATION IS SAFE TO SIGN! + pub fn preliminary_check_attestation_signing_root( + &self, + validator_pubkey: &PublicKeyBytes, + att_source_epoch: Epoch, + att_target_epoch: Epoch, + att_signing_root: SigningRoot, + ) -> Result { + let mut conn = self.conn_pool.get()?; + let txn = conn.transaction_with_behavior(TransactionBehavior::Exclusive)?; + self.check_attestation( + &txn, + validator_pubkey, + att_source_epoch, + att_target_epoch, + att_signing_root, + ) + } + /// Import slashing protection from another client in the interchange format. /// /// This function will atomically import the entire interchange, failing if *any* From 2a333c807be39c0fc77a7ee5077b25b00c8c6202 Mon Sep 17 00:00:00 2001 From: Daniel Knopik Date: Wed, 10 Sep 2025 10:27:23 +0200 Subject: [PATCH 2/2] forbid prelim methods --- .gitignore | 1 - Makefile | 1 + clippy.toml | 7 +++++++ .../slashing_protection/src/slashing_database.rs | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 clippy.toml diff --git a/.gitignore b/.gitignore index e63e218a3bf..efd7916b050 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ perf.data* *.tar.gz /bin genesis.ssz -/clippy.toml /.cargo # IntelliJ diff --git a/Makefile b/Makefile index 475d3aac8a1..1b00ddc5683 100644 --- a/Makefile +++ b/Makefile @@ -269,6 +269,7 @@ lint: -D clippy::fn_to_numeric_cast_any \ -D clippy::manual_let_else \ -D clippy::large_stack_frames \ + -D clippy::disallowed_methods \ -D warnings \ -A clippy::derive_partial_eq_without_eq \ -A clippy::upper-case-acronyms \ diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000000..dabcbe8bf52 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,7 @@ +# Disallow preliminary slashing checks, +disallowed-methods = [ + { path = "slashing_protection::slashing_database::SlashingDatabase::preliminary_check_block_proposal", reason = "not safe for slashing checks", replacement = "slashing_protection::slashing_database::SlashingDatabase::check_and_insert_block_proposal" }, + { path = "slashing_protection::slashing_database::SlashingDatabase::preliminary_check_block_signing_root", reason = "not safe for slashing checks", replacement = "slashing_protection::slashing_database::SlashingDatabase::check_and_insert_block_signing_root" }, + { path = "slashing_protection::slashing_database::SlashingDatabase::preliminary_check_attestation", reason = "not safe for slashing checks", replacement = "slashing_protection::slashing_database::SlashingDatabase::check_and_insert_attestation" }, + { path = "slashing_protection::slashing_database::SlashingDatabase::preliminary_check_attestation_signing_root", reason = "not safe for slashing checks", replacement = "slashing_protection::slashing_database::SlashingDatabase::check_and_insert_attestation_signing_root" }, +] diff --git a/validator_client/slashing_protection/src/slashing_database.rs b/validator_client/slashing_protection/src/slashing_database.rs index c3d4d3e05cc..7d8947a5847 100644 --- a/validator_client/slashing_protection/src/slashing_database.rs +++ b/validator_client/slashing_protection/src/slashing_database.rs @@ -611,6 +611,7 @@ impl SlashingDatabase { block_header: &BeaconBlockHeader, domain: Hash256, ) -> Result { + #[allow(clippy::disallowed_methods)] self.preliminary_check_block_signing_root( validator_pubkey, block_header.slot, @@ -716,6 +717,7 @@ impl SlashingDatabase { domain: Hash256, ) -> Result { let attestation_signing_root = attestation.signing_root(domain).into(); + #[allow(clippy::disallowed_methods)] self.preliminary_check_attestation_signing_root( validator_pubkey, attestation.source.epoch,