Merge branch 'unstable' into deneb-free-blobs

# Conflicts:
#	.github/workflows/docker.yml
#	.github/workflows/local-testnet.yml
#	.github/workflows/test-suite.yml
#	Cargo.lock
#	Cargo.toml
#	beacon_node/beacon_chain/src/beacon_chain.rs
#	beacon_node/beacon_chain/src/builder.rs
#	beacon_node/beacon_chain/src/test_utils.rs
#	beacon_node/execution_layer/src/engine_api/json_structures.rs
#	beacon_node/network/src/beacon_processor/mod.rs
#	beacon_node/network/src/beacon_processor/worker/gossip_methods.rs
#	beacon_node/network/src/sync/backfill_sync/mod.rs
#	beacon_node/store/src/config.rs
#	beacon_node/store/src/hot_cold_store.rs
#	common/eth2_network_config/Cargo.toml
#	consensus/ssz/src/decode/impls.rs
#	consensus/ssz_derive/src/lib.rs
#	consensus/ssz_derive/tests/tests.rs
#	consensus/ssz_types/src/serde_utils/mod.rs
#	consensus/tree_hash/src/impls.rs
#	consensus/tree_hash/src/lib.rs
#	consensus/types/Cargo.toml
#	consensus/types/src/beacon_state.rs
#	consensus/types/src/chain_spec.rs
#	consensus/types/src/eth_spec.rs
#	consensus/types/src/fork_name.rs
#	lcli/Cargo.toml
#	lcli/src/main.rs
#	lcli/src/new_testnet.rs
#	scripts/local_testnet/el_bootnode.sh
#	scripts/local_testnet/genesis.json
#	scripts/local_testnet/geth.sh
#	scripts/local_testnet/setup.sh
#	scripts/local_testnet/start_local_testnet.sh
#	scripts/local_testnet/vars.env
#	scripts/tests/doppelganger_protection.sh
#	scripts/tests/genesis.json
#	scripts/tests/vars.env
#	testing/ef_tests/Cargo.toml
#	validator_client/src/block_service.rs
This commit is contained in:
Jimmy Chen
2023-05-30 11:26:33 +10:00
333 changed files with 5930 additions and 13386 deletions

View File

@@ -13,7 +13,7 @@ tokio = { version = "1.14.0", features = ["time", "rt-multi-thread", "macros"] }
logging = { path = "../common/logging" }
[dependencies]
tree_hash = "0.4.1"
tree_hash = "0.5.0"
clap = "2.33.3"
slashing_protection = { path = "./slashing_protection" }
slot_clock = { path = "../common/slot_clock" }
@@ -25,6 +25,7 @@ bincode = "1.3.1"
serde_json = "1.0.58"
slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] }
tokio = { version = "1.14.0", features = ["time"] }
tokio-stream = { version = "0.1.3", features = ["sync"] }
futures = "0.3.7"
dirs = "3.0.1"
directory = { path = "../common/directory" }
@@ -46,7 +47,7 @@ lighthouse_version = { path = "../common/lighthouse_version" }
warp_utils = { path = "../common/warp_utils" }
warp = "0.3.2"
hyper = "0.14.4"
eth2_serde_utils = "0.1.1"
ethereum_serde_utils = "0.5.0"
libsecp256k1 = "0.7.0"
ring = "0.16.19"
rand = { version = "0.8.5", features = ["small_rng"] }
@@ -61,4 +62,5 @@ url = "2.2.2"
malloc_utils = { path = "../common/malloc_utils" }
sysinfo = "0.26.5"
system_health = { path = "../common/system_health" }
logging = { path = "../common/logging" }

View File

@@ -18,7 +18,7 @@ r2d2_sqlite = "0.21.0"
serde = "1.0.116"
serde_derive = "1.0.116"
serde_json = "1.0.58"
eth2_serde_utils = "0.1.1"
ethereum_serde_utils = "0.5.0"
filesystem = { path = "../../common/filesystem" }
arbitrary = { version = "1.0", features = ["derive"], optional = true }

View File

