mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-03 00:31:50 +00:00
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:
@@ -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" }
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.")
|
||||
)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(ð1_deposit_data.rlp),
|
||||
eth1_deposit_tx_data: serde_utils::hex::encode(ð1_deposit_data.rlp),
|
||||
deposit_gwei: request.deposit_gwei,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user