BN Fallback v2 (#2080)
## Issue Addressed - Resolves #1883 ## Proposed Changes This follows on from @blacktemplar's work in #2018. - Allows the VC to connect to multiple BN for redundancy. - Update the simulator so some nodes always need to rely on their fallback. - Adds some extra deprecation warnings for `--eth1-endpoint` - Pass `SignatureBytes` as a reference instead of by value. ## Additional Info NA Co-authored-by: blacktemplar <blacktemplar@a1.net>
This commit is contained in:
parent
f998eff7ce
commit
a62dc65ca4
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -7141,6 +7141,7 @@ dependencies = [
|
|||||||
"eth2_ssz",
|
"eth2_ssz",
|
||||||
"eth2_ssz_derive",
|
"eth2_ssz_derive",
|
||||||
"exit-future",
|
"exit-future",
|
||||||
|
"fallback",
|
||||||
"futures 0.3.8",
|
"futures 0.3.8",
|
||||||
"hex",
|
"hex",
|
||||||
"hyper 0.13.9",
|
"hyper 0.13.9",
|
||||||
|
@ -1643,7 +1643,7 @@ impl ApiTester {
|
|||||||
|
|
||||||
let block = self
|
let block = self
|
||||||
.client
|
.client
|
||||||
.get_validator_blocks::<E>(slot, randao_reveal, None)
|
.get_validator_blocks::<E>(slot, &randao_reveal, None)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.data;
|
.data;
|
||||||
|
@ -170,6 +170,11 @@ pub fn get_config<E: EthSpec>(
|
|||||||
|
|
||||||
// Defines the URL to reach the eth1 node.
|
// Defines the URL to reach the eth1 node.
|
||||||
if let Some(val) = cli_args.value_of("eth1-endpoint") {
|
if let Some(val) = cli_args.value_of("eth1-endpoint") {
|
||||||
|
warn!(
|
||||||
|
log,
|
||||||
|
"The --eth1-endpoint flag is deprecated";
|
||||||
|
"msg" => "please use --eth1-endpoints instead"
|
||||||
|
);
|
||||||
client_config.sync_eth1_chain = true;
|
client_config.sync_eth1_chain = true;
|
||||||
client_config.eth1.endpoints = vec![val.to_string()];
|
client_config.eth1.endpoints = vec![val.to_string()];
|
||||||
} else if let Some(val) = cli_args.value_of("eth1-endpoints") {
|
} else if let Some(val) = cli_args.value_of("eth1-endpoints") {
|
||||||
|
@ -83,6 +83,18 @@ pub struct BeaconNodeHttpClient {
|
|||||||
server: Url,
|
server: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for BeaconNodeHttpClient {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.server.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for BeaconNodeHttpClient {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.server.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl BeaconNodeHttpClient {
|
impl BeaconNodeHttpClient {
|
||||||
pub fn new(server: Url) -> Self {
|
pub fn new(server: Url) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -848,7 +860,7 @@ impl BeaconNodeHttpClient {
|
|||||||
pub async fn get_validator_blocks<T: EthSpec>(
|
pub async fn get_validator_blocks<T: EthSpec>(
|
||||||
&self,
|
&self,
|
||||||
slot: Slot,
|
slot: Slot,
|
||||||
randao_reveal: SignatureBytes,
|
randao_reveal: &SignatureBytes,
|
||||||
graffiti: Option<&Graffiti>,
|
graffiti: Option<&Graffiti>,
|
||||||
) -> Result<GenericResponse<BeaconBlock<T>>, Error> {
|
) -> Result<GenericResponse<BeaconBlock<T>>, Error> {
|
||||||
let mut path = self.eth_path()?;
|
let mut path = self.eth_path()?;
|
||||||
|
@ -15,7 +15,10 @@ pub const ETH1_GENESIS_UPDATE_INTERVAL: Duration = Duration::from_millis(7_000);
|
|||||||
pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches<'_>) -> Result<(), String> {
|
pub fn run<T: EthSpec>(mut env: Environment<T>, matches: &ArgMatches<'_>) -> Result<(), String> {
|
||||||
let endpoints = matches
|
let endpoints = matches
|
||||||
.value_of("eth1-endpoint")
|
.value_of("eth1-endpoint")
|
||||||
.map(|e| vec![String::from(e)])
|
.map(|e| {
|
||||||
|
warn!("The --eth1-endpoint flag is deprecated. Please use --eth1-endpoints instead");
|
||||||
|
vec![String::from(e)]
|
||||||
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
matches
|
matches
|
||||||
.value_of("eth1-endpoints")
|
.value_of("eth1-endpoints")
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::local_network::INVALID_ADDRESS;
|
||||||
use crate::{checks, LocalNetwork, E};
|
use crate::{checks, LocalNetwork, E};
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use eth1::http::Eth1Id;
|
use eth1::http::Eth1Id;
|
||||||
@ -128,8 +129,12 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> {
|
|||||||
/*
|
/*
|
||||||
* One by one, add beacon nodes to the network.
|
* One by one, add beacon nodes to the network.
|
||||||
*/
|
*/
|
||||||
for _ in 0..node_count - 1 {
|
for i in 0..node_count - 1 {
|
||||||
network.add_beacon_node(beacon_config.clone()).await?;
|
let mut config = beacon_config.clone();
|
||||||
|
if i % 2 == 0 {
|
||||||
|
config.eth1.endpoints.insert(0, INVALID_ADDRESS.to_string());
|
||||||
|
}
|
||||||
|
network.add_beacon_node(config).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -137,7 +142,7 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> {
|
|||||||
*/
|
*/
|
||||||
for (i, files) in validator_files.into_iter().enumerate() {
|
for (i, files) in validator_files.into_iter().enumerate() {
|
||||||
network
|
network
|
||||||
.add_validator_client(testing_validator_config(), i, files)
|
.add_validator_client(testing_validator_config(), i, files, i % 2 == 0)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ use std::sync::Arc;
|
|||||||
use types::{Epoch, EthSpec};
|
use types::{Epoch, EthSpec};
|
||||||
|
|
||||||
const BOOTNODE_PORT: u16 = 42424;
|
const BOOTNODE_PORT: u16 = 42424;
|
||||||
|
pub const INVALID_ADDRESS: &str = "http://127.0.0.1:42423";
|
||||||
|
|
||||||
/// Helper struct to reduce `Arc` usage.
|
/// Helper struct to reduce `Arc` usage.
|
||||||
pub struct Inner<E: EthSpec> {
|
pub struct Inner<E: EthSpec> {
|
||||||
@ -118,6 +119,7 @@ impl<E: EthSpec> LocalNetwork<E> {
|
|||||||
mut validator_config: ValidatorConfig,
|
mut validator_config: ValidatorConfig,
|
||||||
beacon_node: usize,
|
beacon_node: usize,
|
||||||
validator_files: ValidatorFiles,
|
validator_files: ValidatorFiles,
|
||||||
|
invalid_first_beacon_node: bool, //to test beacon node fallbacks
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let index = self.validator_clients.read().len();
|
let index = self.validator_clients.read().len();
|
||||||
let context = self.context.service_context(format!("validator_{}", index));
|
let context = self.context.service_context(format!("validator_{}", index));
|
||||||
@ -133,8 +135,12 @@ impl<E: EthSpec> LocalNetwork<E> {
|
|||||||
.expect("Must have http started")
|
.expect("Must have http started")
|
||||||
};
|
};
|
||||||
|
|
||||||
validator_config.beacon_node =
|
let beacon_node = format!("http://{}:{}", socket_addr.ip(), socket_addr.port());
|
||||||
format!("http://{}:{}", socket_addr.ip(), socket_addr.port());
|
validator_config.beacon_nodes = if invalid_first_beacon_node {
|
||||||
|
vec![INVALID_ADDRESS.to_string(), beacon_node]
|
||||||
|
} else {
|
||||||
|
vec![beacon_node]
|
||||||
|
};
|
||||||
let validator_client = LocalValidatorClient::production_with_insecure_keypairs(
|
let validator_client = LocalValidatorClient::production_with_insecure_keypairs(
|
||||||
context,
|
context,
|
||||||
validator_config,
|
validator_config,
|
||||||
|
@ -103,7 +103,7 @@ pub fn run_no_eth1_sim(matches: &ArgMatches) -> Result<(), String> {
|
|||||||
let add_validators_fut = async {
|
let add_validators_fut = async {
|
||||||
for (i, files) in validator_files.into_iter().enumerate() {
|
for (i, files) in validator_files.into_iter().enumerate() {
|
||||||
network
|
network
|
||||||
.add_validator_client(testing_validator_config(), i, files)
|
.add_validator_client(testing_validator_config(), i, files, i % 2 == 0)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ fn syncing_sim(
|
|||||||
* Add a validator client which handles all validators from the genesis state.
|
* Add a validator client which handles all validators from the genesis state.
|
||||||
*/
|
*/
|
||||||
network
|
network
|
||||||
.add_validator_client(testing_validator_config(), 0, validator_files)
|
.add_validator_client(testing_validator_config(), 0, validator_files, true)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Check all syncing strategies one after other.
|
// Check all syncing strategies one after other.
|
||||||
|
@ -63,3 +63,4 @@ rand = "0.7.3"
|
|||||||
scrypt = { version = "0.5.0", default-features = false }
|
scrypt = { version = "0.5.0", default-features = false }
|
||||||
lighthouse_metrics = { path = "../common/lighthouse_metrics" }
|
lighthouse_metrics = { path = "../common/lighthouse_metrics" }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
fallback = { path = "../common/fallback" }
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
use crate::beacon_node_fallback::{BeaconNodeFallback, RequireSynced};
|
||||||
use crate::{
|
use crate::{
|
||||||
duties_service::{DutiesService, DutyAndProof},
|
duties_service::{DutiesService, DutyAndProof},
|
||||||
http_metrics::metrics,
|
http_metrics::metrics,
|
||||||
validator_store::ValidatorStore,
|
validator_store::ValidatorStore,
|
||||||
};
|
};
|
||||||
use environment::RuntimeContext;
|
use environment::RuntimeContext;
|
||||||
use eth2::BeaconNodeHttpClient;
|
|
||||||
use futures::future::FutureExt;
|
use futures::future::FutureExt;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use slog::{crit, error, info, trace};
|
use slog::{crit, error, info, trace};
|
||||||
@ -24,7 +24,7 @@ pub struct AttestationServiceBuilder<T, E: EthSpec> {
|
|||||||
duties_service: Option<DutiesService<T, E>>,
|
duties_service: Option<DutiesService<T, E>>,
|
||||||
validator_store: Option<ValidatorStore<T, E>>,
|
validator_store: Option<ValidatorStore<T, E>>,
|
||||||
slot_clock: Option<T>,
|
slot_clock: Option<T>,
|
||||||
beacon_node: Option<BeaconNodeHttpClient>,
|
beacon_nodes: Option<Arc<BeaconNodeFallback<T, E>>>,
|
||||||
context: Option<RuntimeContext<E>>,
|
context: Option<RuntimeContext<E>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationServiceBuilder<T, E> {
|
|||||||
duties_service: None,
|
duties_service: None,
|
||||||
validator_store: None,
|
validator_store: None,
|
||||||
slot_clock: None,
|
slot_clock: None,
|
||||||
beacon_node: None,
|
beacon_nodes: None,
|
||||||
context: None,
|
context: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,8 +54,8 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationServiceBuilder<T, E> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn beacon_node(mut self, beacon_node: BeaconNodeHttpClient) -> Self {
|
pub fn beacon_nodes(mut self, beacon_nodes: Arc<BeaconNodeFallback<T, E>>) -> Self {
|
||||||
self.beacon_node = Some(beacon_node);
|
self.beacon_nodes = Some(beacon_nodes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,9 +76,9 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationServiceBuilder<T, E> {
|
|||||||
slot_clock: self
|
slot_clock: self
|
||||||
.slot_clock
|
.slot_clock
|
||||||
.ok_or("Cannot build AttestationService without slot_clock")?,
|
.ok_or("Cannot build AttestationService without slot_clock")?,
|
||||||
beacon_node: self
|
beacon_nodes: self
|
||||||
.beacon_node
|
.beacon_nodes
|
||||||
.ok_or("Cannot build AttestationService without beacon_node")?,
|
.ok_or("Cannot build AttestationService without beacon_nodes")?,
|
||||||
context: self
|
context: self
|
||||||
.context
|
.context
|
||||||
.ok_or("Cannot build AttestationService without runtime_context")?,
|
.ok_or("Cannot build AttestationService without runtime_context")?,
|
||||||
@ -92,7 +92,7 @@ pub struct Inner<T, E: EthSpec> {
|
|||||||
duties_service: DutiesService<T, E>,
|
duties_service: DutiesService<T, E>,
|
||||||
validator_store: ValidatorStore<T, E>,
|
validator_store: ValidatorStore<T, E>,
|
||||||
slot_clock: T,
|
slot_clock: T,
|
||||||
beacon_node: BeaconNodeHttpClient,
|
beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
|
||||||
context: RuntimeContext<E>,
|
context: RuntimeContext<E>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,11 +337,16 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
|
|||||||
.epoch(E::slots_per_epoch());
|
.epoch(E::slots_per_epoch());
|
||||||
|
|
||||||
let attestation_data = self
|
let attestation_data = self
|
||||||
.beacon_node
|
.beacon_nodes
|
||||||
.get_validator_attestation_data(slot, committee_index)
|
.first_success(RequireSynced::No, |beacon_node| async move {
|
||||||
|
beacon_node
|
||||||
|
.get_validator_attestation_data(slot, committee_index)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to produce attestation data: {:?}", e))
|
||||||
|
.map(|result| result.data)
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to produce attestation data: {:?}", e))?
|
.map_err(|e| e.to_string())?;
|
||||||
.data;
|
|
||||||
|
|
||||||
let mut attestations = Vec::with_capacity(validator_duties.len());
|
let mut attestations = Vec::with_capacity(validator_duties.len());
|
||||||
|
|
||||||
@ -408,9 +413,14 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let attestations_slice = attestations.as_slice();
|
||||||
match self
|
match self
|
||||||
.beacon_node
|
.beacon_nodes
|
||||||
.post_beacon_pool_attestations(attestations.as_slice())
|
.first_success(RequireSynced::No, |beacon_node| async move {
|
||||||
|
beacon_node
|
||||||
|
.post_beacon_pool_attestations(attestations_slice)
|
||||||
|
.await
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => info!(
|
Ok(()) => info!(
|
||||||
@ -425,7 +435,7 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
|
|||||||
Err(e) => error!(
|
Err(e) => error!(
|
||||||
log,
|
log,
|
||||||
"Unable to publish attestations";
|
"Unable to publish attestations";
|
||||||
"error" => ?e,
|
"error" => %e,
|
||||||
"committee_index" => attestation_data.index,
|
"committee_index" => attestation_data.index,
|
||||||
"slot" => slot.as_u64(),
|
"slot" => slot.as_u64(),
|
||||||
"type" => "unaggregated",
|
"type" => "unaggregated",
|
||||||
@ -455,16 +465,22 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
|
|||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let log = self.context.log();
|
let log = self.context.log();
|
||||||
|
|
||||||
|
let attestation_data_ref = &attestation_data;
|
||||||
let aggregated_attestation = self
|
let aggregated_attestation = self
|
||||||
.beacon_node
|
.beacon_nodes
|
||||||
.get_validator_aggregate_attestation(
|
.first_success(RequireSynced::No, |beacon_node| async move {
|
||||||
attestation_data.slot,
|
beacon_node
|
||||||
attestation_data.tree_hash_root(),
|
.get_validator_aggregate_attestation(
|
||||||
)
|
attestation_data_ref.slot,
|
||||||
|
attestation_data_ref.tree_hash_root(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to produce an aggregate attestation: {:?}", e))?
|
||||||
|
.ok_or_else(|| format!("No aggregate available for {:?}", attestation_data_ref))
|
||||||
|
.map(|result| result.data)
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to produce an aggregate attestation: {:?}", e))?
|
.map_err(|e| e.to_string())?;
|
||||||
.ok_or_else(|| format!("No aggregate available for {:?}", attestation_data))?
|
|
||||||
.data;
|
|
||||||
|
|
||||||
let mut signed_aggregate_and_proofs = Vec::new();
|
let mut signed_aggregate_and_proofs = Vec::new();
|
||||||
|
|
||||||
@ -507,9 +523,14 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !signed_aggregate_and_proofs.is_empty() {
|
if !signed_aggregate_and_proofs.is_empty() {
|
||||||
|
let signed_aggregate_and_proofs_slice = signed_aggregate_and_proofs.as_slice();
|
||||||
match self
|
match self
|
||||||
.beacon_node
|
.beacon_nodes
|
||||||
.post_validator_aggregate_and_proof(signed_aggregate_and_proofs.as_slice())
|
.first_success(RequireSynced::No, |beacon_node| async move {
|
||||||
|
beacon_node
|
||||||
|
.post_validator_aggregate_and_proof(signed_aggregate_and_proofs_slice)
|
||||||
|
.await
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
@ -533,7 +554,7 @@ impl<T: SlotClock + 'static, E: EthSpec> AttestationService<T, E> {
|
|||||||
crit!(
|
crit!(
|
||||||
log,
|
log,
|
||||||
"Failed to publish attestation";
|
"Failed to publish attestation";
|
||||||
"error" => e.to_string(),
|
"error" => %e,
|
||||||
"committee_index" => attestation.data.index,
|
"committee_index" => attestation.data.index,
|
||||||
"slot" => attestation.data.slot.as_u64(),
|
"slot" => attestation.data.slot.as_u64(),
|
||||||
"type" => "aggregated",
|
"type" => "aggregated",
|
||||||
|
463
validator_client/src/beacon_node_fallback.rs
Normal file
463
validator_client/src/beacon_node_fallback.rs
Normal file
@ -0,0 +1,463 @@
|
|||||||
|
//! Allows for a list of `BeaconNodeHttpClient` to appear as a single entity which will exhibits
|
||||||
|
//! "fallback" behaviour; it will try a request on all of the nodes until one or none of them
|
||||||
|
//! succeed.
|
||||||
|
|
||||||
|
use crate::check_synced::check_synced;
|
||||||
|
use crate::http_metrics::metrics::{inc_counter_vec, ENDPOINT_ERRORS, ENDPOINT_REQUESTS};
|
||||||
|
use environment::RuntimeContext;
|
||||||
|
use eth2::BeaconNodeHttpClient;
|
||||||
|
use futures::future;
|
||||||
|
use slog::{error, info, warn, Logger};
|
||||||
|
use slot_clock::SlotClock;
|
||||||
|
use std::fmt;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::{sync::RwLock, time::sleep};
|
||||||
|
use types::{ChainSpec, EthSpec};
|
||||||
|
|
||||||
|
/// The number of seconds *prior* to slot start that we will try and update the state of fallback
|
||||||
|
/// nodes.
|
||||||
|
///
|
||||||
|
/// Ideally this should be somewhere between 2/3rds through the slot and the end of it. If we set it
|
||||||
|
/// too early, we risk switching nodes between the time of publishing an attestation and publishing
|
||||||
|
/// an aggregate; this may result in a missed aggregation. If we set this time too late, we risk not
|
||||||
|
/// having the correct nodes up and running prior to the start of the slot.
|
||||||
|
const SLOT_LOOKAHEAD: Duration = Duration::from_secs(1);
|
||||||
|
|
||||||
|
/// Starts a service that will routinely try and update the status of the provided `beacon_nodes`.
|
||||||
|
///
|
||||||
|
/// 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>>,
|
||||||
|
) -> 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_unready_candidates().await;
|
||||||
|
|
||||||
|
let sleep_time = beacon_nodes
|
||||||
|
.slot_clock
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|slot_clock| {
|
||||||
|
let slot = slot_clock.now()?;
|
||||||
|
let till_next_slot = slot_clock.duration_to_slot(slot + 1)?;
|
||||||
|
|
||||||
|
till_next_slot.checked_sub(SLOT_LOOKAHEAD)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| Duration::from_secs(1));
|
||||||
|
|
||||||
|
sleep(sleep_time).await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
executor.spawn(future, "fallback");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates if a beacon node must be synced before some action is performed on it.
|
||||||
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
|
pub enum RequireSynced {
|
||||||
|
Yes,
|
||||||
|
No,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<bool> for RequireSynced {
|
||||||
|
fn eq(&self, other: &bool) -> bool {
|
||||||
|
if *other {
|
||||||
|
*self == RequireSynced::Yes
|
||||||
|
} else {
|
||||||
|
*self == RequireSynced::No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error<E> {
|
||||||
|
/// The node was unavailable and we didn't attempt to contact it.
|
||||||
|
Unavailable(CandidateError),
|
||||||
|
/// We attempted to contact the node but it failed.
|
||||||
|
RequestFailed(E),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E> Error<E> {
|
||||||
|
pub fn request_failure(&self) -> Option<&E> {
|
||||||
|
match self {
|
||||||
|
Error::RequestFailed(e) => Some(e),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of errors encountered whilst attempting to perform a query.
|
||||||
|
pub struct AllErrored<E>(pub Vec<(String, Error<E>)>);
|
||||||
|
|
||||||
|
impl<E: Debug> fmt::Display for AllErrored<E> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "All endpoints failed")?;
|
||||||
|
for (i, (id, error)) in self.0.iter().enumerate() {
|
||||||
|
let comma = if i + 1 < self.0.len() { "," } else { "" };
|
||||||
|
|
||||||
|
write!(f, " {} => {:?}{}", id, error, comma)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reasons why a candidate might not be ready.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum CandidateError {
|
||||||
|
Uninitialized,
|
||||||
|
Offline,
|
||||||
|
Incompatible,
|
||||||
|
NotSynced,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a `BeaconNodeHttpClient` inside a `BeaconNodeFallback` that may or may not be used
|
||||||
|
/// for a query.
|
||||||
|
pub struct CandidateBeaconNode<E> {
|
||||||
|
beacon_node: BeaconNodeHttpClient,
|
||||||
|
status: RwLock<Result<(), CandidateError>>,
|
||||||
|
_phantom: PhantomData<E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: EthSpec> CandidateBeaconNode<E> {
|
||||||
|
/// Instantiate a new node.
|
||||||
|
pub fn new(beacon_node: BeaconNodeHttpClient) -> Self {
|
||||||
|
Self {
|
||||||
|
beacon_node,
|
||||||
|
status: RwLock::new(Err(CandidateError::Uninitialized)),
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the status of `self`.
|
||||||
|
///
|
||||||
|
/// If `RequiredSynced::No`, any `NotSynced` node will be ignored and mapped to `Ok(())`.
|
||||||
|
pub async fn status(&self, synced: RequireSynced) -> Result<(), CandidateError> {
|
||||||
|
match *self.status.read().await {
|
||||||
|
Err(CandidateError::NotSynced) if synced == false => Ok(()),
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicate that `self` is offline.
|
||||||
|
pub async fn set_offline(&self) {
|
||||||
|
*self.status.write().await = Err(CandidateError::Offline)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform some queries against the node to determine if it is a good candidate, updating
|
||||||
|
/// `self.status` and returning that result.
|
||||||
|
pub async fn refresh_status<T: SlotClock>(
|
||||||
|
&self,
|
||||||
|
slot_clock: Option<&T>,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
log: &Logger,
|
||||||
|
) -> Result<(), CandidateError> {
|
||||||
|
let mut status = self.status.write().await;
|
||||||
|
|
||||||
|
if let Err(e) = self.is_online(log).await {
|
||||||
|
*status = Err(e);
|
||||||
|
} else if let Err(e) = self.is_compatible(spec, log).await {
|
||||||
|
*status = Err(e);
|
||||||
|
} else if let Err(e) = self.is_synced(slot_clock, log).await {
|
||||||
|
*status = Err(e);
|
||||||
|
} else {
|
||||||
|
*status = Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
*status
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the node is reachable.
|
||||||
|
async fn is_online(&self, log: &Logger) -> Result<(), CandidateError> {
|
||||||
|
let result = self
|
||||||
|
.beacon_node
|
||||||
|
.get_node_version()
|
||||||
|
.await
|
||||||
|
.map(|body| body.data.version);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(version) => {
|
||||||
|
info!(
|
||||||
|
log,
|
||||||
|
"Connected to beacon node";
|
||||||
|
"version" => version,
|
||||||
|
"endpoint" => %self.beacon_node,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(
|
||||||
|
log,
|
||||||
|
"Offline beacon node";
|
||||||
|
"error" => %e,
|
||||||
|
"endpoint" => %self.beacon_node,
|
||||||
|
);
|
||||||
|
Err(CandidateError::Offline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the node has the correct specification.
|
||||||
|
async fn is_compatible(&self, spec: &ChainSpec, log: &Logger) -> Result<(), CandidateError> {
|
||||||
|
let yaml_config = self
|
||||||
|
.beacon_node
|
||||||
|
.get_config_spec()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(
|
||||||
|
log,
|
||||||
|
"Unable to read spec from beacon node";
|
||||||
|
"error" => %e,
|
||||||
|
"endpoint" => %self.beacon_node,
|
||||||
|
);
|
||||||
|
CandidateError::Offline
|
||||||
|
})?
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let beacon_node_spec = yaml_config
|
||||||
|
.apply_to_chain_spec::<E>(&E::default_spec())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
error!(
|
||||||
|
log,
|
||||||
|
"The minimal/mainnet spec type of the beacon node does not match the validator \
|
||||||
|
client. See the --network command.";
|
||||||
|
"endpoint" => %self.beacon_node,
|
||||||
|
);
|
||||||
|
CandidateError::Incompatible
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if *spec == beacon_node_spec {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
log,
|
||||||
|
"The beacon node is using a different Eth2 specification to this validator client. \
|
||||||
|
See the --network command.";
|
||||||
|
"endpoint" => %self.beacon_node,
|
||||||
|
);
|
||||||
|
Err(CandidateError::Incompatible)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the beacon node is synced.
|
||||||
|
async fn is_synced<T: SlotClock>(
|
||||||
|
&self,
|
||||||
|
slot_clock: Option<&T>,
|
||||||
|
log: &Logger,
|
||||||
|
) -> Result<(), CandidateError> {
|
||||||
|
if let Some(slot_clock) = slot_clock {
|
||||||
|
match check_synced(&self.beacon_node, slot_clock, Some(log)).await {
|
||||||
|
r @ Err(CandidateError::NotSynced) => {
|
||||||
|
warn!(
|
||||||
|
log,
|
||||||
|
"Beacon node is not synced";
|
||||||
|
"endpoint" => %self.beacon_node,
|
||||||
|
);
|
||||||
|
r
|
||||||
|
}
|
||||||
|
result => result,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Skip this check if we don't supply a slot clock.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A collection of `CandidateBeaconNode` that can be used to perform requests with "fallback"
|
||||||
|
/// behaviour, where the failure of one candidate results in the next candidate receiving an
|
||||||
|
/// identical query.
|
||||||
|
pub struct BeaconNodeFallback<T, E> {
|
||||||
|
candidates: Vec<CandidateBeaconNode<E>>,
|
||||||
|
slot_clock: Option<T>,
|
||||||
|
spec: ChainSpec,
|
||||||
|
log: Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SlotClock, E: EthSpec> BeaconNodeFallback<T, E> {
|
||||||
|
pub fn new(candidates: Vec<CandidateBeaconNode<E>>, spec: ChainSpec, log: Logger) -> Self {
|
||||||
|
Self {
|
||||||
|
candidates,
|
||||||
|
slot_clock: None,
|
||||||
|
spec,
|
||||||
|
log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to update the slot clock post-instantiation.
|
||||||
|
///
|
||||||
|
/// This is the result of a chicken-and-egg issue where `Self` needs a slot clock for some
|
||||||
|
/// operations, but `Self` is required to obtain the slot clock since we need the genesis time
|
||||||
|
/// from a beacon node.
|
||||||
|
pub fn set_slot_clock(&mut self, slot_clock: T) {
|
||||||
|
self.slot_clock = Some(slot_clock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The count of candidates, regardless of their state.
|
||||||
|
pub async fn num_total(&self) -> usize {
|
||||||
|
self.candidates.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The count of synced and ready candidates.
|
||||||
|
pub async fn num_synced(&self) -> usize {
|
||||||
|
let mut n = 0;
|
||||||
|
for candidate in &self.candidates {
|
||||||
|
if candidate.status(RequireSynced::Yes).await.is_ok() {
|
||||||
|
n += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The count of candidates that are online and compatible, but not necessarily synced.
|
||||||
|
pub async fn num_available(&self) -> usize {
|
||||||
|
let mut n = 0;
|
||||||
|
for candidate in &self.candidates {
|
||||||
|
if candidate.status(RequireSynced::No).await.is_ok() {
|
||||||
|
n += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loop through any `self.candidates` that we don't think are online, compatible or synced and
|
||||||
|
/// poll them to see if their status has changed.
|
||||||
|
///
|
||||||
|
/// We do not poll nodes that are synced to avoid sending additional requests when everything is
|
||||||
|
/// going smoothly.
|
||||||
|
pub async fn update_unready_candidates(&self) {
|
||||||
|
let mut futures = Vec::new();
|
||||||
|
for candidate in &self.candidates {
|
||||||
|
// There is a potential race condition between having the read lock and the write
|
||||||
|
// lock. The worst case of this race is running `try_become_ready` twice, which is
|
||||||
|
// acceptable.
|
||||||
|
//
|
||||||
|
// Note: `RequireSynced` is always set to false here. This forces us to recheck the sync
|
||||||
|
// status of nodes that were previously not-synced.
|
||||||
|
if candidate.status(RequireSynced::Yes).await.is_err() {
|
||||||
|
// There exists a race-condition that could result in `refresh_status` being called
|
||||||
|
// when the status does not require refreshing anymore. This deemed is an
|
||||||
|
// acceptable inefficiency.
|
||||||
|
futures.push(candidate.refresh_status(
|
||||||
|
self.slot_clock.as_ref(),
|
||||||
|
&self.spec,
|
||||||
|
&self.log,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//run all updates concurrently and ignore results
|
||||||
|
let _ = future::join_all(futures).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run `func` against each candidate in `self`, returning immediately if a result is found.
|
||||||
|
/// Otherwise, return all the errors encountered along the way.
|
||||||
|
///
|
||||||
|
/// First this function will try all nodes with a suitable status. If no candidates are suitable
|
||||||
|
/// or all the requests fail, it will try updating the status of all unsuitable nodes and
|
||||||
|
/// re-running `func` again.
|
||||||
|
pub async fn first_success<'a, F, O, Err, R>(
|
||||||
|
&'a self,
|
||||||
|
require_synced: RequireSynced,
|
||||||
|
func: F,
|
||||||
|
) -> Result<O, AllErrored<Err>>
|
||||||
|
where
|
||||||
|
F: Fn(&'a BeaconNodeHttpClient) -> R,
|
||||||
|
R: Future<Output = Result<O, Err>>,
|
||||||
|
{
|
||||||
|
let mut errors = vec![];
|
||||||
|
let mut to_retry = vec![];
|
||||||
|
let mut retry_unsynced = vec![];
|
||||||
|
|
||||||
|
// Run `func` using a `candidate`, returning the value or capturing errors.
|
||||||
|
//
|
||||||
|
// We use a macro instead of a closure here since it is not trivial to move `func` into a
|
||||||
|
// closure.
|
||||||
|
macro_rules! try_func {
|
||||||
|
($candidate: ident) => {{
|
||||||
|
inc_counter_vec(&ENDPOINT_REQUESTS, &[$candidate.beacon_node.as_ref()]);
|
||||||
|
|
||||||
|
// There exists a race condition where `func` may be called when the candidate is
|
||||||
|
// actually not ready. We deem this an acceptable inefficiency.
|
||||||
|
match func(&$candidate.beacon_node).await {
|
||||||
|
Ok(val) => return Ok(val),
|
||||||
|
Err(e) => {
|
||||||
|
// If we have an error on this function, make the client as not-ready.
|
||||||
|
//
|
||||||
|
// There exists a race condition where the candidate may have been marked
|
||||||
|
// as ready between the `func` call and now. We deem this an acceptable
|
||||||
|
// inefficiency.
|
||||||
|
$candidate.set_offline().await;
|
||||||
|
errors.push(($candidate.beacon_node.to_string(), Error::RequestFailed(e)));
|
||||||
|
inc_counter_vec(&ENDPOINT_ERRORS, &[$candidate.beacon_node.as_ref()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// First pass: try `func` on all synced and ready candidates.
|
||||||
|
//
|
||||||
|
// This ensures that we always choose a synced node if it is available.
|
||||||
|
for candidate in &self.candidates {
|
||||||
|
match candidate.status(RequireSynced::Yes).await {
|
||||||
|
Err(e @ CandidateError::NotSynced) if require_synced == false => {
|
||||||
|
// This client is unsynced we will try it after trying all synced clients
|
||||||
|
retry_unsynced.push(candidate);
|
||||||
|
errors.push((candidate.beacon_node.to_string(), Error::Unavailable(e)));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// This client was not ready on the first pass, we might try it again later.
|
||||||
|
to_retry.push(candidate);
|
||||||
|
errors.push((candidate.beacon_node.to_string(), Error::Unavailable(e)));
|
||||||
|
}
|
||||||
|
_ => try_func!(candidate),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: try `func` on ready unsynced candidates. This only runs if we permit
|
||||||
|
// unsynced candidates.
|
||||||
|
//
|
||||||
|
// Due to async race-conditions, it is possible that we will send a request to a candidate
|
||||||
|
// that has been set to an offline/unready status. This is acceptable.
|
||||||
|
if require_synced == false {
|
||||||
|
for candidate in retry_unsynced {
|
||||||
|
try_func!(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third pass: try again, attempting to make non-ready clients become ready.
|
||||||
|
for candidate in to_retry {
|
||||||
|
// If the candidate hasn't luckily transferred into the correct state in the meantime,
|
||||||
|
// force an update of the state.
|
||||||
|
let new_status = match candidate.status(require_synced).await {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(_) => {
|
||||||
|
candidate
|
||||||
|
.refresh_status(self.slot_clock.as_ref(), &self.spec, &self.log)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match new_status {
|
||||||
|
Ok(()) => try_func!(candidate),
|
||||||
|
Err(CandidateError::NotSynced) if require_synced == false => try_func!(candidate),
|
||||||
|
Err(e) => {
|
||||||
|
errors.push((candidate.beacon_node.to_string(), Error::Unavailable(e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There were no candidates already ready and we were unable to make any of them ready.
|
||||||
|
Err(AllErrored(errors))
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
|
use crate::beacon_node_fallback::{BeaconNodeFallback, RequireSynced};
|
||||||
use crate::{http_metrics::metrics, validator_store::ValidatorStore};
|
use crate::{http_metrics::metrics, validator_store::ValidatorStore};
|
||||||
use environment::RuntimeContext;
|
use environment::RuntimeContext;
|
||||||
use eth2::{types::Graffiti, BeaconNodeHttpClient};
|
use eth2::types::Graffiti;
|
||||||
use futures::channel::mpsc::Receiver;
|
use futures::channel::mpsc::Receiver;
|
||||||
use futures::{StreamExt, TryFutureExt};
|
use futures::{StreamExt, TryFutureExt};
|
||||||
use slog::{crit, debug, error, info, trace, warn};
|
use slog::{crit, debug, error, info, trace, warn};
|
||||||
@ -13,7 +14,7 @@ use types::{EthSpec, PublicKey, Slot};
|
|||||||
pub struct BlockServiceBuilder<T, E: EthSpec> {
|
pub struct BlockServiceBuilder<T, E: EthSpec> {
|
||||||
validator_store: Option<ValidatorStore<T, E>>,
|
validator_store: Option<ValidatorStore<T, E>>,
|
||||||
slot_clock: Option<Arc<T>>,
|
slot_clock: Option<Arc<T>>,
|
||||||
beacon_node: Option<BeaconNodeHttpClient>,
|
beacon_nodes: Option<Arc<BeaconNodeFallback<T, E>>>,
|
||||||
context: Option<RuntimeContext<E>>,
|
context: Option<RuntimeContext<E>>,
|
||||||
graffiti: Option<Graffiti>,
|
graffiti: Option<Graffiti>,
|
||||||
}
|
}
|
||||||
@ -23,7 +24,7 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
|
|||||||
Self {
|
Self {
|
||||||
validator_store: None,
|
validator_store: None,
|
||||||
slot_clock: None,
|
slot_clock: None,
|
||||||
beacon_node: None,
|
beacon_nodes: None,
|
||||||
context: None,
|
context: None,
|
||||||
graffiti: None,
|
graffiti: None,
|
||||||
}
|
}
|
||||||
@ -39,8 +40,8 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn beacon_node(mut self, beacon_node: BeaconNodeHttpClient) -> Self {
|
pub fn beacon_nodes(mut self, beacon_nodes: Arc<BeaconNodeFallback<T, E>>) -> Self {
|
||||||
self.beacon_node = Some(beacon_node);
|
self.beacon_nodes = Some(beacon_nodes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +64,8 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
|
|||||||
slot_clock: self
|
slot_clock: self
|
||||||
.slot_clock
|
.slot_clock
|
||||||
.ok_or("Cannot build BlockService without slot_clock")?,
|
.ok_or("Cannot build BlockService without slot_clock")?,
|
||||||
beacon_node: self
|
beacon_nodes: self
|
||||||
.beacon_node
|
.beacon_nodes
|
||||||
.ok_or("Cannot build BlockService without beacon_node")?,
|
.ok_or("Cannot build BlockService without beacon_node")?,
|
||||||
context: self
|
context: self
|
||||||
.context
|
.context
|
||||||
@ -79,7 +80,7 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
|
|||||||
pub struct Inner<T, E: EthSpec> {
|
pub struct Inner<T, E: EthSpec> {
|
||||||
validator_store: ValidatorStore<T, E>,
|
validator_store: ValidatorStore<T, E>,
|
||||||
slot_clock: Arc<T>,
|
slot_clock: Arc<T>,
|
||||||
beacon_node: BeaconNodeHttpClient,
|
beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
|
||||||
context: RuntimeContext<E>,
|
context: RuntimeContext<E>,
|
||||||
graffiti: Option<Graffiti>,
|
graffiti: Option<Graffiti>,
|
||||||
}
|
}
|
||||||
@ -222,24 +223,37 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
|
|||||||
let randao_reveal = self
|
let randao_reveal = self
|
||||||
.validator_store
|
.validator_store
|
||||||
.randao_reveal(&validator_pubkey, slot.epoch(E::slots_per_epoch()))
|
.randao_reveal(&validator_pubkey, slot.epoch(E::slots_per_epoch()))
|
||||||
.ok_or("Unable to produce randao reveal")?;
|
.ok_or("Unable to produce randao reveal")?
|
||||||
|
.into();
|
||||||
let block = self
|
|
||||||
.beacon_node
|
|
||||||
.get_validator_blocks(slot, randao_reveal.into(), self.graffiti.as_ref())
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Error from beacon node when producing block: {:?}", e))?
|
|
||||||
.data;
|
|
||||||
|
|
||||||
|
let randao_reveal_ref = &randao_reveal;
|
||||||
|
let self_ref = &self;
|
||||||
|
let validator_pubkey_ref = &validator_pubkey;
|
||||||
let signed_block = self
|
let signed_block = self
|
||||||
.validator_store
|
.beacon_nodes
|
||||||
.sign_block(&validator_pubkey, block, current_slot)
|
.first_success(RequireSynced::No, |beacon_node| async move {
|
||||||
.ok_or("Unable to sign block")?;
|
let block = beacon_node
|
||||||
|
.get_validator_blocks(slot, randao_reveal_ref, self_ref.graffiti.as_ref())
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Error from beacon node when producing block: {:?}", e))?
|
||||||
|
.data;
|
||||||
|
|
||||||
self.beacon_node
|
let signed_block = self_ref
|
||||||
.post_beacon_blocks(&signed_block)
|
.validator_store
|
||||||
|
.sign_block(validator_pubkey_ref, block, current_slot)
|
||||||
|
.ok_or("Unable to sign block")?;
|
||||||
|
|
||||||
|
beacon_node
|
||||||
|
.post_beacon_blocks(&signed_block)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
format!("Error from beacon node when publishing block: {:?}", e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok::<_, String>(signed_block)
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Error from beacon node when publishing block: {:?}", e))?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
log,
|
log,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::beacon_node_fallback::CandidateError;
|
||||||
use eth2::BeaconNodeHttpClient;
|
use eth2::BeaconNodeHttpClient;
|
||||||
use slog::{debug, error, warn, Logger};
|
use slog::{debug, error, warn, Logger};
|
||||||
use slot_clock::SlotClock;
|
use slot_clock::SlotClock;
|
||||||
@ -5,33 +6,33 @@ use slot_clock::SlotClock;
|
|||||||
/// A distance in slots.
|
/// A distance in slots.
|
||||||
const SYNC_TOLERANCE: u64 = 4;
|
const SYNC_TOLERANCE: u64 = 4;
|
||||||
|
|
||||||
/// Returns `true` if the beacon node is synced and ready for action.
|
/// Returns
|
||||||
///
|
///
|
||||||
/// Returns `false` if:
|
/// `Ok(())` if the beacon node is synced and ready for action,
|
||||||
///
|
/// `Err(CandidateError::Offline)` if the beacon node is unreachable,
|
||||||
/// - The beacon node is unreachable.
|
/// `Err(CandidateError::NotSynced)` if the beacon node indicates that it is syncing **AND**
|
||||||
/// - The beacon node indicates that it is syncing **AND** it is more than `SYNC_TOLERANCE` behind
|
/// it is more than `SYNC_TOLERANCE` behind the highest
|
||||||
/// the highest known slot.
|
/// known slot.
|
||||||
///
|
///
|
||||||
/// The second condition means the even if the beacon node thinks that it's syncing, we'll still
|
/// The second condition means the even if the beacon node thinks that it's syncing, we'll still
|
||||||
/// try to use it if it's close enough to the head.
|
/// try to use it if it's close enough to the head.
|
||||||
pub async fn is_synced<T: SlotClock>(
|
pub async fn check_synced<T: SlotClock>(
|
||||||
beacon_node: &BeaconNodeHttpClient,
|
beacon_node: &BeaconNodeHttpClient,
|
||||||
slot_clock: &T,
|
slot_clock: &T,
|
||||||
log_opt: Option<&Logger>,
|
log_opt: Option<&Logger>,
|
||||||
) -> bool {
|
) -> Result<(), CandidateError> {
|
||||||
let resp = match beacon_node.get_node_syncing().await {
|
let resp = match beacon_node.get_node_syncing().await {
|
||||||
Ok(resp) => resp,
|
Ok(resp) => resp,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Some(log) = log_opt {
|
if let Some(log) = log_opt {
|
||||||
error!(
|
warn!(
|
||||||
log,
|
log,
|
||||||
"Unable connect to beacon node";
|
"Unable connect to beacon node";
|
||||||
"error" => e.to_string()
|
"error" => %e
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return Err(CandidateError::Offline);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,9 +49,9 @@ pub async fn is_synced<T: SlotClock>(
|
|||||||
warn!(
|
warn!(
|
||||||
log,
|
log,
|
||||||
"Beacon node is syncing";
|
"Beacon node is syncing";
|
||||||
"msg" => "not receiving new duties",
|
|
||||||
"sync_distance" => resp.data.sync_distance.as_u64(),
|
"sync_distance" => resp.data.sync_distance.as_u64(),
|
||||||
"head_slot" => resp.data.head_slot.as_u64(),
|
"head_slot" => resp.data.head_slot.as_u64(),
|
||||||
|
"endpoint" => %beacon_node,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,10 +64,15 @@ pub async fn is_synced<T: SlotClock>(
|
|||||||
"msg" => "check the system time on this host and the beacon node",
|
"msg" => "check the system time on this host and the beacon node",
|
||||||
"beacon_node_slot" => remote_slot,
|
"beacon_node_slot" => remote_slot,
|
||||||
"local_slot" => local_slot,
|
"local_slot" => local_slot,
|
||||||
|
"endpoint" => %beacon_node,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is_synced
|
if is_synced {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(CandidateError::NotSynced)
|
||||||
|
}
|
||||||
}
|
}
|
@ -9,22 +9,31 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
"When connected to a beacon node, performs the duties of a staked \
|
"When connected to a beacon node, performs the duties of a staked \
|
||||||
validator (e.g., proposing blocks and attestations).",
|
validator (e.g., proposing blocks and attestations).",
|
||||||
)
|
)
|
||||||
|
// This argument is deprecated, use `--beacon-nodes` instead.
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("beacon-node")
|
Arg::with_name("beacon-node")
|
||||||
.long("beacon-node")
|
.long("beacon-node")
|
||||||
.value_name("NETWORK_ADDRESS")
|
.value_name("NETWORK_ADDRESS")
|
||||||
.help("Address to a beacon node HTTP API")
|
.help("Deprecated. Use --beacon-nodes.")
|
||||||
|
.takes_value(true)
|
||||||
|
.conflicts_with("beacon-nodes"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("beacon-nodes")
|
||||||
|
.long("beacon-nodes")
|
||||||
|
.value_name("NETWORK_ADDRESSES")
|
||||||
|
.help("Comma-separated addresses to one or more beacon node HTTP APIs")
|
||||||
.default_value(&DEFAULT_BEACON_NODE)
|
.default_value(&DEFAULT_BEACON_NODE)
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
// This argument is deprecated, use `--beacon-node` instead.
|
// This argument is deprecated, use `--beacon-nodes` instead.
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("server")
|
Arg::with_name("server")
|
||||||
.long("server")
|
.long("server")
|
||||||
.value_name("NETWORK_ADDRESS")
|
.value_name("NETWORK_ADDRESS")
|
||||||
.help("Deprecated. Use --beacon-node.")
|
.help("Deprecated. Use --beacon-nodes.")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.conflicts_with("beacon-node"),
|
.conflicts_with_all(&["beacon-node", "beacon-nodes"]),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("validators-dir")
|
Arg::with_name("validators-dir")
|
||||||
|
@ -22,10 +22,10 @@ pub struct Config {
|
|||||||
pub validator_dir: PathBuf,
|
pub validator_dir: PathBuf,
|
||||||
/// The directory containing the passwords to unlock validator keystores.
|
/// The directory containing the passwords to unlock validator keystores.
|
||||||
pub secrets_dir: PathBuf,
|
pub secrets_dir: PathBuf,
|
||||||
/// The http endpoint of the beacon node API.
|
/// The http endpoints of the beacon node APIs.
|
||||||
///
|
///
|
||||||
/// Should be similar to `http://localhost:8080`
|
/// Should be similar to `["http://localhost:8080"]`
|
||||||
pub beacon_node: String,
|
pub beacon_nodes: Vec<String>,
|
||||||
/// If true, the validator client will still poll for duties and produce blocks even if the
|
/// If true, the validator client will still poll for duties and produce blocks even if the
|
||||||
/// beacon node is not synced at startup.
|
/// beacon node is not synced at startup.
|
||||||
pub allow_unsynced_beacon_node: bool,
|
pub allow_unsynced_beacon_node: bool,
|
||||||
@ -55,7 +55,7 @@ impl Default for Config {
|
|||||||
Self {
|
Self {
|
||||||
validator_dir,
|
validator_dir,
|
||||||
secrets_dir,
|
secrets_dir,
|
||||||
beacon_node: DEFAULT_BEACON_NODE.to_string(),
|
beacon_nodes: vec![DEFAULT_BEACON_NODE.to_string()],
|
||||||
allow_unsynced_beacon_node: false,
|
allow_unsynced_beacon_node: false,
|
||||||
disable_auto_discover: false,
|
disable_auto_discover: false,
|
||||||
init_slashing_protection: false,
|
init_slashing_protection: false,
|
||||||
@ -106,18 +106,26 @@ impl Config {
|
|||||||
.map_err(|e| format!("Failed to create {:?}: {:?}", config.validator_dir, e))?;
|
.map_err(|e| format!("Failed to create {:?}: {:?}", config.validator_dir, e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(beacon_node) = parse_optional(cli_args, "beacon-node")? {
|
if let Some(beacon_nodes) = parse_optional::<String>(cli_args, "beacon-nodes")? {
|
||||||
config.beacon_node = beacon_node;
|
config.beacon_nodes = beacon_nodes.as_str().split(',').map(String::from).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// To be deprecated.
|
// To be deprecated.
|
||||||
if let Some(server) = parse_optional(cli_args, "server")? {
|
else if let Some(beacon_node) = parse_optional(cli_args, "beacon-node")? {
|
||||||
|
warn!(
|
||||||
|
log,
|
||||||
|
"The --beacon-node flag is deprecated";
|
||||||
|
"msg" => "please use --beacon-nodes instead"
|
||||||
|
);
|
||||||
|
config.beacon_nodes = vec![beacon_node];
|
||||||
|
}
|
||||||
|
// To be deprecated.
|
||||||
|
else if let Some(server) = parse_optional(cli_args, "server")? {
|
||||||
warn!(
|
warn!(
|
||||||
log,
|
log,
|
||||||
"The --server flag is deprecated";
|
"The --server flag is deprecated";
|
||||||
"msg" => "please use --beacon-node instead"
|
"msg" => "please use --beacon-nodes instead"
|
||||||
);
|
);
|
||||||
config.beacon_node = server;
|
config.beacon_nodes = vec![server];
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli_args.is_present("delete-lockfiles") {
|
if cli_args.is_present("delete-lockfiles") {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
use crate::beacon_node_fallback::{BeaconNodeFallback, RequireSynced};
|
||||||
use crate::{
|
use crate::{
|
||||||
block_service::BlockServiceNotification, http_metrics::metrics, is_synced::is_synced,
|
block_service::BlockServiceNotification, http_metrics::metrics, validator_duty::ValidatorDuty,
|
||||||
validator_duty::ValidatorDuty, validator_store::ValidatorStore,
|
validator_store::ValidatorStore,
|
||||||
};
|
};
|
||||||
use environment::RuntimeContext;
|
use environment::RuntimeContext;
|
||||||
use eth2::BeaconNodeHttpClient;
|
|
||||||
use futures::channel::mpsc::Sender;
|
use futures::channel::mpsc::Sender;
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
@ -324,12 +324,12 @@ impl DutiesStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DutiesServiceBuilder<T, E: EthSpec> {
|
pub struct DutiesServiceBuilder<T: SlotClock + 'static, E: EthSpec> {
|
||||||
validator_store: Option<ValidatorStore<T, E>>,
|
validator_store: Option<ValidatorStore<T, E>>,
|
||||||
slot_clock: Option<T>,
|
slot_clock: Option<T>,
|
||||||
beacon_node: Option<BeaconNodeHttpClient>,
|
beacon_nodes: Option<Arc<BeaconNodeFallback<T, E>>>,
|
||||||
context: Option<RuntimeContext<E>>,
|
|
||||||
allow_unsynced_beacon_node: bool,
|
allow_unsynced_beacon_node: bool,
|
||||||
|
context: Option<RuntimeContext<E>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: SlotClock + 'static, E: EthSpec> DutiesServiceBuilder<T, E> {
|
impl<T: SlotClock + 'static, E: EthSpec> DutiesServiceBuilder<T, E> {
|
||||||
@ -337,9 +337,9 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesServiceBuilder<T, E> {
|
|||||||
Self {
|
Self {
|
||||||
validator_store: None,
|
validator_store: None,
|
||||||
slot_clock: None,
|
slot_clock: None,
|
||||||
beacon_node: None,
|
beacon_nodes: None,
|
||||||
context: None,
|
|
||||||
allow_unsynced_beacon_node: false,
|
allow_unsynced_beacon_node: false,
|
||||||
|
context: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,8 +353,13 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesServiceBuilder<T, E> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn beacon_node(mut self, beacon_node: BeaconNodeHttpClient) -> Self {
|
pub fn beacon_nodes(mut self, beacon_nodes: Arc<BeaconNodeFallback<T, E>>) -> Self {
|
||||||
self.beacon_node = Some(beacon_node);
|
self.beacon_nodes = Some(beacon_nodes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allow_unsynced_beacon_node(mut self, allow_unsynced_beacon_node: bool) -> Self {
|
||||||
|
self.allow_unsynced_beacon_node = allow_unsynced_beacon_node;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,12 +368,6 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesServiceBuilder<T, E> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set to `true` to allow polling for duties when the beacon node is not synced.
|
|
||||||
pub fn allow_unsynced_beacon_node(mut self, allow_unsynced_beacon_node: bool) -> Self {
|
|
||||||
self.allow_unsynced_beacon_node = allow_unsynced_beacon_node;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> Result<DutiesService<T, E>, String> {
|
pub fn build(self) -> Result<DutiesService<T, E>, String> {
|
||||||
Ok(DutiesService {
|
Ok(DutiesService {
|
||||||
inner: Arc::new(Inner {
|
inner: Arc::new(Inner {
|
||||||
@ -379,13 +378,13 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesServiceBuilder<T, E> {
|
|||||||
slot_clock: self
|
slot_clock: self
|
||||||
.slot_clock
|
.slot_clock
|
||||||
.ok_or("Cannot build DutiesService without slot_clock")?,
|
.ok_or("Cannot build DutiesService without slot_clock")?,
|
||||||
beacon_node: self
|
beacon_nodes: self
|
||||||
.beacon_node
|
.beacon_nodes
|
||||||
.ok_or("Cannot build DutiesService without beacon_node")?,
|
.ok_or("Cannot build DutiesService without beacon_node")?,
|
||||||
|
allow_unsynced_beacon_node: self.allow_unsynced_beacon_node,
|
||||||
context: self
|
context: self
|
||||||
.context
|
.context
|
||||||
.ok_or("Cannot build DutiesService without runtime_context")?,
|
.ok_or("Cannot build DutiesService without runtime_context")?,
|
||||||
allow_unsynced_beacon_node: self.allow_unsynced_beacon_node,
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -396,11 +395,9 @@ pub struct Inner<T, E: EthSpec> {
|
|||||||
store: Arc<DutiesStore>,
|
store: Arc<DutiesStore>,
|
||||||
validator_store: ValidatorStore<T, E>,
|
validator_store: ValidatorStore<T, E>,
|
||||||
pub(crate) slot_clock: T,
|
pub(crate) slot_clock: T,
|
||||||
pub(crate) beacon_node: BeaconNodeHttpClient,
|
pub(crate) beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
|
||||||
context: RuntimeContext<E>,
|
|
||||||
/// If true, the duties service will poll for duties from the beacon node even if it is not
|
|
||||||
/// synced.
|
|
||||||
allow_unsynced_beacon_node: bool,
|
allow_unsynced_beacon_node: bool,
|
||||||
|
context: RuntimeContext<E>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maintains a store of the duties for all voting validators in the `validator_store`.
|
/// Maintains a store of the duties for all voting validators in the `validator_store`.
|
||||||
@ -513,12 +510,6 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
|
|||||||
let _timer =
|
let _timer =
|
||||||
metrics::start_timer_vec(&metrics::DUTIES_SERVICE_TIMES, &[metrics::FULL_UPDATE]);
|
metrics::start_timer_vec(&metrics::DUTIES_SERVICE_TIMES, &[metrics::FULL_UPDATE]);
|
||||||
|
|
||||||
if !is_synced(&self.beacon_node, &self.slot_clock, None).await
|
|
||||||
&& !self.allow_unsynced_beacon_node
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let slot = if let Some(slot) = self.slot_clock.now() {
|
let slot = if let Some(slot) = self.slot_clock.now() {
|
||||||
slot
|
slot
|
||||||
} else {
|
} else {
|
||||||
@ -594,6 +585,12 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
|
|||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let log = self.context.log();
|
let log = self.context.log();
|
||||||
|
|
||||||
|
let maybe_require_synced = if self.allow_unsynced_beacon_node {
|
||||||
|
RequireSynced::No
|
||||||
|
} else {
|
||||||
|
RequireSynced::Yes
|
||||||
|
};
|
||||||
|
|
||||||
let mut new_validator = 0;
|
let mut new_validator = 0;
|
||||||
let mut new_epoch = 0;
|
let mut new_epoch = 0;
|
||||||
let mut new_proposal_slots = 0;
|
let mut new_proposal_slots = 0;
|
||||||
@ -614,21 +611,27 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut validator_subscriptions = vec![];
|
let mut validator_subscriptions = vec![];
|
||||||
let remote_duties: Vec<ValidatorDuty> = match ValidatorDuty::download(
|
let pubkeys_ref = &pubkeys;
|
||||||
&self.beacon_node,
|
let remote_duties: Vec<ValidatorDuty> = match self
|
||||||
current_epoch,
|
.beacon_nodes
|
||||||
request_epoch,
|
.first_success(maybe_require_synced, |beacon_node| async move {
|
||||||
pubkeys,
|
ValidatorDuty::download(
|
||||||
&log,
|
&beacon_node,
|
||||||
)
|
current_epoch,
|
||||||
.await
|
request_epoch,
|
||||||
|
pubkeys_ref.clone(),
|
||||||
|
&log,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
Ok(duties) => duties,
|
Ok(duties) => duties,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
log,
|
log,
|
||||||
"Failed to download validator duties";
|
"Failed to download validator duties";
|
||||||
"error" => e
|
"error" => %e
|
||||||
);
|
);
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
@ -720,10 +723,16 @@ impl<T: SlotClock + 'static, E: EthSpec> DutiesService<T, E> {
|
|||||||
if count == 0 {
|
if count == 0 {
|
||||||
debug!(log, "No new subscriptions required");
|
debug!(log, "No new subscriptions required");
|
||||||
} else {
|
} else {
|
||||||
self.beacon_node
|
let validator_subscriptions_ref = &validator_subscriptions;
|
||||||
.post_validator_beacon_committee_subscriptions(&validator_subscriptions)
|
self.beacon_nodes
|
||||||
|
.first_success(RequireSynced::No, |beacon_node| async move {
|
||||||
|
beacon_node
|
||||||
|
.post_validator_beacon_committee_subscriptions(validator_subscriptions_ref)
|
||||||
|
.await
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to subscribe validators: {:?}", e))?;
|
.map_err(|e| format!("Failed to subscribe validators: {}", e))?;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
log,
|
log,
|
||||||
"Successfully subscribed validators";
|
"Successfully subscribed validators";
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
use crate::beacon_node_fallback::{BeaconNodeFallback, RequireSynced};
|
||||||
use crate::http_metrics::metrics;
|
use crate::http_metrics::metrics;
|
||||||
use environment::RuntimeContext;
|
use environment::RuntimeContext;
|
||||||
use eth2::{types::StateId, BeaconNodeHttpClient};
|
use eth2::types::StateId;
|
||||||
use futures::future::FutureExt;
|
use futures::future::FutureExt;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
@ -16,19 +17,19 @@ use types::{EthSpec, Fork};
|
|||||||
const TIME_DELAY_FROM_SLOT: Duration = Duration::from_millis(80);
|
const TIME_DELAY_FROM_SLOT: Duration = Duration::from_millis(80);
|
||||||
|
|
||||||
/// Builds a `ForkService`.
|
/// Builds a `ForkService`.
|
||||||
pub struct ForkServiceBuilder<T> {
|
pub struct ForkServiceBuilder<T, E: EthSpec> {
|
||||||
fork: Option<Fork>,
|
fork: Option<Fork>,
|
||||||
slot_clock: Option<T>,
|
slot_clock: Option<T>,
|
||||||
beacon_node: Option<BeaconNodeHttpClient>,
|
beacon_nodes: Option<Arc<BeaconNodeFallback<T, E>>>,
|
||||||
log: Option<Logger>,
|
log: Option<Logger>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: SlotClock + 'static> ForkServiceBuilder<T> {
|
impl<T: SlotClock + 'static, E: EthSpec> ForkServiceBuilder<T, E> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
fork: None,
|
fork: None,
|
||||||
slot_clock: None,
|
slot_clock: None,
|
||||||
beacon_node: None,
|
beacon_nodes: None,
|
||||||
log: None,
|
log: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,8 +39,8 @@ impl<T: SlotClock + 'static> ForkServiceBuilder<T> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn beacon_node(mut self, beacon_node: BeaconNodeHttpClient) -> Self {
|
pub fn beacon_nodes(mut self, beacon_nodes: Arc<BeaconNodeFallback<T, E>>) -> Self {
|
||||||
self.beacon_node = Some(beacon_node);
|
self.beacon_nodes = Some(beacon_nodes);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,15 +49,15 @@ impl<T: SlotClock + 'static> ForkServiceBuilder<T> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Result<ForkService<T>, String> {
|
pub fn build(self) -> Result<ForkService<T, E>, String> {
|
||||||
Ok(ForkService {
|
Ok(ForkService {
|
||||||
inner: Arc::new(Inner {
|
inner: Arc::new(Inner {
|
||||||
fork: RwLock::new(self.fork),
|
fork: RwLock::new(self.fork),
|
||||||
slot_clock: self
|
slot_clock: self
|
||||||
.slot_clock
|
.slot_clock
|
||||||
.ok_or("Cannot build ForkService without slot_clock")?,
|
.ok_or("Cannot build ForkService without slot_clock")?,
|
||||||
beacon_node: self
|
beacon_nodes: self
|
||||||
.beacon_node
|
.beacon_nodes
|
||||||
.ok_or("Cannot build ForkService without beacon_node")?,
|
.ok_or("Cannot build ForkService without beacon_node")?,
|
||||||
log: self
|
log: self
|
||||||
.log
|
.log
|
||||||
@ -69,8 +70,21 @@ impl<T: SlotClock + 'static> ForkServiceBuilder<T> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl ForkServiceBuilder<slot_clock::TestingSlotClock> {
|
impl<E: EthSpec> ForkServiceBuilder<slot_clock::TestingSlotClock, E> {
|
||||||
pub fn testing_only(log: Logger) -> Self {
|
pub fn testing_only(spec: types::ChainSpec, log: Logger) -> Self {
|
||||||
|
use crate::beacon_node_fallback::CandidateBeaconNode;
|
||||||
|
|
||||||
|
let slot_clock = slot_clock::TestingSlotClock::new(
|
||||||
|
types::Slot::new(0),
|
||||||
|
std::time::Duration::from_secs(42),
|
||||||
|
std::time::Duration::from_secs(42),
|
||||||
|
);
|
||||||
|
let candidates = vec![CandidateBeaconNode::new(eth2::BeaconNodeHttpClient::new(
|
||||||
|
eth2::Url::parse("http://127.0.0.1").unwrap(),
|
||||||
|
))];
|
||||||
|
let mut beacon_nodes = BeaconNodeFallback::new(candidates, spec, log.clone());
|
||||||
|
beacon_nodes.set_slot_clock(slot_clock.clone());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
fork: Some(types::Fork::default()),
|
fork: Some(types::Fork::default()),
|
||||||
slot_clock: Some(slot_clock::TestingSlotClock::new(
|
slot_clock: Some(slot_clock::TestingSlotClock::new(
|
||||||
@ -78,28 +92,26 @@ impl ForkServiceBuilder<slot_clock::TestingSlotClock> {
|
|||||||
std::time::Duration::from_secs(42),
|
std::time::Duration::from_secs(42),
|
||||||
std::time::Duration::from_secs(42),
|
std::time::Duration::from_secs(42),
|
||||||
)),
|
)),
|
||||||
beacon_node: Some(eth2::BeaconNodeHttpClient::new(
|
beacon_nodes: Some(Arc::new(beacon_nodes)),
|
||||||
eth2::Url::parse("http://127.0.0.1").unwrap(),
|
|
||||||
)),
|
|
||||||
log: Some(log),
|
log: Some(log),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to minimise `Arc` usage.
|
/// Helper to minimise `Arc` usage.
|
||||||
pub struct Inner<T> {
|
pub struct Inner<T, E: EthSpec> {
|
||||||
fork: RwLock<Option<Fork>>,
|
fork: RwLock<Option<Fork>>,
|
||||||
beacon_node: BeaconNodeHttpClient,
|
beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
slot_clock: T,
|
slot_clock: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to download the `Fork` struct from the beacon node at the start of each epoch.
|
/// Attempts to download the `Fork` struct from the beacon node at the start of each epoch.
|
||||||
pub struct ForkService<T> {
|
pub struct ForkService<T, E: EthSpec> {
|
||||||
inner: Arc<Inner<T>>,
|
inner: Arc<Inner<T, E>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Clone for ForkService<T> {
|
impl<T, E: EthSpec> Clone for ForkService<T, E> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: self.inner.clone(),
|
inner: self.inner.clone(),
|
||||||
@ -107,25 +119,22 @@ impl<T> Clone for ForkService<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref for ForkService<T> {
|
impl<T, E: EthSpec> Deref for ForkService<T, E> {
|
||||||
type Target = Inner<T>;
|
type Target = Inner<T, E>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
self.inner.deref()
|
self.inner.deref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: SlotClock + 'static> ForkService<T> {
|
impl<T: SlotClock + 'static, E: EthSpec> ForkService<T, E> {
|
||||||
/// Returns the last fork downloaded from the beacon node, if any.
|
/// Returns the last fork downloaded from the beacon node, if any.
|
||||||
pub fn fork(&self) -> Option<Fork> {
|
pub fn fork(&self) -> Option<Fork> {
|
||||||
*self.fork.read()
|
*self.fork.read()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts the service that periodically polls for the `Fork`.
|
/// Starts the service that periodically polls for the `Fork`.
|
||||||
pub fn start_update_service<E: EthSpec>(
|
pub fn start_update_service(self, context: &RuntimeContext<E>) -> Result<(), String> {
|
||||||
self,
|
|
||||||
context: &RuntimeContext<E>,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let spec = &context.eth2_config.spec;
|
let spec = &context.eth2_config.spec;
|
||||||
|
|
||||||
let duration_to_next_epoch = self
|
let duration_to_next_epoch = self
|
||||||
@ -165,26 +174,32 @@ impl<T: SlotClock + 'static> ForkService<T> {
|
|||||||
let _timer =
|
let _timer =
|
||||||
metrics::start_timer_vec(&metrics::FORK_SERVICE_TIMES, &[metrics::FULL_UPDATE]);
|
metrics::start_timer_vec(&metrics::FORK_SERVICE_TIMES, &[metrics::FULL_UPDATE]);
|
||||||
|
|
||||||
|
let log = &self.log;
|
||||||
let fork = self
|
let fork = self
|
||||||
.inner
|
.inner
|
||||||
.beacon_node
|
.beacon_nodes
|
||||||
.get_beacon_states_fork(StateId::Head)
|
.first_success(RequireSynced::No, |beacon_node| async move {
|
||||||
|
beacon_node
|
||||||
|
.get_beacon_states_fork(StateId::Head)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
trace!(
|
||||||
|
log,
|
||||||
|
"Fork update failed";
|
||||||
|
"error" => format!("Error retrieving fork: {:?}", e)
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
trace!(
|
||||||
|
log,
|
||||||
|
"Fork update failed";
|
||||||
|
"error" => "The beacon head fork is unknown"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|result| result.data)
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|_| ())?;
|
||||||
trace!(
|
|
||||||
self.log,
|
|
||||||
"Fork update failed";
|
|
||||||
"error" => format!("Error retrieving fork: {:?}", e)
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
trace!(
|
|
||||||
self.log,
|
|
||||||
"Fork update failed";
|
|
||||||
"error" => "The beacon head fork is unknown"
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.data;
|
|
||||||
|
|
||||||
if self.fork.read().as_ref() != Some(&fork) {
|
if self.fork.read().as_ref() != Some(&fork) {
|
||||||
*(self.fork.write()) = Some(fork);
|
*(self.fork.write()) = Some(fork);
|
||||||
|
@ -73,7 +73,9 @@ impl ApiTester {
|
|||||||
config.validator_dir = validator_dir.path().into();
|
config.validator_dir = validator_dir.path().into();
|
||||||
config.secrets_dir = secrets_dir.path().into();
|
config.secrets_dir = secrets_dir.path().into();
|
||||||
|
|
||||||
let fork_service = ForkServiceBuilder::testing_only(log.clone())
|
let spec = E::default_spec();
|
||||||
|
|
||||||
|
let fork_service = ForkServiceBuilder::testing_only(spec.clone(), log.clone())
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -84,7 +86,7 @@ impl ApiTester {
|
|||||||
initialized_validators,
|
initialized_validators,
|
||||||
slashing_protection,
|
slashing_protection,
|
||||||
Hash256::repeat_byte(42),
|
Hash256::repeat_byte(42),
|
||||||
E::default_spec(),
|
spec,
|
||||||
fork_service.clone(),
|
fork_service.clone(),
|
||||||
log.clone(),
|
log.clone(),
|
||||||
);
|
);
|
||||||
|
@ -80,6 +80,19 @@ lazy_static::lazy_static! {
|
|||||||
"Number of attesters on this host",
|
"Number of attesters on this host",
|
||||||
&["task"]
|
&["task"]
|
||||||
);
|
);
|
||||||
|
/*
|
||||||
|
* Endpoint metrics
|
||||||
|
*/
|
||||||
|
pub static ref ENDPOINT_ERRORS: Result<IntCounterVec> = try_create_int_counter_vec(
|
||||||
|
"bn_endpoint_errors",
|
||||||
|
"The number of beacon node request errors for each endpoint",
|
||||||
|
&["endpoint"]
|
||||||
|
);
|
||||||
|
pub static ref ENDPOINT_REQUESTS: Result<IntCounterVec> = try_create_int_counter_vec(
|
||||||
|
"bn_endpoint_requests",
|
||||||
|
"The number of beacon node requests for each endpoint",
|
||||||
|
&["endpoint"]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gather_prometheus_metrics<T: EthSpec>(
|
pub fn gather_prometheus_metrics<T: EthSpec>(
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
mod attestation_service;
|
mod attestation_service;
|
||||||
|
mod beacon_node_fallback;
|
||||||
mod block_service;
|
mod block_service;
|
||||||
|
mod check_synced;
|
||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
mod duties_service;
|
mod duties_service;
|
||||||
mod fork_service;
|
mod fork_service;
|
||||||
mod http_metrics;
|
mod http_metrics;
|
||||||
mod initialized_validators;
|
mod initialized_validators;
|
||||||
mod is_synced;
|
|
||||||
mod key_cache;
|
mod key_cache;
|
||||||
mod notifier;
|
mod notifier;
|
||||||
mod validator_duty;
|
mod validator_duty;
|
||||||
@ -17,6 +18,9 @@ pub mod http_api;
|
|||||||
pub use cli::cli_app;
|
pub use cli::cli_app;
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
|
|
||||||
|
use crate::beacon_node_fallback::{
|
||||||
|
start_fallback_updater_service, BeaconNodeFallback, CandidateBeaconNode, RequireSynced,
|
||||||
|
};
|
||||||
use account_utils::validator_definitions::ValidatorDefinitions;
|
use account_utils::validator_definitions::ValidatorDefinitions;
|
||||||
use attestation_service::{AttestationService, AttestationServiceBuilder};
|
use attestation_service::{AttestationService, AttestationServiceBuilder};
|
||||||
use block_service::{BlockService, BlockServiceBuilder};
|
use block_service::{BlockService, BlockServiceBuilder};
|
||||||
@ -55,7 +59,7 @@ const HTTP_TIMEOUT: Duration = Duration::from_secs(12);
|
|||||||
pub struct ProductionValidatorClient<T: EthSpec> {
|
pub struct ProductionValidatorClient<T: EthSpec> {
|
||||||
context: RuntimeContext<T>,
|
context: RuntimeContext<T>,
|
||||||
duties_service: DutiesService<SystemTimeSlotClock, T>,
|
duties_service: DutiesService<SystemTimeSlotClock, T>,
|
||||||
fork_service: ForkService<SystemTimeSlotClock>,
|
fork_service: ForkService<SystemTimeSlotClock, T>,
|
||||||
block_service: BlockService<SystemTimeSlotClock, T>,
|
block_service: BlockService<SystemTimeSlotClock, T>,
|
||||||
attestation_service: AttestationService<SystemTimeSlotClock, T>,
|
attestation_service: AttestationService<SystemTimeSlotClock, T>,
|
||||||
validator_store: ValidatorStore<SystemTimeSlotClock, T>,
|
validator_store: ValidatorStore<SystemTimeSlotClock, T>,
|
||||||
@ -84,7 +88,7 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
|
|||||||
info!(
|
info!(
|
||||||
log,
|
log,
|
||||||
"Starting validator client";
|
"Starting validator client";
|
||||||
"beacon_node" => &config.beacon_node,
|
"beacon_nodes" => format!("{:?}", &config.beacon_nodes),
|
||||||
"validator_dir" => format!("{:?}", config.validator_dir),
|
"validator_dir" => format!("{:?}", config.validator_dir),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -202,20 +206,36 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let beacon_node_url: Url = config
|
let beacon_node_urls: Vec<Url> = config
|
||||||
.beacon_node
|
.beacon_nodes
|
||||||
.parse()
|
.iter()
|
||||||
|
.map(|s| s.parse())
|
||||||
|
.collect::<Result<_, _>>()
|
||||||
.map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?;
|
.map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?;
|
||||||
let beacon_node_http_client = ClientBuilder::new()
|
let beacon_nodes: Vec<BeaconNodeHttpClient> = beacon_node_urls
|
||||||
.timeout(HTTP_TIMEOUT)
|
.into_iter()
|
||||||
.build()
|
.map(|url| {
|
||||||
.map_err(|e| format!("Unable to build HTTP client: {:?}", e))?;
|
let beacon_node_http_client = ClientBuilder::new()
|
||||||
let beacon_node =
|
.timeout(HTTP_TIMEOUT)
|
||||||
BeaconNodeHttpClient::from_components(beacon_node_url, beacon_node_http_client);
|
.build()
|
||||||
|
.map_err(|e| format!("Unable to build HTTP client: {:?}", e))?;
|
||||||
|
Ok(BeaconNodeHttpClient::from_components(
|
||||||
|
url,
|
||||||
|
beacon_node_http_client,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<BeaconNodeHttpClient>, String>>()?;
|
||||||
|
|
||||||
|
let candidates = beacon_nodes
|
||||||
|
.into_iter()
|
||||||
|
.map(CandidateBeaconNode::new)
|
||||||
|
.collect();
|
||||||
|
let mut beacon_nodes: BeaconNodeFallback<_, T> =
|
||||||
|
BeaconNodeFallback::new(candidates, context.eth2_config.spec.clone(), log.clone());
|
||||||
|
|
||||||
// Perform some potentially long-running initialization tasks.
|
// Perform some potentially long-running initialization tasks.
|
||||||
let (genesis_time, genesis_validators_root) = tokio::select! {
|
let (genesis_time, genesis_validators_root) = tokio::select! {
|
||||||
tuple = init_from_beacon_node(&beacon_node, &context) => tuple?,
|
tuple = init_from_beacon_node(&beacon_nodes, &context) => tuple?,
|
||||||
() = context.executor.exit() => return Err("Shutting down".to_string())
|
() = context.executor.exit() => return Err("Shutting down".to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -230,9 +250,13 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
|
|||||||
Duration::from_millis(context.eth2_config.spec.milliseconds_per_slot),
|
Duration::from_millis(context.eth2_config.spec.milliseconds_per_slot),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
beacon_nodes.set_slot_clock(slot_clock.clone());
|
||||||
|
let beacon_nodes = Arc::new(beacon_nodes);
|
||||||
|
start_fallback_updater_service(context.clone(), beacon_nodes.clone())?;
|
||||||
|
|
||||||
let fork_service = ForkServiceBuilder::new()
|
let fork_service = ForkServiceBuilder::new()
|
||||||
.slot_clock(slot_clock.clone())
|
.slot_clock(slot_clock.clone())
|
||||||
.beacon_node(beacon_node.clone())
|
.beacon_nodes(beacon_nodes.clone())
|
||||||
.log(log.clone())
|
.log(log.clone())
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
@ -254,9 +278,9 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
|
|||||||
let duties_service = DutiesServiceBuilder::new()
|
let duties_service = DutiesServiceBuilder::new()
|
||||||
.slot_clock(slot_clock.clone())
|
.slot_clock(slot_clock.clone())
|
||||||
.validator_store(validator_store.clone())
|
.validator_store(validator_store.clone())
|
||||||
.beacon_node(beacon_node.clone())
|
.beacon_nodes(beacon_nodes.clone())
|
||||||
.runtime_context(context.service_context("duties".into()))
|
|
||||||
.allow_unsynced_beacon_node(config.allow_unsynced_beacon_node)
|
.allow_unsynced_beacon_node(config.allow_unsynced_beacon_node)
|
||||||
|
.runtime_context(context.service_context("duties".into()))
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
// Update the metrics server.
|
// Update the metrics server.
|
||||||
@ -268,7 +292,7 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
|
|||||||
let block_service = BlockServiceBuilder::new()
|
let block_service = BlockServiceBuilder::new()
|
||||||
.slot_clock(slot_clock.clone())
|
.slot_clock(slot_clock.clone())
|
||||||
.validator_store(validator_store.clone())
|
.validator_store(validator_store.clone())
|
||||||
.beacon_node(beacon_node.clone())
|
.beacon_nodes(beacon_nodes.clone())
|
||||||
.runtime_context(context.service_context("block".into()))
|
.runtime_context(context.service_context("block".into()))
|
||||||
.graffiti(config.graffiti)
|
.graffiti(config.graffiti)
|
||||||
.build()?;
|
.build()?;
|
||||||
@ -277,7 +301,7 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
|
|||||||
.duties_service(duties_service.clone())
|
.duties_service(duties_service.clone())
|
||||||
.slot_clock(slot_clock)
|
.slot_clock(slot_clock)
|
||||||
.validator_store(validator_store.clone())
|
.validator_store(validator_store.clone())
|
||||||
.beacon_node(beacon_node.clone())
|
.beacon_nodes(beacon_nodes.clone())
|
||||||
.runtime_context(context.service_context("attestation".into()))
|
.runtime_context(context.service_context("attestation".into()))
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
@ -285,7 +309,7 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
|
|||||||
//
|
//
|
||||||
// It seems most sensible to move this into the `start_service` function, but I'm caution
|
// It seems most sensible to move this into the `start_service` function, but I'm caution
|
||||||
// of making too many changes this close to genesis (<1 week).
|
// of making too many changes this close to genesis (<1 week).
|
||||||
wait_for_genesis(&beacon_node, genesis_time, &context).await?;
|
wait_for_genesis(&beacon_nodes, genesis_time, &context).await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
context,
|
context,
|
||||||
@ -368,40 +392,50 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn init_from_beacon_node<E: EthSpec>(
|
async fn init_from_beacon_node<E: EthSpec>(
|
||||||
beacon_node: &BeaconNodeHttpClient,
|
beacon_nodes: &BeaconNodeFallback<SystemTimeSlotClock, E>,
|
||||||
context: &RuntimeContext<E>,
|
context: &RuntimeContext<E>,
|
||||||
) -> Result<(u64, Hash256), String> {
|
) -> Result<(u64, Hash256), String> {
|
||||||
// Wait for the beacon node to come online.
|
loop {
|
||||||
wait_for_connectivity(beacon_node, context.log()).await?;
|
beacon_nodes.update_unready_candidates().await;
|
||||||
|
let num_available = beacon_nodes.num_available().await;
|
||||||
let yaml_config = beacon_node
|
let num_total = beacon_nodes.num_total().await;
|
||||||
.get_config_spec()
|
if num_available > 0 {
|
||||||
.await
|
info!(
|
||||||
.map_err(|e| format!("Unable to read spec from beacon node: {:?}", e))?
|
context.log(),
|
||||||
.data;
|
"Initialized beacon node connections";
|
||||||
|
"total" => num_total,
|
||||||
let beacon_node_spec = yaml_config
|
"available" => num_available,
|
||||||
.apply_to_chain_spec::<E>(&E::default_spec())
|
);
|
||||||
.ok_or_else(|| {
|
break;
|
||||||
"The minimal/mainnet spec type of the beacon node does not match the validator client. \
|
} else {
|
||||||
See the --network command."
|
warn!(
|
||||||
.to_string()
|
context.log(),
|
||||||
})?;
|
"Unable to connect to a beacon node";
|
||||||
|
"retry in" => format!("{} seconds", RETRY_DELAY.as_secs()),
|
||||||
if context.eth2_config.spec != beacon_node_spec {
|
"total" => num_total,
|
||||||
return Err(
|
"available" => num_available,
|
||||||
"The beacon node is using a different Eth2 specification to this validator client. \
|
);
|
||||||
See the --network command."
|
sleep(RETRY_DELAY).await;
|
||||||
.to_string(),
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let genesis = loop {
|
let genesis = loop {
|
||||||
match beacon_node.get_beacon_genesis().await {
|
match beacon_nodes
|
||||||
|
.first_success(RequireSynced::No, |node| async move {
|
||||||
|
node.get_beacon_genesis().await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(genesis) => break genesis.data,
|
Ok(genesis) => break genesis.data,
|
||||||
Err(e) => {
|
Err(errors) => {
|
||||||
// A 404 error on the genesis endpoint indicates that genesis has not yet occurred.
|
// Search for a 404 error which indicates that genesis has not yet
|
||||||
if e.status() == Some(StatusCode::NOT_FOUND) {
|
// occurred.
|
||||||
|
if errors
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(_, e)| e.request_failure())
|
||||||
|
.any(|e| e.status() == Some(StatusCode::NOT_FOUND))
|
||||||
|
{
|
||||||
info!(
|
info!(
|
||||||
context.log(),
|
context.log(),
|
||||||
"Waiting for genesis";
|
"Waiting for genesis";
|
||||||
@ -409,8 +443,8 @@ async fn init_from_beacon_node<E: EthSpec>(
|
|||||||
} else {
|
} else {
|
||||||
error!(
|
error!(
|
||||||
context.log(),
|
context.log(),
|
||||||
"Error polling beacon node";
|
"Errors polling beacon node";
|
||||||
"error" => format!("{:?}", e)
|
"error" => %errors
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -423,7 +457,7 @@ async fn init_from_beacon_node<E: EthSpec>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_for_genesis<E: EthSpec>(
|
async fn wait_for_genesis<E: EthSpec>(
|
||||||
beacon_node: &BeaconNodeHttpClient,
|
beacon_nodes: &BeaconNodeFallback<SystemTimeSlotClock, E>,
|
||||||
genesis_time: u64,
|
genesis_time: u64,
|
||||||
context: &RuntimeContext<E>,
|
context: &RuntimeContext<E>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
@ -447,7 +481,7 @@ async fn wait_for_genesis<E: EthSpec>(
|
|||||||
// Start polling the node for pre-genesis information, cancelling the polling as soon as the
|
// Start polling the node for pre-genesis information, cancelling the polling as soon as the
|
||||||
// timer runs out.
|
// timer runs out.
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
result = poll_whilst_waiting_for_genesis(beacon_node, genesis_time, context.log()) => result?,
|
result = poll_whilst_waiting_for_genesis(beacon_nodes, genesis_time, context.log()) => result?,
|
||||||
() = sleep(genesis_time - now) => ()
|
() = sleep(genesis_time - now) => ()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -469,50 +503,18 @@ async fn wait_for_genesis<E: EthSpec>(
|
|||||||
|
|
||||||
/// Request the version from the node, looping back and trying again on failure. Exit once the node
|
/// Request the version from the node, looping back and trying again on failure. Exit once the node
|
||||||
/// has been contacted.
|
/// has been contacted.
|
||||||
async fn wait_for_connectivity(
|
async fn poll_whilst_waiting_for_genesis<E: EthSpec>(
|
||||||
beacon_node: &BeaconNodeHttpClient,
|
beacon_nodes: &BeaconNodeFallback<SystemTimeSlotClock, E>,
|
||||||
log: &Logger,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
// Try to get the version string from the node, looping until success is returned.
|
|
||||||
loop {
|
|
||||||
let log = log.clone();
|
|
||||||
let result = beacon_node
|
|
||||||
.get_node_version()
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("{:?}", e))
|
|
||||||
.map(|body| body.data.version);
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(version) => {
|
|
||||||
info!(
|
|
||||||
log,
|
|
||||||
"Connected to beacon node";
|
|
||||||
"version" => version,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!(
|
|
||||||
log,
|
|
||||||
"Unable to connect to beacon node";
|
|
||||||
"error" => format!("{:?}", e),
|
|
||||||
);
|
|
||||||
sleep(RETRY_DELAY).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Request the version from the node, looping back and trying again on failure. Exit once the node
|
|
||||||
/// has been contacted.
|
|
||||||
async fn poll_whilst_waiting_for_genesis(
|
|
||||||
beacon_node: &BeaconNodeHttpClient,
|
|
||||||
genesis_time: Duration,
|
genesis_time: Duration,
|
||||||
log: &Logger,
|
log: &Logger,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
loop {
|
loop {
|
||||||
match beacon_node.get_lighthouse_staking().await {
|
match beacon_nodes
|
||||||
|
.first_success(RequireSynced::No, |beacon_node| async move {
|
||||||
|
beacon_node.get_lighthouse_staking().await
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(is_staking) => {
|
Ok(is_staking) => {
|
||||||
let now = SystemTime::now()
|
let now = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
@ -542,7 +544,7 @@ async fn poll_whilst_waiting_for_genesis(
|
|||||||
error!(
|
error!(
|
||||||
log,
|
log,
|
||||||
"Error polling beacon node";
|
"Error polling beacon node";
|
||||||
"error" => format!("{:?}", e)
|
"error" => %e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{is_synced::is_synced, ProductionValidatorClient};
|
use crate::ProductionValidatorClient;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use slog::{error, info};
|
use slog::{error, info};
|
||||||
use slot_clock::SlotClock;
|
use slot_clock::SlotClock;
|
||||||
@ -10,7 +10,6 @@ pub fn spawn_notifier<T: EthSpec>(client: &ProductionValidatorClient<T>) -> Resu
|
|||||||
let context = client.context.service_context("notifier".into());
|
let context = client.context.service_context("notifier".into());
|
||||||
let executor = context.executor.clone();
|
let executor = context.executor.clone();
|
||||||
let duties_service = client.duties_service.clone();
|
let duties_service = client.duties_service.clone();
|
||||||
let allow_unsynced_beacon_node = client.config.allow_unsynced_beacon_node;
|
|
||||||
|
|
||||||
let slot_duration = Duration::from_millis(context.eth2_config.spec.milliseconds_per_slot);
|
let slot_duration = Duration::from_millis(context.eth2_config.spec.milliseconds_per_slot);
|
||||||
let duration_to_next_slot = duties_service
|
let duration_to_next_slot = duties_service
|
||||||
@ -26,15 +25,25 @@ pub fn spawn_notifier<T: EthSpec>(client: &ProductionValidatorClient<T>) -> Resu
|
|||||||
let log = context.log();
|
let log = context.log();
|
||||||
|
|
||||||
while interval.next().await.is_some() {
|
while interval.next().await.is_some() {
|
||||||
if !is_synced(
|
let num_available = duties_service.beacon_nodes.num_available().await;
|
||||||
&duties_service.beacon_node,
|
let num_synced = duties_service.beacon_nodes.num_synced().await;
|
||||||
&duties_service.slot_clock,
|
let num_total = duties_service.beacon_nodes.num_total().await;
|
||||||
Some(&log),
|
if num_synced > 0 {
|
||||||
)
|
info!(
|
||||||
.await
|
log,
|
||||||
&& !allow_unsynced_beacon_node
|
"Connected to beacon node(s)";
|
||||||
{
|
"total" => num_total,
|
||||||
continue;
|
"available" => num_available,
|
||||||
|
"synced" => num_synced,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
log,
|
||||||
|
"No synced beacon nodes";
|
||||||
|
"total" => num_total,
|
||||||
|
"available" => num_available,
|
||||||
|
"synced" => num_synced,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(slot) = duties_service.slot_clock.now() {
|
if let Some(slot) = duties_service.slot_clock.now() {
|
||||||
|
@ -6,7 +6,6 @@ use parking_lot::RwLock;
|
|||||||
use slashing_protection::{NotSafe, Safe, SlashingDatabase};
|
use slashing_protection::{NotSafe, Safe, SlashingDatabase};
|
||||||
use slog::{crit, error, warn, Logger};
|
use slog::{crit, error, warn, Logger};
|
||||||
use slot_clock::SlotClock;
|
use slot_clock::SlotClock;
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
@ -49,8 +48,7 @@ pub struct ValidatorStore<T, E: EthSpec> {
|
|||||||
spec: Arc<ChainSpec>,
|
spec: Arc<ChainSpec>,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
temp_dir: Option<Arc<TempDir>>,
|
temp_dir: Option<Arc<TempDir>>,
|
||||||
fork_service: ForkService<T>,
|
fork_service: ForkService<T, E>,
|
||||||
_phantom: PhantomData<E>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
||||||
@ -59,7 +57,7 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
|||||||
slashing_protection: SlashingDatabase,
|
slashing_protection: SlashingDatabase,
|
||||||
genesis_validators_root: Hash256,
|
genesis_validators_root: Hash256,
|
||||||
spec: ChainSpec,
|
spec: ChainSpec,
|
||||||
fork_service: ForkService<T>,
|
fork_service: ForkService<T, E>,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -70,7 +68,6 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore<T, E> {
|
|||||||
log,
|
log,
|
||||||
temp_dir: None,
|
temp_dir: None,
|
||||||
fork_service,
|
fork_service,
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user