@@ -9,7 +9,7 @@ use types::{Epoch, Hash256, PublicKeyBytes, Slot};
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct InterchangeMetadata {
#[serde(with = "eth2_serde_utils::quoted_u64::require_quotes")]
#[serde(with = "serde_utils::quoted_u64::require_quotes")]
pub interchange_format_version: u64,
pub genesis_validators_root: Hash256,
}
@@ -27,7 +27,7 @@ pub struct InterchangeData {
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct SignedBlock {
#[serde(with = "eth2_serde_utils::quoted_u64::require_quotes")]
#[serde(with = "serde_utils::quoted_u64::require_quotes")]
pub slot: Slot,
#[serde(skip_serializing_if = "Option::is_none")]
pub signing_root: Option<Hash256>,
@@ -37,9 +37,9 @@ pub struct SignedBlock {
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct SignedAttestation {
#[serde(with = "eth2_serde_utils::quoted_u64::require_quotes")]
#[serde(with = "serde_utils::quoted_u64::require_quotes")]
pub source_epoch: Epoch,
#[serde(with = "eth2_serde_utils::quoted_u64::require_quotes")]
#[serde(with = "serde_utils::quoted_u64::require_quotes")]
pub target_epoch: Epoch,
#[serde(skip_serializing_if = "Option::is_none")]
pub signing_root: Option<Hash256>,

View File

@@ -28,7 +28,7 @@ const UPDATE_REQUIRED_LOG_HINT: &str = "this VC or the remote BN may need updati
/// 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);
const SLOT_LOOKAHEAD: Duration = Duration::from_secs(2);
/// Indicates a measurement of latency between the VC and a BN.
pub struct LatencyMeasurement {
@@ -52,7 +52,7 @@ pub fn start_fallback_updater_service<T: SlotClock + 'static, E: EthSpec>(
let future = async move {
loop {
beacon_nodes.update_unready_candidates().await;
beacon_nodes.update_all_candidates().await;
let sleep_time = beacon_nodes
.slot_clock
@@ -182,7 +182,10 @@ impl<E: EthSpec> CandidateBeaconNode<E> {
spec: &ChainSpec,
log: &Logger,
) -> Result<(), CandidateError> {
let new_status = if let Err(e) = self.is_online(log).await {
let previous_status = self.status(RequireSynced::Yes).await;
let was_offline = matches!(previous_status, Err(CandidateError::Offline));
let new_status = if let Err(e) = self.is_online(was_offline, log).await {
Err(e)
} else if let Err(e) = self.is_compatible(spec, log).await {
Err(e)
@@ -202,7 +205,7 @@ impl<E: EthSpec> CandidateBeaconNode<E> {
}
/// Checks if the node is reachable.
async fn is_online(&self, log: &Logger) -> Result<(), CandidateError> {
async fn is_online(&self, was_offline: bool, log: &Logger) -> Result<(), CandidateError> {
let result = self
.beacon_node
.get_node_version()
@@ -211,12 +214,14 @@ impl<E: EthSpec> CandidateBeaconNode<E> {
match result {
Ok(version) => {
info!(
log,
"Connected to beacon node";
"version" => version,
"endpoint" => %self.beacon_node,
);
if was_offline {
info!(
log,
"Connected to beacon node";
"version" => version,
"endpoint" => %self.beacon_node,
);
}
Ok(())
}
Err(e) => {
@@ -385,33 +390,21 @@ impl<T: SlotClock, E: EthSpec> BeaconNodeFallback<T, E> {
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.
/// Loop through ALL candidates in `self.candidates` and update their sync status.
///
/// 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 is deemed an
// acceptable inefficiency.
futures.push(candidate.refresh_status(
self.slot_clock.as_ref(),
&self.spec,
&self.log,
));
}
}
/// 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) {
let futures = self
.candidates
.iter()
.map(|candidate| {
candidate.refresh_status(self.slot_clock.as_ref(), &self.spec, &self.log)
})
.collect::<Vec<_>>();
//run all updates concurrently and ignore results
// run all updates concurrently and ignore errors
let _ = future::join_all(futures).await;
}

View File

@@ -12,6 +12,8 @@ use eth2::types::{BlockContents, SignedBlockContents};
use eth2::BeaconNodeHttpClient;
use slog::{crit, debug, error, info, trace, warn, Logger};
use slot_clock::SlotClock;
use std::fmt::Debug;
use std::future::Future;
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
@@ -48,6 +50,7 @@ pub struct BlockServiceBuilder<T, E: EthSpec> {
validator_store: Option<Arc<ValidatorStore<T, E>>>,
slot_clock: Option<Arc<T>>,
beacon_nodes: Option<Arc<BeaconNodeFallback<T, E>>>,
proposer_nodes: Option<Arc<BeaconNodeFallback<T, E>>>,
context: Option<RuntimeContext<E>>,
graffiti: Option<Graffiti>,
graffiti_file: Option<GraffitiFile>,
@@ -60,6 +63,7 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
validator_store: None,
slot_clock: None,
beacon_nodes: None,
proposer_nodes: None,
context: None,
graffiti: None,
graffiti_file: None,
@@ -82,6 +86,11 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
self
}
pub fn proposer_nodes(mut self, proposer_nodes: Arc<BeaconNodeFallback<T, E>>) -> Self {
self.proposer_nodes = Some(proposer_nodes);
self
}
pub fn runtime_context(mut self, context: RuntimeContext<E>) -> Self {
self.context = Some(context);
self
@@ -117,6 +126,7 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
context: self
.context
.ok_or("Cannot build BlockService without runtime_context")?,
proposer_nodes: self.proposer_nodes,
graffiti: self.graffiti,
graffiti_file: self.graffiti_file,
block_delay: self.block_delay,
@@ -125,11 +135,81 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockServiceBuilder<T, E> {
}
}
// Combines a set of non-block-proposing `beacon_nodes` and only-block-proposing
// `proposer_nodes`.
pub struct ProposerFallback<T, E: EthSpec> {
beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
proposer_nodes: Option<Arc<BeaconNodeFallback<T, E>>>,
}
impl<T: SlotClock, E: EthSpec> ProposerFallback<T, E> {
// Try `func` on `self.proposer_nodes` first. If that doesn't work, try `self.beacon_nodes`.
pub async fn first_success_try_proposers_first<'a, F, O, Err, R>(
&'a self,
require_synced: RequireSynced,
offline_on_failure: OfflineOnFailure,
func: F,
) -> Result<O, Errors<Err>>
where
F: Fn(&'a BeaconNodeHttpClient) -> R + Clone,
R: Future<Output = Result<O, Err>>,
Err: Debug,
{
// If there are proposer nodes, try calling `func` on them and return early if they are successful.
if let Some(proposer_nodes) = &self.proposer_nodes {
if let Ok(result) = proposer_nodes
.first_success(require_synced, offline_on_failure, func.clone())
.await
{
return Ok(result);
}
}
// If the proposer nodes failed, try on the non-proposer nodes.
self.beacon_nodes
.first_success(require_synced, offline_on_failure, func)
.await
}
// Try `func` on `self.beacon_nodes` first. If that doesn't work, try `self.proposer_nodes`.
pub async fn first_success_try_proposers_last<'a, F, O, Err, R>(
&'a self,
require_synced: RequireSynced,
offline_on_failure: OfflineOnFailure,
func: F,
) -> Result<O, Errors<Err>>
where
F: Fn(&'a BeaconNodeHttpClient) -> R + Clone,
R: Future<Output = Result<O, Err>>,
Err: Debug,
{
// Try running `func` on the non-proposer beacon nodes.
let beacon_nodes_result = self
.beacon_nodes
.first_success(require_synced, offline_on_failure, func.clone())
.await;
match (beacon_nodes_result, &self.proposer_nodes) {
// The non-proposer node call succeed, return the result.
(Ok(success), _) => Ok(success),
// The non-proposer node call failed, but we don't have any proposer nodes. Return an error.
(Err(e), None) => Err(e),
// The non-proposer node call failed, try the same call on the proposer nodes.
(Err(_), Some(proposer_nodes)) => {
proposer_nodes
.first_success(require_synced, offline_on_failure, func)
.await
}
}
}
}
/// Helper to minimise `Arc` usage.
pub struct Inner<T, E: EthSpec> {
validator_store: Arc<ValidatorStore<T, E>>,
slot_clock: Arc<T>,
beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
proposer_nodes: Option<Arc<BeaconNodeFallback<T, E>>>,
context: RuntimeContext<E>,
graffiti: Option<Graffiti>,
graffiti_file: Option<GraffitiFile>,
@@ -337,16 +417,23 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
let self_ref = &self;
let proposer_index = self.validator_store.validator_index(&validator_pubkey);
let validator_pubkey_ref = &validator_pubkey;
let proposer_fallback = ProposerFallback {
beacon_nodes: self.beacon_nodes.clone(),
proposer_nodes: self.proposer_nodes.clone(),
};
info!(
log,
"Requesting unsigned block";
"slot" => slot.as_u64(),
);
// Request block from first responsive beacon node.
let block_contents = self
.beacon_nodes
.first_success(
//
// Try the proposer nodes last, since it's likely that they don't have a
// great view of attestations on the network.
let block_contents = proposer_fallback
.first_success_try_proposers_last(
RequireSynced::No,
OfflineOnFailure::Yes,
move |beacon_node| {
@@ -397,8 +484,12 @@ impl<T: SlotClock + 'static, E: EthSpec> BlockService<T, E> {
let signed_block_contents = SignedBlockContents::from((signed_block, maybe_signed_blobs));
// Publish block with first available beacon node.
self.beacon_nodes
.first_success(
//
// Try the proposer nodes first, since we've likely gone to efforts to
// protect them from DoS attacks and they're most likely to successfully
// publish a block.
proposer_fallback
.first_success_try_proposers_first(
RequireSynced::No,
OfflineOnFailure::Yes,
|beacon_node| async {

View File

@@ -36,7 +36,10 @@ pub async fn check_synced<T: SlotClock>(
}
};
let is_synced = !resp.data.is_syncing || (resp.data.sync_distance.as_u64() < SYNC_TOLERANCE);
// Default EL status to "online" for backwards-compatibility with BNs that don't include it.
let el_offline = resp.data.el_offline.unwrap_or(false);
let bn_is_synced = !resp.data.is_syncing || (resp.data.sync_distance.as_u64() < SYNC_TOLERANCE);
let is_synced = bn_is_synced && !el_offline;
if let Some(log) = log_opt {
if !is_synced {
@@ -52,6 +55,7 @@ pub async fn check_synced<T: SlotClock>(
"sync_distance" => resp.data.sync_distance.as_u64(),
"head_slot" => resp.data.head_slot.as_u64(),
"endpoint" => %beacon_node,
"el_offline" => el_offline,
);
}

View File

@@ -26,6 +26,15 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
)
.takes_value(true),
)
.arg(
Arg::with_name("proposer-nodes")
.long("proposer-nodes")
.value_name("NETWORK_ADDRESSES")
.help("Comma-separated addresses to one or more beacon node HTTP APIs. \
These specify nodes that are used to send beacon block proposals. A failure will revert back to the standard beacon nodes specified in --beacon-nodes."
)
.takes_value(true),
)
.arg(
Arg::with_name("disable-run-on-all")
.long("disable-run-on-all")
@@ -100,10 +109,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.arg(
Arg::with_name("allow-unsynced")
.long("allow-unsynced")
.help(
"If present, the validator client will still poll for duties if the beacon
node is not synced.",
),
.help("DEPRECATED: this flag does nothing"),
)
.arg(
Arg::with_name("use-long-timeouts")
@@ -118,7 +124,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.value_name("CERTIFICATE-FILES")
.takes_value(true)
.help("Comma-separated paths to custom TLS certificates to use when connecting \
to a beacon node. These certificates must be in PEM format and are used \
to a beacon node (and/or proposer node). These certificates must be in PEM format and are used \
in addition to the OS trust store. Commas must only be used as a \
delimiter, and must not be part of the certificate path.")
)

View File

@@ -29,6 +29,8 @@ pub struct Config {
///
/// Should be similar to `["http://localhost:8080"]`
pub beacon_nodes: Vec<SensitiveUrl>,
/// An optional beacon node used for block proposals only.
pub proposer_nodes: Vec<SensitiveUrl>,
/// If true, the validator client will still poll for duties and produce blocks even if the
/// beacon node is not synced at startup.
pub allow_unsynced_beacon_node: bool,
@@ -95,6 +97,7 @@ impl Default for Config {
validator_dir,
secrets_dir,
beacon_nodes,
proposer_nodes: Vec::new(),
allow_unsynced_beacon_node: false,
disable_auto_discover: false,
init_slashing_protection: false,
@@ -186,6 +189,14 @@ impl Config {
.map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?];
}
if let Some(proposer_nodes) = parse_optional::<String>(cli_args, "proposer_nodes")? {
config.proposer_nodes = proposer_nodes
.split(',')
.map(SensitiveUrl::parse)
.collect::<Result<_, _>>()
.map_err(|e| format!("Unable to parse proposer node URL: {:?}", e))?;
}
if cli_args.is_present("delete-lockfiles") {
warn!(
log,
@@ -194,7 +205,13 @@ impl Config {
);
}
config.allow_unsynced_beacon_node = cli_args.is_present("allow-unsynced");
if cli_args.is_present("allow-unsynced") {
warn!(
log,
"The --allow-unsynced flag is deprecated";
"msg" => "it no longer has any effect",
);
}
config.disable_run_on_all = cli_args.is_present("disable-run-on-all");
config.disable_auto_discover = cli_args.is_present("disable-auto-discover");
config.init_slashing_protection = cli_args.is_present("init-slashing-protection");

View File

@@ -16,12 +16,15 @@ use crate::{
validator_store::{DoppelgangerStatus, Error as ValidatorStoreError, ValidatorStore},
};
use environment::RuntimeContext;
use eth2::types::{AttesterData, BeaconCommitteeSubscription, ProposerData, StateId, ValidatorId};
use eth2::types::{
AttesterData, BeaconCommitteeSubscription, DutiesResponse, ProposerData, StateId, ValidatorId,
};
use futures::{stream, StreamExt};
use parking_lot::RwLock;
use safe_arith::ArithError;
use slog::{debug, error, info, warn, Logger};
use slot_clock::SlotClock;
use std::cmp::min;
use std::collections::{hash_map, BTreeMap, HashMap, HashSet};
use std::sync::Arc;
use std::time::Duration;
@@ -54,6 +57,11 @@ const SELECTION_PROOF_SCHEDULE_DENOM: u32 = 2;
/// flag in the cli to enable collection of per validator metrics.
const VALIDATOR_METRICS_MIN_COUNT: usize = 64;
/// The number of validators to request duty information for in the initial request.
/// The initial request is used to determine if further requests are required, so that it
/// reduces the amount of data that needs to be transferred.
const INITIAL_DUTIES_QUERY_SIZE: usize = 1;
#[derive(Debug)]
pub enum Error {
UnableToReadSlotClock,
@@ -139,11 +147,6 @@ pub struct DutiesService<T, E: EthSpec> {
pub slot_clock: T,
/// Provides HTTP access to remote beacon nodes.
pub beacon_nodes: Arc<BeaconNodeFallback<T, E>>,
/// Controls whether or not this function will refuse to interact with non-synced beacon nodes.
///
/// This functionality is a little redundant since most BNs will likely reject duties when they
/// aren't synced, but we keep it around for an emergency.
pub require_synced: RequireSynced,
pub enable_high_validator_count_metrics: bool,
pub context: RuntimeContext<E>,
pub spec: ChainSpec,
@@ -413,7 +416,7 @@ async fn poll_validator_indices<T: SlotClock + 'static, E: EthSpec>(
let download_result = duties_service
.beacon_nodes
.first_success(
duties_service.require_synced,
RequireSynced::No,
OfflineOnFailure::Yes,
|beacon_node| async move {
let _timer = metrics::start_timer_vec(
@@ -531,7 +534,6 @@ async fn poll_beacon_attesters<T: SlotClock + 'static, E: EthSpec>(
current_epoch,
&local_indices,
&local_pubkeys,
current_slot,
)
.await
{
@@ -544,6 +546,8 @@ async fn poll_beacon_attesters<T: SlotClock + 'static, E: EthSpec>(
)
}
update_per_validator_duty_metrics::<T, E>(duties_service, current_epoch, current_slot);
drop(current_epoch_timer);
let next_epoch_timer = metrics::start_timer_vec(
&metrics::DUTIES_SERVICE_TIMES,
@@ -551,14 +555,9 @@ async fn poll_beacon_attesters<T: SlotClock + 'static, E: EthSpec>(
);
// Download the duties and update the duties for the next epoch.
if let Err(e) = poll_beacon_attesters_for_epoch(
duties_service,
next_epoch,
&local_indices,
&local_pubkeys,
current_slot,
)
.await
if let Err(e) =
poll_beacon_attesters_for_epoch(duties_service, next_epoch, &local_indices, &local_pubkeys)
.await
{
error!(
log,
@@ -569,6 +568,8 @@ async fn poll_beacon_attesters<T: SlotClock + 'static, E: EthSpec>(
)
}
update_per_validator_duty_metrics::<T, E>(duties_service, next_epoch, current_slot);
drop(next_epoch_timer);
let subscriptions_timer =
metrics::start_timer_vec(&metrics::DUTIES_SERVICE_TIMES, &[metrics::SUBSCRIPTIONS]);
@@ -612,7 +613,7 @@ async fn poll_beacon_attesters<T: SlotClock + 'static, E: EthSpec>(
if let Err(e) = duties_service
.beacon_nodes
.run(
duties_service.require_synced,
RequireSynced::No,
OfflineOnFailure::Yes,
|beacon_node| async move {
let _timer = metrics::start_timer_vec(
@@ -655,7 +656,6 @@ async fn poll_beacon_attesters_for_epoch<T: SlotClock + 'static, E: EthSpec>(
epoch: Epoch,
local_indices: &[u64],
local_pubkeys: &HashSet<PublicKeyBytes>,
current_slot: Slot,
) -> Result<(), Error> {
let log = duties_service.context.log();
@@ -674,84 +674,69 @@ async fn poll_beacon_attesters_for_epoch<T: SlotClock + 'static, E: EthSpec>(
&[metrics::UPDATE_ATTESTERS_FETCH],
);
let response = duties_service
.beacon_nodes
.first_success(
duties_service.require_synced,
OfflineOnFailure::Yes,
|beacon_node| async move {
let _timer = metrics::start_timer_vec(
&metrics::DUTIES_SERVICE_TIMES,
&[metrics::ATTESTER_DUTIES_HTTP_POST],
);
beacon_node
.post_validator_duties_attester(epoch, local_indices)
.await
},
)
.await
.map_err(|e| Error::FailedToDownloadAttesters(e.to_string()))?;
// Request duties for all uninitialized validators. If there isn't any, we will just request for
// `INITIAL_DUTIES_QUERY_SIZE` validators. We use the `dependent_root` in the response to
// determine whether validator duties need to be updated. This is to ensure that we don't
// request for extra data unless necessary in order to save on network bandwidth.
let uninitialized_validators =
get_uninitialized_validators(duties_service, &epoch, local_pubkeys);
let indices_to_request = if !uninitialized_validators.is_empty() {
uninitialized_validators.as_slice()
} else {
&local_indices[0..min(INITIAL_DUTIES_QUERY_SIZE, local_indices.len())]
};
let response =
post_validator_duties_attester(duties_service, epoch, indices_to_request).await?;
let dependent_root = response.dependent_root;
// Find any validators which have conflicting (epoch, dependent_root) values or missing duties for the epoch.
let validators_to_update: Vec<_> = {
// Avoid holding the read-lock for any longer than required.
let attesters = duties_service.attesters.read();
local_pubkeys
.iter()
.filter(|pubkey| {
attesters.get(pubkey).map_or(true, |duties| {
duties
.get(&epoch)
.map_or(true, |(prior, _)| *prior != dependent_root)
})
})
.collect::<Vec<_>>()
};
if validators_to_update.is_empty() {
// No validators have conflicting (epoch, dependent_root) values or missing duties for the epoch.
return Ok(());
}
// Filter out validators which have already been requested.
let initial_duties = &response.data;
let indices_to_request = validators_to_update
.iter()
.filter(|&&&pubkey| !initial_duties.iter().any(|duty| duty.pubkey == pubkey))
.filter_map(|pubkey| duties_service.validator_store.validator_index(pubkey))
.collect::<Vec<_>>();
let new_duties = if !indices_to_request.is_empty() {
post_validator_duties_attester(duties_service, epoch, indices_to_request.as_slice())
.await?
.data
.into_iter()
.chain(response.data)
.collect::<Vec<_>>()
} else {
response.data
};
drop(fetch_timer);
let _store_timer = metrics::start_timer_vec(
&metrics::DUTIES_SERVICE_TIMES,
&[metrics::UPDATE_ATTESTERS_STORE],
);
let dependent_root = response.dependent_root;
// Filter any duties that are not relevant or already known.
let new_duties = {
// Avoid holding the read-lock for any longer than required.
let attesters = duties_service.attesters.read();
response
.data
.into_iter()
.filter(|duty| {
if duties_service.per_validator_metrics() {
let validator_index = duty.validator_index;
let duty_slot = duty.slot;
if let Some(existing_slot_gauge) =
get_int_gauge(&ATTESTATION_DUTY, &[&validator_index.to_string()])
{
let existing_slot = Slot::new(existing_slot_gauge.get() as u64);
let existing_epoch = existing_slot.epoch(E::slots_per_epoch());
// First condition ensures that we switch to the next epoch duty slot
// once the current epoch duty slot passes.
// Second condition is to ensure that next epoch duties don't override
// current epoch duties.
if existing_slot < current_slot
|| (duty_slot.epoch(E::slots_per_epoch()) <= existing_epoch
&& duty_slot > current_slot
&& duty_slot != existing_slot)
{
existing_slot_gauge.set(duty_slot.as_u64() as i64);
}
} else {
set_int_gauge(
&ATTESTATION_DUTY,
&[&validator_index.to_string()],
duty_slot.as_u64() as i64,
);
}
}
local_pubkeys.contains(&duty.pubkey) && {
// Only update the duties if either is true:
//
// - There were no known duties for this epoch.
// - The dependent root has changed, signalling a re-org.
attesters.get(&duty.pubkey).map_or(true, |duties| {
duties
.get(&epoch)
.map_or(true, |(prior, _)| *prior != dependent_root)
})
}
})
.collect::<Vec<_>>()
};
debug!(
log,
"Downloaded attester duties";
@@ -799,6 +784,89 @@ async fn poll_beacon_attesters_for_epoch<T: SlotClock + 'static, E: EthSpec>(
Ok(())
}
/// Get a filtered list of local validators for which we don't already know their duties for that epoch
fn get_uninitialized_validators<T: SlotClock + 'static, E: EthSpec>(
duties_service: &Arc<DutiesService<T, E>>,
epoch: &Epoch,
local_pubkeys: &HashSet<PublicKeyBytes>,
) -> Vec<u64> {
let attesters = duties_service.attesters.read();
local_pubkeys
.iter()
.filter(|pubkey| {
attesters
.get(pubkey)
.map_or(true, |duties| !duties.contains_key(epoch))
})
.filter_map(|pubkey| duties_service.validator_store.validator_index(pubkey))
.collect::<Vec<_>>()
}
fn update_per_validator_duty_metrics<T: SlotClock + 'static, E: EthSpec>(
duties_service: &Arc<DutiesService<T, E>>,
epoch: Epoch,
current_slot: Slot,
) {
if duties_service.per_validator_metrics() {
let attesters = duties_service.attesters.read();
attesters.values().for_each(|attester_duties_by_epoch| {
if let Some((_, duty_and_proof)) = attester_duties_by_epoch.get(&epoch) {
let duty = &duty_and_proof.duty;
let validator_index = duty.validator_index;
let duty_slot = duty.slot;
if let Some(existing_slot_gauge) =
get_int_gauge(&ATTESTATION_DUTY, &[&validator_index.to_string()])
{
let existing_slot = Slot::new(existing_slot_gauge.get() as u64);
let existing_epoch = existing_slot.epoch(E::slots_per_epoch());
// First condition ensures that we switch to the next epoch duty slot
// once the current epoch duty slot passes.
// Second condition is to ensure that next epoch duties don't override
// current epoch duties.
if existing_slot < current_slot
|| (duty_slot.epoch(E::slots_per_epoch()) <= existing_epoch
&& duty_slot > current_slot
&& duty_slot != existing_slot)
{
existing_slot_gauge.set(duty_slot.as_u64() as i64);
}
} else {
set_int_gauge(
&ATTESTATION_DUTY,
&[&validator_index.to_string()],
duty_slot.as_u64() as i64,
);
}
}
});
}
}
async fn post_validator_duties_attester<T: SlotClock + 'static, E: EthSpec>(
duties_service: &Arc<DutiesService<T, E>>,
epoch: Epoch,
validator_indices: &[u64],
) -> Result<DutiesResponse<Vec<AttesterData>>, Error> {
duties_service
.beacon_nodes
.first_success(
RequireSynced::No,
OfflineOnFailure::Yes,
|beacon_node| async move {
let _timer = metrics::start_timer_vec(
&metrics::DUTIES_SERVICE_TIMES,
&[metrics::ATTESTER_DUTIES_HTTP_POST],
);
beacon_node
.post_validator_duties_attester(epoch, validator_indices)
.await
},
)
.await
.map_err(|e| Error::FailedToDownloadAttesters(e.to_string()))
}
/// Compute the attestation selection proofs for the `duties` and add them to the `attesters` map.
///
/// Duties are computed in batches each slot. If a re-org is detected then the process will
@@ -990,7 +1058,7 @@ async fn poll_beacon_proposers<T: SlotClock + 'static, E: EthSpec>(
let download_result = duties_service
.beacon_nodes
.first_success(
duties_service.require_synced,
RequireSynced::No,
OfflineOnFailure::Yes,
|beacon_node| async move {
let _timer = metrics::start_timer_vec(

View File

@@ -1,4 +1,4 @@
use crate::beacon_node_fallback::OfflineOnFailure;
use crate::beacon_node_fallback::{OfflineOnFailure, RequireSynced};
use crate::{
doppelganger_service::DoppelgangerStatus,
duties_service::{DutiesService, Error},
@@ -422,7 +422,7 @@ pub async fn poll_sync_committee_duties_for_period<T: SlotClock + 'static, E: Et
let duties_response = duties_service
.beacon_nodes
.first_success(
duties_service.require_synced,
RequireSynced::No,
OfflineOnFailure::Yes,
|beacon_node| async move {
beacon_node

View File

@@ -60,7 +60,7 @@ impl ApiSecret {
// Create and write the secret key to file with appropriate permissions
create_with_600_perms(
&sk_path,
eth2_serde_utils::hex::encode(sk.serialize()).as_bytes(),
serde_utils::hex::encode(sk.serialize()).as_bytes(),
)
.map_err(|e| {
format!(
@@ -75,7 +75,7 @@ impl ApiSecret {
format!(
"{}{}",
PK_PREFIX,
eth2_serde_utils::hex::encode(&pk.serialize_compressed()[..])
serde_utils::hex::encode(&pk.serialize_compressed()[..])
)
.as_bytes(),
)
@@ -90,7 +90,7 @@ impl ApiSecret {
let sk = fs::read(&sk_path)
.map_err(|e| format!("cannot read {}: {}", SK_FILENAME, e))
.and_then(|bytes| {
eth2_serde_utils::hex::decode(&String::from_utf8_lossy(&bytes))
serde_utils::hex::decode(&String::from_utf8_lossy(&bytes))
.map_err(|_| format!("{} should be 0x-prefixed hex", PK_FILENAME))
})
.and_then(|bytes| {
@@ -114,7 +114,7 @@ impl ApiSecret {
let hex =
String::from_utf8(bytes).map_err(|_| format!("{} is not utf8", SK_FILENAME))?;
if let Some(stripped) = hex.strip_prefix(PK_PREFIX) {
eth2_serde_utils::hex::decode(stripped)
serde_utils::hex::decode(stripped)
.map_err(|_| format!("{} should be 0x-prefixed hex", SK_FILENAME))
} else {
Err(format!("unable to parse {}", SK_FILENAME))
@@ -153,7 +153,7 @@ impl ApiSecret {
/// Returns the public key of `self` as a 0x-prefixed hex string.
fn pubkey_string(&self) -> String {
eth2_serde_utils::hex::encode(&self.pk.serialize_compressed()[..])
serde_utils::hex::encode(&self.pk.serialize_compressed()[..])
}
/// Returns the API token.
@@ -205,7 +205,7 @@ impl ApiSecret {
let message =
Message::parse_slice(digest(&SHA256, input).as_ref()).expect("sha256 is 32 bytes");
let (signature, _) = libsecp256k1::sign(&message, &sk);
eth2_serde_utils::hex::encode(signature.serialize_der().as_ref())
serde_utils::hex::encode(signature.serialize_der().as_ref())
}
}
}

View File

@@ -159,7 +159,7 @@ pub async fn create_validators_mnemonic<P: AsRef<Path>, T: 'static + SlotClock,
gas_limit: request.gas_limit,
builder_proposals: request.builder_proposals,
voting_pubkey,
eth1_deposit_tx_data: eth2_serde_utils::hex::encode(&eth1_deposit_data.rlp),
eth1_deposit_tx_data: serde_utils::hex::encode(&eth1_deposit_data.rlp),
deposit_gwei: request.deposit_gwei,
});
}

View File

@@ -18,6 +18,7 @@ use eth2::lighthouse_vc::{
types::{self as api_types, GenericResponse, Graffiti, PublicKey, PublicKeyBytes},
};
use lighthouse_version::version_with_platform;
use logging::SSELoggingComponents;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use slog::{crit, info, warn, Logger};
@@ -31,6 +32,7 @@ use std::sync::Arc;
use sysinfo::{System, SystemExt};
use system_health::observe_system_health_vc;
use task_executor::TaskExecutor;
use tokio_stream::{wrappers::BroadcastStream, StreamExt};
use types::{ChainSpec, ConfigAndPreset, EthSpec};
use validator_dir::Builder as ValidatorDirBuilder;
use warp::{
@@ -39,6 +41,7 @@ use warp::{
response::Response,
StatusCode,
},
sse::Event,
Filter,
};
@@ -73,6 +76,7 @@ pub struct Context<T: SlotClock, E: EthSpec> {
pub spec: ChainSpec,
pub config: Config,
pub log: Logger,
pub sse_logging_components: Option<SSELoggingComponents>,
pub slot_clock: T,
pub _phantom: PhantomData<E>,
}
@@ -201,6 +205,10 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
let api_token_path_inner = api_token_path.clone();
let api_token_path_filter = warp::any().map(move || api_token_path_inner.clone());
// Filter for SEE Logging events
let inner_components = ctx.sse_logging_components.clone();
let sse_component_filter = warp::any().map(move || inner_components.clone());
// Create a `warp` filter that provides access to local system information.
let system_info = Arc::new(RwLock::new(sysinfo::System::new()));
{
@@ -1021,6 +1029,49 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
})
});
// Subscribe to get VC logs via Server side events
// /lighthouse/logs
let get_log_events = warp::path("lighthouse")
.and(warp::path("logs"))
.and(warp::path::end())
.and(sse_component_filter)
.and_then(|sse_component: Option<SSELoggingComponents>| {
warp_utils::task::blocking_task(move || {
if let Some(logging_components) = sse_component {
// Build a JSON stream
let s =
BroadcastStream::new(logging_components.sender.subscribe()).map(|msg| {
match msg {
Ok(data) => {
// Serialize to json
match data.to_json_string() {
// Send the json as a Server Sent Event
Ok(json) => Event::default().json_data(json).map_err(|e| {
warp_utils::reject::server_sent_event_error(format!(
"{:?}",
e
))
}),
Err(e) => Err(warp_utils::reject::server_sent_event_error(
format!("Unable to serialize to JSON {}", e),
)),
}
}
Err(e) => Err(warp_utils::reject::server_sent_event_error(
format!("Unable to receive event {}", e),
)),
}
});
Ok::<_, warp::Rejection>(warp::sse::reply(warp::sse::keep_alive().stream(s)))
} else {
Err(warp_utils::reject::custom_server_error(
"SSE Logging is not enabled".to_string(),
))
}
})
});
let routes = warp::any()
.and(authorization_header_filter)
// Note: it is critical that the `authorization_header_filter` is applied to all routes.
@@ -1061,8 +1112,8 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
.or(delete_std_remotekeys),
)),
)
// The auth route is the only route that is allowed to be accessed without the API token.
.or(warp::get().and(get_auth))
// The auth route and logs are the only routes that are allowed to be accessed without the API token.
.or(warp::get().and(get_auth.or(get_log_events.boxed())))
// Maps errors into HTTP responses.
.recover(warp_utils::reject::handle_rejection)
// Add a `Server` header.

View File

@@ -134,7 +134,8 @@ impl ApiTester {
listen_port: 0,
allow_origin: None,
},
log: log.clone(),
sse_logging_components: None,
log,
slot_clock: slot_clock.clone(),
_phantom: PhantomData,
});
@@ -365,7 +366,7 @@ impl ApiTester {
let withdrawal_keypair = keypairs.withdrawal.decrypt_keypair(PASSWORD_BYTES).unwrap();
let deposit_bytes =
eth2_serde_utils::hex::decode(&response[i].eth1_deposit_tx_data).unwrap();
serde_utils::hex::decode(&response[i].eth1_deposit_tx_data).unwrap();
let (deposit_data, _) =
decode_eth1_tx_data(&deposit_bytes, E::default_spec().max_effective_balance)

View File

@@ -24,6 +24,7 @@ pub use config::Config;
use initialized_validators::InitializedValidators;
use lighthouse_metrics::set_gauge;
use monitoring_api::{MonitoringHttpClient, ProcessType};
use sensitive_url::SensitiveUrl;
pub use slashing_protection::{SlashingDatabase, SLASHING_PROTECTION_FILENAME};
use crate::beacon_node_fallback::{
@@ -263,60 +264,70 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
.checked_sub(1)
.ok_or_else(|| "No beacon nodes defined.".to_string())?;
let beacon_node_setup = |x: (usize, &SensitiveUrl)| {
let i = x.0;
let url = x.1;
let slot_duration = Duration::from_secs(context.eth2_config.spec.seconds_per_slot);
let mut beacon_node_http_client_builder = ClientBuilder::new();
// Add new custom root certificates if specified.
if let Some(certificates) = &config.beacon_nodes_tls_certs {
for cert in certificates {
beacon_node_http_client_builder = beacon_node_http_client_builder
.add_root_certificate(load_pem_certificate(cert)?);
}
}
let beacon_node_http_client = beacon_node_http_client_builder
// Set default timeout to be the full slot duration.
.timeout(slot_duration)
.build()
.map_err(|e| format!("Unable to build HTTP client: {:?}", e))?;
// Use quicker timeouts if a fallback beacon node exists.
let timeouts = if i < last_beacon_node_index && !config.use_long_timeouts {
info!(
log,
"Fallback endpoints are available, using optimized timeouts.";
);
Timeouts {
attestation: slot_duration / HTTP_ATTESTATION_TIMEOUT_QUOTIENT,
attester_duties: slot_duration / HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT,
liveness: slot_duration / HTTP_LIVENESS_TIMEOUT_QUOTIENT,
proposal: slot_duration / HTTP_PROPOSAL_TIMEOUT_QUOTIENT,
proposer_duties: slot_duration / HTTP_PROPOSER_DUTIES_TIMEOUT_QUOTIENT,
sync_committee_contribution: slot_duration
/ HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT,
sync_duties: slot_duration / HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT,
get_beacon_blocks_ssz: slot_duration
/ HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT,
get_debug_beacon_states: slot_duration / HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT,
get_deposit_snapshot: slot_duration / HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT,
}
} else {
Timeouts::set_all(slot_duration)
};
Ok(BeaconNodeHttpClient::from_components(
url.clone(),
beacon_node_http_client,
timeouts,
))
};
let beacon_nodes: Vec<BeaconNodeHttpClient> = config
.beacon_nodes
.iter()
.enumerate()
.map(|(i, url)| {
let slot_duration = Duration::from_secs(context.eth2_config.spec.seconds_per_slot);
.map(beacon_node_setup)
.collect::<Result<Vec<BeaconNodeHttpClient>, String>>()?;
let mut beacon_node_http_client_builder = ClientBuilder::new();
// Add new custom root certificates if specified.
if let Some(certificates) = &config.beacon_nodes_tls_certs {
for cert in certificates {
beacon_node_http_client_builder = beacon_node_http_client_builder
.add_root_certificate(load_pem_certificate(cert)?);
}
}
let beacon_node_http_client = beacon_node_http_client_builder
// Set default timeout to be the full slot duration.
.timeout(slot_duration)
.build()
.map_err(|e| format!("Unable to build HTTP client: {:?}", e))?;
// Use quicker timeouts if a fallback beacon node exists.
let timeouts = if i < last_beacon_node_index && !config.use_long_timeouts {
info!(
log,
"Fallback endpoints are available, using optimized timeouts.";
);
Timeouts {
attestation: slot_duration / HTTP_ATTESTATION_TIMEOUT_QUOTIENT,
attester_duties: slot_duration / HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT,
liveness: slot_duration / HTTP_LIVENESS_TIMEOUT_QUOTIENT,
proposal: slot_duration / HTTP_PROPOSAL_TIMEOUT_QUOTIENT,
proposer_duties: slot_duration / HTTP_PROPOSER_DUTIES_TIMEOUT_QUOTIENT,
sync_committee_contribution: slot_duration
/ HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT,
sync_duties: slot_duration / HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT,
get_beacon_blocks_ssz: slot_duration
/ HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT,
get_debug_beacon_states: slot_duration
/ HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT,
get_deposit_snapshot: slot_duration / HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT,
}
} else {
Timeouts::set_all(slot_duration)
};
Ok(BeaconNodeHttpClient::from_components(
url.clone(),
beacon_node_http_client,
timeouts,
))
})
let proposer_nodes: Vec<BeaconNodeHttpClient> = config
.proposer_nodes
.iter()
.enumerate()
.map(beacon_node_setup)
.collect::<Result<Vec<BeaconNodeHttpClient>, String>>()?;
let num_nodes = beacon_nodes.len();
@@ -325,6 +336,12 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
.map(CandidateBeaconNode::new)
.collect();
let proposer_nodes_num = proposer_nodes.len();
let proposer_candidates = proposer_nodes
.into_iter()
.map(CandidateBeaconNode::new)
.collect();
// Set the count for beacon node fallbacks excluding the primary beacon node.
set_gauge(
&http_metrics::metrics::ETH2_FALLBACK_CONFIGURED,
@@ -349,9 +366,16 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
log.clone(),
);
let mut proposer_nodes: BeaconNodeFallback<_, T> = BeaconNodeFallback::new(
proposer_candidates,
config.disable_run_on_all,
context.eth2_config.spec.clone(),
log.clone(),
);
// Perform some potentially long-running initialization tasks.
let (genesis_time, genesis_validators_root) = tokio::select! {
tuple = init_from_beacon_node(&beacon_nodes, &context) => tuple?,
tuple = init_from_beacon_node(&beacon_nodes, &proposer_nodes, &context) => tuple?,
() = context.executor.exit() => return Err("Shutting down".to_string())
};
@@ -367,9 +391,14 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
);
beacon_nodes.set_slot_clock(slot_clock.clone());
proposer_nodes.set_slot_clock(slot_clock.clone());
let beacon_nodes = Arc::new(beacon_nodes);
start_fallback_updater_service(context.clone(), beacon_nodes.clone())?;
let proposer_nodes = Arc::new(proposer_nodes);
start_fallback_updater_service(context.clone(), proposer_nodes.clone())?;
let doppelganger_service = if config.enable_doppelganger_protection {
Some(Arc::new(DoppelgangerService::new(
context
@@ -417,11 +446,6 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
slot_clock: slot_clock.clone(),
beacon_nodes: beacon_nodes.clone(),
validator_store: validator_store.clone(),
require_synced: if config.allow_unsynced_beacon_node {
RequireSynced::Yes
} else {
RequireSynced::No
},
spec: context.eth2_config.spec.clone(),
context: duties_context,
enable_high_validator_count_metrics: config.enable_high_validator_count_metrics,
@@ -433,15 +457,21 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
ctx.shared.write().duties_service = Some(duties_service.clone());
}
let block_service = BlockServiceBuilder::new()
let mut block_service_builder = BlockServiceBuilder::new()
.slot_clock(slot_clock.clone())
.validator_store(validator_store.clone())
.beacon_nodes(beacon_nodes.clone())
.runtime_context(context.service_context("block".into()))
.graffiti(config.graffiti)
.graffiti_file(config.graffiti_file.clone())
.block_delay(config.block_delay)
.build()?;
.block_delay(config.block_delay);
// If we have proposer nodes, add them to the block service builder.
if proposer_nodes_num > 0 {
block_service_builder = block_service_builder.proposer_nodes(proposer_nodes.clone());
}
let block_service = block_service_builder.build()?;
let attestation_service = AttestationServiceBuilder::new()
.duties_service(duties_service.clone())
@@ -546,6 +576,7 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
graffiti_flag: self.config.graffiti,
spec: self.context.eth2_config.spec.clone(),
config: self.config.http_api.clone(),
sse_logging_components: self.context.sse_logging_components.clone(),
slot_clock: self.slot_clock.clone(),
log: log.clone(),
_phantom: PhantomData,
@@ -581,13 +612,32 @@ impl<T: EthSpec> ProductionValidatorClient<T> {
async fn init_from_beacon_node<E: EthSpec>(
beacon_nodes: &BeaconNodeFallback<SystemTimeSlotClock, E>,
proposer_nodes: &BeaconNodeFallback<SystemTimeSlotClock, E>,
context: &RuntimeContext<E>,
) -> Result<(u64, Hash256), String> {
loop {
beacon_nodes.update_unready_candidates().await;
beacon_nodes.update_all_candidates().await;
proposer_nodes.update_all_candidates().await;
let num_available = beacon_nodes.num_available().await;
let num_total = beacon_nodes.num_total();
if num_available > 0 {
let proposer_available = beacon_nodes.num_available().await;
let proposer_total = beacon_nodes.num_total();
if proposer_total > 0 && proposer_available == 0 {
warn!(
context.log(),
"Unable to connect to a proposer node";
"retry in" => format!("{} seconds", RETRY_DELAY.as_secs()),
"total_proposers" => proposer_total,
"available_proposers" => proposer_available,
"total_beacon_nodes" => num_total,
"available_beacon_nodes" => num_available,
);
}
if num_available > 0 && proposer_available == 0 {
info!(
context.log(),
"Initialized beacon node connections";
@@ -595,6 +645,16 @@ async fn init_from_beacon_node<E: EthSpec>(
"available" => num_available,
);
break;
} else if num_available > 0 {
info!(
context.log(),
"Initialized beacon node connections";
"total" => num_total,
"available" => num_available,
"proposers_available" => proposer_available,
"proposers_total" => proposer_total,
);
break;
} else {
warn!(
context.log(),

View File

@@ -332,7 +332,7 @@ impl<T: SlotClock + 'static, E: EthSpec> PreparationService<T, E> {
match self
.beacon_nodes
.run(
RequireSynced::Yes,
RequireSynced::No,
OfflineOnFailure::Yes,
|beacon_node| async move {
beacon_node
@@ -451,7 +451,7 @@ impl<T: SlotClock + 'static, E: EthSpec> PreparationService<T, E> {
match self
.beacon_nodes
.first_success(
RequireSynced::Yes,
RequireSynced::No,
OfflineOnFailure::No,
|beacon_node| async move {
beacon_node.post_validator_register_validator(batch).await

View File

@@ -55,9 +55,9 @@ pub enum Web3SignerObject<'a, T: EthSpec, Payload: AbstractExecPayload<T>> {
Deposit {
pubkey: PublicKeyBytes,
withdrawal_credentials: Hash256,
#[serde(with = "eth2_serde_utils::quoted_u64")]
#[serde(with = "serde_utils::quoted_u64")]
amount: u64,
#[serde(with = "eth2_serde_utils::bytes_4_hex")]
#[serde(with = "serde_utils::bytes_4_hex")]
genesis_fork_version: [u8; 4],
},
RandaoReveal {

View File

@@ -178,7 +178,7 @@ impl<T: SlotClock + 'static, E: EthSpec> SyncCommitteeService<T, E> {
let response = self
.beacon_nodes
.first_success(
RequireSynced::Yes,
RequireSynced::No,
OfflineOnFailure::Yes,
|beacon_node| async move {
match beacon_node.get_beacon_blocks_root(BlockId::Head).await {