Optional slashing protection for remote keys (#4981)
* Optional slashing protection for remote keys * Merge remote-tracking branch 'origin/unstable' into disable-slashing-protection-web3signer * Start writing tests * Merge remote-tracking branch 'origin/unstable' into disable-slashing-protection-web3signer * Merge remote-tracking branch 'michael/disable-slashing-protection-web3signer' into disable-slashing-protection-web3signer * Make half-written tests compile * Make tests work * Update help text * Update book CLI text * Merge remote-tracking branch 'origin/unstable' into disable-slashing-protection-web3signer * More logging & CLI tests * CLI tweaks
This commit is contained in:
parent
795c5778e1
commit
7bec3f9b59
@ -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 <NETWORK_ADDRESSES>
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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<tokio::runtime::Runtime>,
|
||||
_runtime_shutdown: exit_future::Signal,
|
||||
using_web3signer: bool,
|
||||
}
|
||||
|
||||
impl ValidatorStoreRig {
|
||||
pub async fn new(validator_definitions: Vec<ValidatorDefinition>, spec: ChainSpec) -> Self {
|
||||
pub async fn new(
|
||||
validator_definitions: Vec<ValidatorDefinition>,
|
||||
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<F, R>(
|
||||
self,
|
||||
case_name: &str,
|
||||
generate_sig: F,
|
||||
web3signer_should_sign: bool,
|
||||
) -> Self
|
||||
where
|
||||
F: Fn(PublicKeyBytes, Arc<ValidatorStore<TestingSlotClock, E>>) -> R,
|
||||
R: Future<Output = Result<(), ValidatorStoreError>>,
|
||||
{
|
||||
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,64 +576,69 @@ mod tests {
|
||||
let network_config = Eth2NetworkConfig::constant(network).unwrap().unwrap();
|
||||
let spec = &network_config.chain_spec::<E>().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();
|
||||
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
|
||||
.sign_validator_registration_data(val_reg_data)
|
||||
.await
|
||||
.unwrap()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Test all the Altair types.
|
||||
@ -573,82 +650,78 @@ mod tests {
|
||||
.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_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;
|
||||
}
|
||||
|
||||
/// Test all the Merge types.
|
||||
@ -660,17 +733,154 @@ 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;
|
||||
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;
|
||||
}
|
||||
|
||||
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::<E>().unwrap();
|
||||
let merge_fork_slot = spec
|
||||
.bellatrix_fork_epoch
|
||||
.unwrap()
|
||||
.start_slot(E::slots_per_epoch());
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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<T: EthSpec, Payload: AbstractExecPayload<T>>(
|
||||
&self,
|
||||
|
@ -97,6 +97,7 @@ pub struct ValidatorStore<T, E: EthSpec> {
|
||||
fee_recipient_process: Option<Address>,
|
||||
gas_limit: Option<u64>,
|
||||
builder_proposals: bool,
|
||||
enable_web3signer_slashing_protection: bool,
|
||||
produce_block_v3: bool,
|
||||
prefer_builder_proposals: bool,
|
||||
builder_boost_factor: Option<u64>,
|
||||
@ -131,6 +132,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
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<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
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::<E, Payload>(
|
||||
SignableMessage::BeaconBlock(&block),
|
||||
@ -672,20 +681,28 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||
});
|
||||
}
|
||||
|
||||
// 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::<E, BlindedPayload<E>>(
|
||||
SignableMessage::AttestationData(&attestation.data),
|
||||
|
Loading…
Reference in New Issue
Block a user