diff --git a/book/src/help_vc.md b/book/src/help_vc.md index 2a0fcbf3d72..02819804315 100644 --- a/book/src/help_vc.md +++ b/book/src/help_vc.md @@ -13,7 +13,7 @@ FLAGS: --disable-auto-discover If present, do not attempt to discover new validators in the validators-dir. Validators will need to be manually added to the validator_definitions.yml file. - --disable-log-timestamp If present, do not include timestamps in logging output. + --disable-log-timestamp If present, do not include timestamps in logging output. --disable-malloc-tuning If present, do not configure the system allocator. Providing this flag will generally increase memory usage, it should only be provided when debugging specific memory allocation issues. @@ -21,6 +21,11 @@ FLAGS: DEPRECATED. Use --broadcast. By default, Lighthouse publishes attestation, sync committee subscriptions and proposer preparation messages to all beacon nodes provided in the `--beacon-nodes flag`. This option changes that behaviour such that these api calls only go out to the first available and synced beacon node + --disable-slashing-protection-web3signer + Disable Lighthouse's slashing protection for all web3signer keys. This can reduce the I/O burden on the VC + but is only safe if slashing protection is enabled on the remote signer and is implemented correctly. DO NOT + ENABLE THIS FLAG UNLESS YOU ARE CERTAIN THAT SLASHING PROTECTION IS ENABLED ON THE REMOTE SIGNER. YOU WILL + GET SLASHED IF YOU USE THIS FLAG WITHOUT ENABLING WEB3SIGNER'S SLASHING PROTECTION. --enable-doppelganger-protection If this flag is set, Lighthouse will delay startup for three epochs and monitor for messages on the network by any of the validators managed by this client. This will result in three (possibly four) epochs worth of @@ -32,8 +37,8 @@ FLAGS: Enable per validator metrics for > 64 validators. Note: This flag is automatically enabled for <= 64 validators. Enabling this metric for higher validator counts will lead to higher volume of prometheus metrics being collected. - -h, --help Prints help information - --http Enable the RESTful HTTP API server. Disabled by default. + -h, --help Prints help information + --http Enable the RESTful HTTP API server. Disabled by default. --http-allow-keystore-export If present, allow access to the DELETE /lighthouse/keystores HTTP API method, which allows exporting keystores and passwords to HTTP API consumers who have access to the API token. This method is useful for @@ -47,7 +52,7 @@ FLAGS: flag unless you're certain that a new slashing protection database is required. Usually, your database will have been initialized when you imported your validator keys. If you misplace your database and then run with this flag you risk being slashed. - --log-color Force outputting colors when emitting logs to the terminal. + --log-color Force outputting colors when emitting logs to the terminal. --logfile-compress If present, compress old log files. This can help reduce the space needed to store old logs. @@ -55,7 +60,7 @@ FLAGS: If present, log files will be generated as world-readable meaning they can be read by any user on the machine. Note that logs can often contain sensitive information about your validator and so this flag should be used with caution. For Windows users, the log file permissions will be inherited from the parent folder. - --metrics Enable the Prometheus metrics HTTP server. Disabled by default. + --metrics Enable the Prometheus metrics HTTP server. Disabled by default. --prefer-builder-proposals If this flag is set, Lighthouse will always prefer blocks constructed by builders, regardless of payload value. @@ -69,7 +74,7 @@ FLAGS: --use-long-timeouts If present, the validator client will use longer timeouts for requests made to the beacon node. This flag is generally not recommended, longer timeouts can cause missed duties when fallbacks are used. - -V, --version Prints version information + -V, --version Prints version information OPTIONS: --beacon-nodes diff --git a/lighthouse/tests/validator_client.rs b/lighthouse/tests/validator_client.rs index 701aed07edc..764fd87ccdf 100644 --- a/lighthouse/tests/validator_client.rs +++ b/lighthouse/tests/validator_client.rs @@ -636,3 +636,20 @@ fn validator_registration_batch_size_zero_value() { .flag("validator-registration-batch-size", Some("0")) .run(); } + +#[test] +fn validator_disable_web3_signer_slashing_protection_default() { + CommandLineTest::new().run().with_config(|config| { + assert!(config.enable_web3signer_slashing_protection); + }); +} + +#[test] +fn validator_disable_web3_signer_slashing_protection() { + CommandLineTest::new() + .flag("disable-slashing-protection-web3signer", None) + .run() + .with_config(|config| { + assert!(!config.enable_web3signer_slashing_protection); + }); +} diff --git a/testing/web3signer_tests/src/lib.rs b/testing/web3signer_tests/src/lib.rs index d83ef2d59d9..8feea1fd7f2 100644 --- a/testing/web3signer_tests/src/lib.rs +++ b/testing/web3signer_tests/src/lib.rs @@ -45,7 +45,7 @@ mod tests { initialized_validators::{ load_pem_certificate, load_pkcs12_identity, InitializedValidators, }, - validator_store::ValidatorStore, + validator_store::{Error as ValidatorStoreError, ValidatorStore}, SlashingDatabase, SLASHING_PROTECTION_FILENAME, }; @@ -157,6 +157,18 @@ mod tests { } } + #[derive(Debug, Clone, Copy)] + struct SlashingProtectionConfig { + /// Whether to enable slashing protection for web3signer keys locally within Lighthouse. + local: bool, + } + + impl Default for SlashingProtectionConfig { + fn default() -> Self { + SlashingProtectionConfig { local: true } + } + } + impl Web3SignerRig { pub async fn new(network: &str, listen_address: &str, listen_port: u16) -> Self { GET_WEB3SIGNER_BIN @@ -231,6 +243,8 @@ mod tests { )) .arg("eth2") .arg(format!("--network={}", network)) + // Can't *easily* test `--slashing-protection-enabled=true` because web3signer + // requires a Postgres instance. .arg("--slashing-protection-enabled=false") .stdout(stdio()) .stderr(stdio()) @@ -294,10 +308,16 @@ mod tests { _validator_dir: TempDir, runtime: Arc, _runtime_shutdown: exit_future::Signal, + using_web3signer: bool, } impl ValidatorStoreRig { - pub async fn new(validator_definitions: Vec, spec: ChainSpec) -> Self { + pub async fn new( + validator_definitions: Vec, + slashing_protection_config: SlashingProtectionConfig, + using_web3signer: bool, + spec: ChainSpec, + ) -> Self { let log = environment::null_logger().unwrap(); let validator_dir = TempDir::new().unwrap(); @@ -333,6 +353,10 @@ mod tests { let slot_clock = TestingSlotClock::new(Slot::new(0), Duration::from_secs(0), Duration::from_secs(1)); + let config = validator_client::Config { + enable_web3signer_slashing_protection: slashing_protection_config.local, + ..Default::default() + }; let validator_store = ValidatorStore::<_, E>::new( initialized_validators, @@ -351,6 +375,7 @@ mod tests { _validator_dir: validator_dir, runtime, _runtime_shutdown: runtime_shutdown, + using_web3signer, } } @@ -379,7 +404,12 @@ mod tests { } impl TestingRig { - pub async fn new(network: &str, spec: ChainSpec, listen_port: u16) -> Self { + pub async fn new( + network: &str, + slashing_protection_config: SlashingProtectionConfig, + spec: ChainSpec, + listen_port: u16, + ) -> Self { let signer_rig = Web3SignerRig::new(network, WEB3SIGNER_LISTEN_ADDRESS, listen_port).await; let validator_pubkey = signer_rig.keypair.pk.clone(); @@ -401,7 +431,13 @@ mod tests { voting_keystore_password: Some(KEYSTORE_PASSWORD.to_string().into()), }, }; - ValidatorStoreRig::new(vec![validator_definition], spec.clone()).await + ValidatorStoreRig::new( + vec![validator_definition], + slashing_protection_config, + false, + spec.clone(), + ) + .await }; let remote_signer_validator_store = { @@ -423,7 +459,13 @@ mod tests { client_identity_password: Some(client_identity_password()), }), }; - ValidatorStoreRig::new(vec![validator_definition], spec).await + ValidatorStoreRig::new( + vec![validator_definition], + slashing_protection_config, + true, + spec, + ) + .await }; Self { @@ -466,6 +508,36 @@ mod tests { assert!(prev_signature.is_some(), "sanity check"); self } + + /// Assert that a slashable message fails to be signed locally and is either signed or not + /// by the web3signer rig depending on the value of `web3signer_should_sign`. + pub async fn assert_slashable_message_should_sign( + self, + case_name: &str, + generate_sig: F, + web3signer_should_sign: bool, + ) -> Self + where + F: Fn(PublicKeyBytes, Arc>) -> R, + R: Future>, + { + for validator_rig in &self.validator_rigs { + let result = + generate_sig(self.validator_pubkey, validator_rig.validator_store.clone()) + .await; + + if !validator_rig.using_web3signer || !web3signer_should_sign { + let err = result.unwrap_err(); + assert!( + matches!(err, ValidatorStoreError::Slashable(_)), + "should not sign slashable {case_name}" + ); + } else { + assert_eq!(result, Ok(()), "should sign slashable {case_name}"); + } + } + self + } } /// Get a generic, arbitrary attestation for signing. @@ -504,155 +576,188 @@ mod tests { let network_config = Eth2NetworkConfig::constant(network).unwrap().unwrap(); let spec = &network_config.chain_spec::().unwrap(); - TestingRig::new(network, spec.clone(), listen_port) - .await - .assert_signatures_match("randao_reveal", |pubkey, validator_store| async move { + TestingRig::new( + network, + SlashingProtectionConfig::default(), + spec.clone(), + listen_port, + ) + .await + .assert_signatures_match("randao_reveal", |pubkey, validator_store| async move { + validator_store + .randao_reveal(pubkey, Epoch::new(0)) + .await + .unwrap() + }) + .await + .assert_signatures_match("beacon_block_base", |pubkey, validator_store| async move { + let block = BeaconBlock::Base(BeaconBlockBase::empty(spec)); + let block_slot = block.slot(); + validator_store + .sign_block(pubkey, block, block_slot) + .await + .unwrap() + }) + .await + .assert_signatures_match("attestation", |pubkey, validator_store| async move { + let mut attestation = get_attestation(); + validator_store + .sign_attestation(pubkey, 0, &mut attestation, Epoch::new(0)) + .await + .unwrap(); + attestation + }) + .await + .assert_signatures_match("signed_aggregate", |pubkey, validator_store| async move { + let attestation = get_attestation(); + validator_store + .produce_signed_aggregate_and_proof( + pubkey, + 0, + attestation, + SelectionProof::from(Signature::empty()), + ) + .await + .unwrap() + }) + .await + .assert_signatures_match("selection_proof", |pubkey, validator_store| async move { + validator_store + .produce_selection_proof(pubkey, Slot::new(0)) + .await + .unwrap() + }) + .await + .assert_signatures_match( + "validator_registration", + |pubkey, validator_store| async move { + let val_reg_data = get_validator_registration(pubkey); validator_store - .randao_reveal(pubkey, Epoch::new(0)) + .sign_validator_registration_data(val_reg_data) .await .unwrap() - }) - .await - .assert_signatures_match("beacon_block_base", |pubkey, validator_store| async move { - let block = BeaconBlock::Base(BeaconBlockBase::empty(spec)); - let block_slot = block.slot(); + }, + ) + .await; + } + + /// Test all the Altair types. + async fn test_altair_types(network: &str, listen_port: u16) { + let network_config = Eth2NetworkConfig::constant(network).unwrap().unwrap(); + let spec = &network_config.chain_spec::().unwrap(); + let altair_fork_slot = spec + .altair_fork_epoch + .unwrap() + .start_slot(E::slots_per_epoch()); + + TestingRig::new( + network, + SlashingProtectionConfig::default(), + spec.clone(), + listen_port, + ) + .await + .assert_signatures_match( + "beacon_block_altair", + |pubkey, validator_store| async move { + let mut altair_block = BeaconBlockAltair::empty(spec); + altair_block.slot = altair_fork_slot; validator_store - .sign_block(pubkey, block, block_slot) + .sign_block(pubkey, BeaconBlock::Altair(altair_block), altair_fork_slot) .await .unwrap() - }) - .await - .assert_signatures_match("attestation", |pubkey, validator_store| async move { - let mut attestation = get_attestation(); + }, + ) + .await + .assert_signatures_match( + "sync_selection_proof", + |pubkey, validator_store| async move { validator_store - .sign_attestation(pubkey, 0, &mut attestation, Epoch::new(0)) + .produce_sync_selection_proof(&pubkey, altair_fork_slot, SyncSubnetId::from(0)) .await - .unwrap(); - attestation - }) - .await - .assert_signatures_match("signed_aggregate", |pubkey, validator_store| async move { - let attestation = get_attestation(); + .unwrap() + }, + ) + .await + .assert_signatures_match( + "sync_committee_signature", + |pubkey, validator_store| async move { validator_store - .produce_signed_aggregate_and_proof( - pubkey, + .produce_sync_committee_signature(altair_fork_slot, Hash256::zero(), 0, &pubkey) + .await + .unwrap() + }, + ) + .await + .assert_signatures_match( + "signed_contribution_and_proof", + |pubkey, validator_store| async move { + let contribution = SyncCommitteeContribution { + slot: altair_fork_slot, + beacon_block_root: <_>::default(), + subcommittee_index: <_>::default(), + aggregation_bits: <_>::default(), + signature: AggregateSignature::empty(), + }; + validator_store + .produce_signed_contribution_and_proof( 0, - attestation, - SelectionProof::from(Signature::empty()), + pubkey, + contribution, + SyncSelectionProof::from(Signature::empty()), ) .await .unwrap() - }) - .await - .assert_signatures_match("selection_proof", |pubkey, validator_store| async move { + }, + ) + .await + .assert_signatures_match( + "validator_registration", + |pubkey, validator_store| async move { + let val_reg_data = get_validator_registration(pubkey); validator_store - .produce_selection_proof(pubkey, Slot::new(0)) + .sign_validator_registration_data(val_reg_data) .await .unwrap() - }) - .await - .assert_signatures_match( - "validator_registration", - |pubkey, validator_store| async move { - let val_reg_data = get_validator_registration(pubkey); - validator_store - .sign_validator_registration_data(val_reg_data) - .await - .unwrap() - }, - ) - .await; + }, + ) + .await; } - /// Test all the Altair types. - async fn test_altair_types(network: &str, listen_port: u16) { + /// Test all the Merge types. + async fn test_merge_types(network: &str, listen_port: u16) { let network_config = Eth2NetworkConfig::constant(network).unwrap().unwrap(); let spec = &network_config.chain_spec::().unwrap(); - let altair_fork_slot = spec - .altair_fork_epoch + let merge_fork_slot = spec + .bellatrix_fork_epoch .unwrap() .start_slot(E::slots_per_epoch()); - TestingRig::new(network, spec.clone(), listen_port) - .await - .assert_signatures_match( - "beacon_block_altair", - |pubkey, validator_store| async move { - let mut altair_block = BeaconBlockAltair::empty(spec); - altair_block.slot = altair_fork_slot; - validator_store - .sign_block(pubkey, BeaconBlock::Altair(altair_block), altair_fork_slot) - .await - .unwrap() - }, - ) - .await - .assert_signatures_match( - "sync_selection_proof", - |pubkey, validator_store| async move { - validator_store - .produce_sync_selection_proof( - &pubkey, - altair_fork_slot, - SyncSubnetId::from(0), - ) - .await - .unwrap() - }, - ) - .await - .assert_signatures_match( - "sync_committee_signature", - |pubkey, validator_store| async move { - validator_store - .produce_sync_committee_signature( - altair_fork_slot, - Hash256::zero(), - 0, - &pubkey, - ) - .await - .unwrap() - }, - ) - .await - .assert_signatures_match( - "signed_contribution_and_proof", - |pubkey, validator_store| async move { - let contribution = SyncCommitteeContribution { - slot: altair_fork_slot, - beacon_block_root: <_>::default(), - subcommittee_index: <_>::default(), - aggregation_bits: <_>::default(), - signature: AggregateSignature::empty(), - }; - validator_store - .produce_signed_contribution_and_proof( - 0, - pubkey, - contribution, - SyncSelectionProof::from(Signature::empty()), - ) - .await - .unwrap() - }, - ) - .await - .assert_signatures_match( - "validator_registration", - |pubkey, validator_store| async move { - let val_reg_data = get_validator_registration(pubkey); - validator_store - .sign_validator_registration_data(val_reg_data) - .await - .unwrap() - }, - ) - .await; + TestingRig::new( + network, + SlashingProtectionConfig::default(), + spec.clone(), + listen_port, + ) + .await + .assert_signatures_match("beacon_block_merge", |pubkey, validator_store| async move { + let mut merge_block = BeaconBlockMerge::empty(spec); + merge_block.slot = merge_fork_slot; + validator_store + .sign_block(pubkey, BeaconBlock::Merge(merge_block), merge_fork_slot) + .await + .unwrap() + }) + .await; } - /// Test all the Merge types. - async fn test_merge_types(network: &str, listen_port: u16) { + async fn test_lighthouse_slashing_protection( + slashing_protection_config: SlashingProtectionConfig, + listen_port: u16, + ) { + // Run these tests on mainnet. + let network = "mainnet"; + let network_config = Eth2NetworkConfig::constant(network).unwrap().unwrap(); let spec = &network_config.chain_spec::().unwrap(); let merge_fork_slot = spec @@ -660,17 +765,122 @@ mod tests { .unwrap() .start_slot(E::slots_per_epoch()); - TestingRig::new(network, spec.clone(), listen_port) - .await - .assert_signatures_match("beacon_block_merge", |pubkey, validator_store| async move { - let mut merge_block = BeaconBlockMerge::empty(spec); - merge_block.slot = merge_fork_slot; + // The slashable message should only be signed by the web3signer validator if slashing + // protection is disabled in Lighthouse. + let slashable_message_should_sign = !slashing_protection_config.local; + + let first_attestation = || { + let mut attestation = get_attestation(); + attestation.data.source.epoch = Epoch::new(1); + attestation.data.target.epoch = Epoch::new(4); + attestation + }; + + let double_vote_attestation = || { + let mut attestation = first_attestation(); + attestation.data.beacon_block_root = Hash256::from_low_u64_be(1); + attestation + }; + + let surrounding_attestation = || { + let mut attestation = first_attestation(); + attestation.data.source.epoch = Epoch::new(0); + attestation.data.target.epoch = Epoch::new(5); + attestation + }; + + let surrounded_attestation = || { + let mut attestation = first_attestation(); + attestation.data.source.epoch = Epoch::new(2); + attestation.data.target.epoch = Epoch::new(3); + attestation + }; + + let first_block = || { + let mut merge_block = BeaconBlockMerge::empty(spec); + merge_block.slot = merge_fork_slot; + BeaconBlock::Merge(merge_block) + }; + + let double_vote_block = || { + let mut block = first_block(); + *block.state_root_mut() = Hash256::repeat_byte(0xff); + block + }; + + let current_epoch = Epoch::new(5); + + TestingRig::new( + network, + slashing_protection_config, + spec.clone(), + listen_port, + ) + .await + .assert_signatures_match("first_attestation", |pubkey, validator_store| async move { + let mut attestation = first_attestation(); + validator_store + .sign_attestation(pubkey, 0, &mut attestation, current_epoch) + .await + .unwrap(); + attestation + }) + .await + .assert_slashable_message_should_sign( + "double_vote_attestation", + move |pubkey, validator_store| async move { + let mut attestation = double_vote_attestation(); validator_store - .sign_block(pubkey, BeaconBlock::Merge(merge_block), merge_fork_slot) + .sign_attestation(pubkey, 0, &mut attestation, current_epoch) .await - .unwrap() - }) - .await; + }, + slashable_message_should_sign, + ) + .await + .assert_slashable_message_should_sign( + "surrounding_attestation", + move |pubkey, validator_store| async move { + let mut attestation = surrounding_attestation(); + validator_store + .sign_attestation(pubkey, 0, &mut attestation, current_epoch) + .await + }, + slashable_message_should_sign, + ) + .await + .assert_slashable_message_should_sign( + "surrounded_attestation", + move |pubkey, validator_store| async move { + let mut attestation = surrounded_attestation(); + validator_store + .sign_attestation(pubkey, 0, &mut attestation, current_epoch) + .await + }, + slashable_message_should_sign, + ) + .await + .assert_signatures_match("first_block", |pubkey, validator_store| async move { + let block = first_block(); + let slot = block.slot(); + validator_store + .sign_block(pubkey, block, slot) + .await + .unwrap() + }) + .await + .assert_slashable_message_should_sign( + "double_vote_block", + move |pubkey, validator_store| async move { + let block = double_vote_block(); + let slot = block.slot(); + validator_store + .sign_block(pubkey, block, slot) + .await + .map(|_| ()) + }, + slashable_message_should_sign, + ) + .await; } #[tokio::test] @@ -707,4 +917,14 @@ mod tests { async fn sepolia_merge_types() { test_merge_types("sepolia", 4252).await } + + #[tokio::test] + async fn slashing_protection_disabled_locally() { + test_lighthouse_slashing_protection(SlashingProtectionConfig { local: false }, 4253).await + } + + #[tokio::test] + async fn slashing_protection_enabled_locally() { + test_lighthouse_slashing_protection(SlashingProtectionConfig { local: true }, 4254).await + } } diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 295b8910b20..e4e5a2f1307 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -367,6 +367,17 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { constructed by builders, regardless of payload value.") .takes_value(false), ) + .arg( + Arg::with_name("disable-slashing-protection-web3signer") + .long("disable-slashing-protection-web3signer") + .help("Disable Lighthouse's slashing protection for all web3signer keys. This can \ + reduce the I/O burden on the VC but is only safe if slashing protection \ + is enabled on the remote signer and is implemented correctly. DO NOT ENABLE \ + THIS FLAG UNLESS YOU ARE CERTAIN THAT SLASHING PROTECTION IS ENABLED ON \ + THE REMOTE SIGNER. YOU WILL GET SLASHED IF YOU USE THIS FLAG WITHOUT \ + ENABLING WEB3SIGNER'S SLASHING PROTECTION.") + .takes_value(false) + ) /* * Experimental/development options. */ diff --git a/validator_client/src/config.rs b/validator_client/src/config.rs index 5c1f87e61ba..7ac9e3e3bc7 100644 --- a/validator_client/src/config.rs +++ b/validator_client/src/config.rs @@ -76,6 +76,8 @@ pub struct Config { pub enable_latency_measurement_service: bool, /// Defines the number of validators per `validator/register_validator` request sent to the BN. pub validator_registration_batch_size: usize, + /// Enable slashing protection even while using web3signer keys. + pub enable_web3signer_slashing_protection: bool, /// Enables block production via the block v3 endpoint. This configuration option can be removed post deneb. pub produce_block_v3: bool, /// Specifies the boost factor, a percentage multiplier to apply to the builder's payload value. @@ -124,6 +126,7 @@ impl Default for Config { broadcast_topics: vec![ApiTopic::Subscriptions], enable_latency_measurement_service: true, validator_registration_batch_size: 500, + enable_web3signer_slashing_protection: true, produce_block_v3: false, builder_boost_factor: None, prefer_builder_proposals: false, @@ -407,6 +410,19 @@ impl Config { return Err("validator-registration-batch-size cannot be 0".to_string()); } + config.enable_web3signer_slashing_protection = + if cli_args.is_present("disable-slashing-protection-web3signer") { + warn!( + log, + "Slashing protection for remote keys disabled"; + "info" => "ensure slashing protection on web3signer is enabled or you WILL \ + get slashed" + ); + false + } else { + true + }; + Ok(config) } } diff --git a/validator_client/src/signing_method.rs b/validator_client/src/signing_method.rs index 0de2f2f54fa..4ead0ed3c77 100644 --- a/validator_client/src/signing_method.rs +++ b/validator_client/src/signing_method.rs @@ -117,6 +117,20 @@ impl SigningContext { } impl SigningMethod { + /// Return whether this signing method requires local slashing protection. + pub fn requires_local_slashing_protection( + &self, + enable_web3signer_slashing_protection: bool, + ) -> bool { + match self { + // Slashing protection is ALWAYS required for local keys. DO NOT TURN THIS OFF. + SigningMethod::LocalKeystore { .. } => true, + // Slashing protection is only required for remote signer keys when the configuration + // dictates that it is desired. + SigningMethod::Web3Signer { .. } => enable_web3signer_slashing_protection, + } + } + /// Return the signature of `signable_message`, with respect to the `signing_context`. pub async fn get_signature>( &self, diff --git a/validator_client/src/validator_store.rs b/validator_client/src/validator_store.rs index a2298d30380..b8c11a79bc0 100644 --- a/validator_client/src/validator_store.rs +++ b/validator_client/src/validator_store.rs @@ -97,6 +97,7 @@ pub struct ValidatorStore { fee_recipient_process: Option
, gas_limit: Option, builder_proposals: bool, + enable_web3signer_slashing_protection: bool, produce_block_v3: bool, prefer_builder_proposals: bool, builder_boost_factor: Option, @@ -131,6 +132,7 @@ impl ValidatorStore { fee_recipient_process: config.fee_recipient, gas_limit: config.gas_limit, builder_proposals: config.builder_proposals, + enable_web3signer_slashing_protection: config.enable_web3signer_slashing_protection, produce_block_v3: config.produce_block_v3, prefer_builder_proposals: config.prefer_builder_proposals, builder_boost_factor: config.builder_boost_factor, @@ -604,19 +606,26 @@ impl ValidatorStore { let signing_context = self.signing_context(Domain::BeaconProposer, signing_epoch); let domain_hash = signing_context.domain_hash(&self.spec); + let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; + // Check for slashing conditions. - let slashing_status = self.slashing_protection.check_and_insert_block_proposal( - &validator_pubkey, - &block.block_header(), - domain_hash, - ); + let slashing_status = if signing_method + .requires_local_slashing_protection(self.enable_web3signer_slashing_protection) + { + self.slashing_protection.check_and_insert_block_proposal( + &validator_pubkey, + &block.block_header(), + domain_hash, + ) + } else { + Ok(Safe::Valid) + }; match slashing_status { // We can safely sign this block without slashing. Ok(Safe::Valid) => { metrics::inc_counter_vec(&metrics::SIGNED_BLOCKS_TOTAL, &[metrics::SUCCESS]); - let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; let signature = signing_method .get_signature::( SignableMessage::BeaconBlock(&block), @@ -672,20 +681,28 @@ impl ValidatorStore { }); } + // Get the signing method and check doppelganger protection. + let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; + // Checking for slashing conditions. let signing_epoch = attestation.data.target.epoch; let signing_context = self.signing_context(Domain::BeaconAttester, signing_epoch); let domain_hash = signing_context.domain_hash(&self.spec); - let slashing_status = self.slashing_protection.check_and_insert_attestation( - &validator_pubkey, - &attestation.data, - domain_hash, - ); + let slashing_status = if signing_method + .requires_local_slashing_protection(self.enable_web3signer_slashing_protection) + { + self.slashing_protection.check_and_insert_attestation( + &validator_pubkey, + &attestation.data, + domain_hash, + ) + } else { + Ok(Safe::Valid) + }; match slashing_status { // We can safely sign this attestation. Ok(Safe::Valid) => { - let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; let signature = signing_method .get_signature::>( SignableMessage::AttestationData(&attestation.data),