Modularize validator store (#6705)

- Create trait `ValidatorStore` with all functions used by the `validator_services`
- Make `validator_services` generic on `S: ValidatorStore`
- Introduce `LighthouseValidatorStore`, which has identical functionality to the old `ValidatorStore`
- Remove dependencies (especially `environment`) from `validator_services` and `beacon_node_fallback` in order to be able to cleanly use them in Anchor
This commit is contained in:
Daniel Knopik
2025-05-07 05:43:33 +02:00
committed by GitHub
parent beb0ce68bd
commit 3d92e3663b
42 changed files with 2010 additions and 1622 deletions

View File

@@ -42,68 +42,7 @@ use task_executor::ShutdownReason;
use tokio::time::sleep;
use tracing::{error, info};
use types::{Epoch, EthSpec, PublicKeyBytes, Slot};
/// A wrapper around `PublicKeyBytes` which encodes information about the status of a validator
/// pubkey with regards to doppelganger protection.
#[derive(Debug, PartialEq)]
pub enum DoppelgangerStatus {
/// Doppelganger protection has approved this for signing.
///
/// This is because the service has waited some period of time to
/// detect other instances of this key on the network.
SigningEnabled(PublicKeyBytes),
/// Doppelganger protection is still waiting to detect other instances.
///
/// Do not use this pubkey for signing slashable messages!!
///
/// However, it can safely be used for other non-slashable operations (e.g., collecting duties
/// or subscribing to subnets).
SigningDisabled(PublicKeyBytes),
/// This pubkey is unknown to the doppelganger service.
///
/// This represents a serious internal error in the program. This validator will be permanently
/// disabled!
UnknownToDoppelganger(PublicKeyBytes),
}
impl DoppelgangerStatus {
/// Only return a pubkey if it is explicitly safe for doppelganger protection.
///
/// If `Some(pubkey)` is returned, doppelganger has declared it safe for signing.
///
/// ## Note
///
/// "Safe" is only best-effort by doppelganger. There is no guarantee that a doppelganger
/// doesn't exist.
pub fn only_safe(self) -> Option<PublicKeyBytes> {
match self {
DoppelgangerStatus::SigningEnabled(pubkey) => Some(pubkey),
DoppelgangerStatus::SigningDisabled(_) => None,
DoppelgangerStatus::UnknownToDoppelganger(_) => None,
}
}
/// Returns a key regardless of whether or not doppelganger has approved it. Such a key might be
/// used for signing non-slashable messages, duties collection or other activities.
///
/// If the validator is unknown to doppelganger then `None` will be returned.
pub fn ignored(self) -> Option<PublicKeyBytes> {
match self {
DoppelgangerStatus::SigningEnabled(pubkey) => Some(pubkey),
DoppelgangerStatus::SigningDisabled(pubkey) => Some(pubkey),
DoppelgangerStatus::UnknownToDoppelganger(_) => None,
}
}
/// Only return a pubkey if it will not be used for signing due to doppelganger detection.
pub fn only_unsafe(self) -> Option<PublicKeyBytes> {
match self {
DoppelgangerStatus::SigningEnabled(_) => None,
DoppelgangerStatus::SigningDisabled(pubkey) => Some(pubkey),
DoppelgangerStatus::UnknownToDoppelganger(pubkey) => Some(pubkey),
}
}
}
use validator_store::{DoppelgangerStatus, ValidatorStore};
struct LivenessResponses {
current_epoch_responses: Vec<LivenessResponseData>,
@@ -114,13 +53,6 @@ struct LivenessResponses {
/// validators on the network.
pub const DEFAULT_REMAINING_DETECTION_EPOCHS: u64 = 1;
/// This crate cannot depend on ValidatorStore as validator_store depends on this crate and
/// initialises the doppelganger protection. For this reason, we abstract the validator store
/// functions this service needs through the following trait
pub trait DoppelgangerValidatorStore {
fn get_validator_index(&self, pubkey: &PublicKeyBytes) -> Option<u64>;
}
/// Store the per-validator status of doppelganger checking.
#[derive(Debug, PartialEq)]
pub struct DoppelgangerState {
@@ -163,8 +95,8 @@ impl DoppelgangerState {
/// If the BN fails to respond to either of these requests, simply return an empty response.
/// This behaviour is to help prevent spurious failures on the BN from needlessly preventing
/// doppelganger progression.
async fn beacon_node_liveness<T: 'static + SlotClock, E: EthSpec>(
beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
async fn beacon_node_liveness<T: 'static + SlotClock>(
beacon_nodes: Arc<BeaconNodeFallback<T>>,
current_epoch: Epoch,
validator_indices: Vec<u64>,
) -> LivenessResponses {
@@ -280,20 +212,20 @@ impl DoppelgangerService {
service: Arc<Self>,
context: RuntimeContext<E>,
validator_store: Arc<V>,
beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
beacon_nodes: Arc<BeaconNodeFallback<T>>,
slot_clock: T,
) -> Result<(), String>
where
E: EthSpec,
T: 'static + SlotClock,
V: DoppelgangerValidatorStore + Send + Sync + 'static,
V: ValidatorStore<E = E> + Send + Sync + 'static,
{
// Define the `get_index` function as one that uses the validator store.
let get_index = move |pubkey| validator_store.get_validator_index(&pubkey);
let get_index = move |pubkey| validator_store.validator_index(&pubkey);
// Define the `get_liveness` function as one that queries the beacon node API.
let get_liveness = move |current_epoch, validator_indices| {
beacon_node_liveness(beacon_nodes.clone(), current_epoch, validator_indices)
beacon_node_liveness::<T>(beacon_nodes.clone(), current_epoch, validator_indices)
};
let mut shutdown_sender = context.executor.shutdown_sender();
@@ -378,17 +310,18 @@ impl DoppelgangerService {
///
/// Validators added during the genesis epoch will not have doppelganger protection applied to
/// them.
pub fn register_new_validator<E: EthSpec, T: SlotClock>(
pub fn register_new_validator<T: SlotClock>(
&self,
validator: PublicKeyBytes,
slot_clock: &T,
slots_per_epoch: u64,
) -> Result<(), String> {
let current_epoch = slot_clock
// If registering before genesis, use the genesis slot.
.now_or_genesis()
.ok_or_else(|| "Unable to read slot clock when registering validator".to_string())?
.epoch(E::slots_per_epoch());
let genesis_epoch = slot_clock.genesis_slot().epoch(E::slots_per_epoch());
.epoch(slots_per_epoch);
let genesis_epoch = slot_clock.genesis_slot().epoch(slots_per_epoch);
let remaining_epochs = if current_epoch <= genesis_epoch {
// Disable doppelganger protection when the validator was initialized before genesis.
@@ -673,6 +606,7 @@ mod test {
test_utils::{SeedableRng, TestRandom, XorShiftRng},
MainnetEthSpec,
};
use validator_store::DoppelgangerStatus;
const DEFAULT_VALIDATORS: usize = 8;
@@ -773,7 +707,7 @@ mod test {
.expect("index should exist");
self.doppelganger
.register_new_validator::<E, _>(pubkey, &self.slot_clock)
.register_new_validator(pubkey, &self.slot_clock, E::slots_per_epoch())
.unwrap();
self.doppelganger
.doppelganger_states