mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-02 16:21:42 +00:00
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:
@@ -10,18 +10,17 @@ path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
environment = { workspace = true }
|
||||
eth2 = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
slot_clock = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
task_executor = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
types = { workspace = true }
|
||||
validator_metrics = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
logging = { workspace = true }
|
||||
validator_test_rig = { workspace = true }
|
||||
|
||||
@@ -8,7 +8,6 @@ use beacon_node_health::{
|
||||
IsOptimistic, SyncDistanceTier,
|
||||
};
|
||||
use clap::ValueEnum;
|
||||
use environment::RuntimeContext;
|
||||
use eth2::BeaconNodeHttpClient;
|
||||
use futures::future;
|
||||
use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
|
||||
@@ -17,11 +16,11 @@ use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::vec::Vec;
|
||||
use strum::EnumVariantNames;
|
||||
use task_executor::TaskExecutor;
|
||||
use tokio::{sync::RwLock, time::sleep};
|
||||
use tracing::{debug, error, warn};
|
||||
use types::{ChainSpec, Config as ConfigSpec, EthSpec, Slot};
|
||||
@@ -61,17 +60,16 @@ pub struct LatencyMeasurement {
|
||||
///
|
||||
/// See `SLOT_LOOKAHEAD` for information about when this should run.
|
||||
pub fn start_fallback_updater_service<T: SlotClock + 'static, E: EthSpec>(
|
||||
context: RuntimeContext<E>,
|
||||
beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
|
||||
executor: TaskExecutor,
|
||||
beacon_nodes: Arc<BeaconNodeFallback<T>>,
|
||||
) -> Result<(), &'static str> {
|
||||
let executor = context.executor;
|
||||
if beacon_nodes.slot_clock.is_none() {
|
||||
return Err("Cannot start fallback updater without slot clock");
|
||||
}
|
||||
|
||||
let future = async move {
|
||||
loop {
|
||||
beacon_nodes.update_all_candidates().await;
|
||||
beacon_nodes.update_all_candidates::<E>().await;
|
||||
|
||||
let sleep_time = beacon_nodes
|
||||
.slot_clock
|
||||
@@ -186,29 +184,27 @@ impl Serialize for CandidateInfo {
|
||||
/// Represents a `BeaconNodeHttpClient` inside a `BeaconNodeFallback` that may or may not be used
|
||||
/// for a query.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CandidateBeaconNode<E> {
|
||||
pub struct CandidateBeaconNode {
|
||||
pub index: usize,
|
||||
pub beacon_node: BeaconNodeHttpClient,
|
||||
pub health: Arc<RwLock<Result<BeaconNodeHealth, CandidateError>>>,
|
||||
_phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> PartialEq for CandidateBeaconNode<E> {
|
||||
impl PartialEq for CandidateBeaconNode {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.index == other.index && self.beacon_node == other.beacon_node
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> Eq for CandidateBeaconNode<E> {}
|
||||
impl Eq for CandidateBeaconNode {}
|
||||
|
||||
impl<E: EthSpec> CandidateBeaconNode<E> {
|
||||
impl CandidateBeaconNode {
|
||||
/// Instantiate a new node.
|
||||
pub fn new(beacon_node: BeaconNodeHttpClient, index: usize) -> Self {
|
||||
Self {
|
||||
index,
|
||||
beacon_node,
|
||||
health: Arc::new(RwLock::new(Err(CandidateError::Uninitialized))),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,13 +213,13 @@ impl<E: EthSpec> CandidateBeaconNode<E> {
|
||||
*self.health.read().await
|
||||
}
|
||||
|
||||
pub async fn refresh_health<T: SlotClock>(
|
||||
pub async fn refresh_health<E: EthSpec, T: SlotClock>(
|
||||
&self,
|
||||
distance_tiers: &BeaconNodeSyncDistanceTiers,
|
||||
slot_clock: Option<&T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), CandidateError> {
|
||||
if let Err(e) = self.is_compatible(spec).await {
|
||||
if let Err(e) = self.is_compatible::<E>(spec).await {
|
||||
*self.health.write().await = Err(e);
|
||||
return Err(e);
|
||||
}
|
||||
@@ -287,7 +283,7 @@ impl<E: EthSpec> CandidateBeaconNode<E> {
|
||||
}
|
||||
|
||||
/// Checks if the node has the correct specification.
|
||||
async fn is_compatible(&self, spec: &ChainSpec) -> Result<(), CandidateError> {
|
||||
async fn is_compatible<E: EthSpec>(&self, spec: &ChainSpec) -> Result<(), CandidateError> {
|
||||
let config = self
|
||||
.beacon_node
|
||||
.get_config_spec::<ConfigSpec>()
|
||||
@@ -372,17 +368,17 @@ impl<E: EthSpec> CandidateBeaconNode<E> {
|
||||
/// behaviour, where the failure of one candidate results in the next candidate receiving an
|
||||
/// identical query.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BeaconNodeFallback<T, E> {
|
||||
pub candidates: Arc<RwLock<Vec<CandidateBeaconNode<E>>>>,
|
||||
pub struct BeaconNodeFallback<T> {
|
||||
pub candidates: Arc<RwLock<Vec<CandidateBeaconNode>>>,
|
||||
distance_tiers: BeaconNodeSyncDistanceTiers,
|
||||
slot_clock: Option<T>,
|
||||
broadcast_topics: Vec<ApiTopic>,
|
||||
spec: Arc<ChainSpec>,
|
||||
}
|
||||
|
||||
impl<T: SlotClock, E: EthSpec> BeaconNodeFallback<T, E> {
|
||||
impl<T: SlotClock> BeaconNodeFallback<T> {
|
||||
pub fn new(
|
||||
candidates: Vec<CandidateBeaconNode<E>>,
|
||||
candidates: Vec<CandidateBeaconNode>,
|
||||
config: Config,
|
||||
broadcast_topics: Vec<ApiTopic>,
|
||||
spec: Arc<ChainSpec>,
|
||||
@@ -464,7 +460,7 @@ impl<T: SlotClock, E: EthSpec> BeaconNodeFallback<T, E> {
|
||||
/// It is possible for a node to return an unsynced status while continuing to serve
|
||||
/// low quality responses. To route around this it's best to poll all connected beacon nodes.
|
||||
/// A previous implementation of this function polled only the unavailable BNs.
|
||||
pub async fn update_all_candidates(&self) {
|
||||
pub async fn update_all_candidates<E: EthSpec>(&self) {
|
||||
// Clone the vec, so we release the read lock immediately.
|
||||
// `candidate.health` is behind an Arc<RwLock>, so this would still allow us to mutate the values.
|
||||
let candidates = self.candidates.read().await.clone();
|
||||
@@ -472,7 +468,7 @@ impl<T: SlotClock, E: EthSpec> BeaconNodeFallback<T, E> {
|
||||
let mut nodes = Vec::with_capacity(candidates.len());
|
||||
|
||||
for candidate in candidates.iter() {
|
||||
futures.push(candidate.refresh_health(
|
||||
futures.push(candidate.refresh_health::<E, T>(
|
||||
&self.distance_tiers,
|
||||
self.slot_clock.as_ref(),
|
||||
&self.spec,
|
||||
@@ -675,7 +671,7 @@ impl<T: SlotClock, E: EthSpec> BeaconNodeFallback<T, E> {
|
||||
}
|
||||
|
||||
/// Helper functions to allow sorting candidate nodes by health.
|
||||
async fn sort_nodes_by_health<E: EthSpec>(nodes: &mut Vec<CandidateBeaconNode<E>>) {
|
||||
async fn sort_nodes_by_health(nodes: &mut Vec<CandidateBeaconNode>) {
|
||||
// Fetch all health values.
|
||||
let health_results: Vec<Result<BeaconNodeHealth, CandidateError>> =
|
||||
future::join_all(nodes.iter().map(|node| node.health())).await;
|
||||
@@ -693,7 +689,7 @@ async fn sort_nodes_by_health<E: EthSpec>(nodes: &mut Vec<CandidateBeaconNode<E>
|
||||
});
|
||||
|
||||
// Reorder candidates based on the sorted indices.
|
||||
let sorted_nodes: Vec<CandidateBeaconNode<E>> = indices_with_health
|
||||
let sorted_nodes: Vec<CandidateBeaconNode> = indices_with_health
|
||||
.into_iter()
|
||||
.map(|(index, _)| nodes[index].clone())
|
||||
.collect();
|
||||
@@ -752,7 +748,7 @@ mod tests {
|
||||
let optimistic_status = IsOptimistic::No;
|
||||
let execution_status = ExecutionEngineHealth::Healthy;
|
||||
|
||||
fn new_candidate(index: usize) -> CandidateBeaconNode<E> {
|
||||
fn new_candidate(index: usize) -> CandidateBeaconNode {
|
||||
let beacon_node = BeaconNodeHttpClient::new(
|
||||
SensitiveUrl::parse(&format!("http://example_{index}.com")).unwrap(),
|
||||
Timeouts::set_all(Duration::from_secs(index as u64)),
|
||||
@@ -859,21 +855,21 @@ mod tests {
|
||||
async fn new_mock_beacon_node(
|
||||
index: usize,
|
||||
spec: &ChainSpec,
|
||||
) -> (MockBeaconNode<E>, CandidateBeaconNode<E>) {
|
||||
) -> (MockBeaconNode<E>, CandidateBeaconNode) {
|
||||
let mut mock_beacon_node = MockBeaconNode::<E>::new().await;
|
||||
mock_beacon_node.mock_config_spec(spec);
|
||||
|
||||
let beacon_node =
|
||||
CandidateBeaconNode::<E>::new(mock_beacon_node.beacon_api_client.clone(), index);
|
||||
CandidateBeaconNode::new(mock_beacon_node.beacon_api_client.clone(), index);
|
||||
|
||||
(mock_beacon_node, beacon_node)
|
||||
}
|
||||
|
||||
fn create_beacon_node_fallback(
|
||||
candidates: Vec<CandidateBeaconNode<E>>,
|
||||
candidates: Vec<CandidateBeaconNode>,
|
||||
topics: Vec<ApiTopic>,
|
||||
spec: Arc<ChainSpec>,
|
||||
) -> BeaconNodeFallback<TestingSlotClock, E> {
|
||||
) -> BeaconNodeFallback<TestingSlotClock> {
|
||||
let mut beacon_node_fallback =
|
||||
BeaconNodeFallback::new(candidates, Config::default(), topics, spec);
|
||||
|
||||
@@ -929,7 +925,7 @@ mod tests {
|
||||
sync_distance: Slot::new(0),
|
||||
});
|
||||
|
||||
beacon_node_fallback.update_all_candidates().await;
|
||||
beacon_node_fallback.update_all_candidates::<E>().await;
|
||||
|
||||
let candidates = beacon_node_fallback.candidates.read().await;
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user