mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-21 23:08:23 +00:00
Merge remote-tracking branch 'origin/unstable' into dvt
This commit is contained in:
@@ -13,6 +13,7 @@ clap = { workspace = true }
|
||||
eth2 = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
sensitive_url = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
slot_clock = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
|
||||
@@ -8,8 +8,9 @@ use beacon_node_health::{
|
||||
IsOptimistic, SyncDistanceTier,
|
||||
};
|
||||
use clap::ValueEnum;
|
||||
use eth2::BeaconNodeHttpClient;
|
||||
use eth2::{BeaconNodeHttpClient, Timeouts};
|
||||
use futures::future;
|
||||
use sensitive_url::SensitiveUrl;
|
||||
use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
|
||||
use slot_clock::SlotClock;
|
||||
use std::cmp::Ordering;
|
||||
@@ -455,6 +456,39 @@ impl<T: SlotClock> BeaconNodeFallback<T> {
|
||||
(candidate_info, num_available, num_synced)
|
||||
}
|
||||
|
||||
/// Update the list of candidates with a new list.
|
||||
/// Returns `Ok(new_list)` if the update was successful.
|
||||
/// Returns `Err(some_err)` if the list is empty.
|
||||
pub async fn update_candidates_list(
|
||||
&self,
|
||||
new_list: Vec<SensitiveUrl>,
|
||||
use_long_timeouts: bool,
|
||||
) -> Result<Vec<SensitiveUrl>, String> {
|
||||
if new_list.is_empty() {
|
||||
return Err("list cannot be empty".to_string());
|
||||
}
|
||||
|
||||
let timeouts: Timeouts = if new_list.len() == 1 || use_long_timeouts {
|
||||
Timeouts::set_all(Duration::from_secs(self.spec.seconds_per_slot))
|
||||
} else {
|
||||
Timeouts::use_optimized_timeouts(Duration::from_secs(self.spec.seconds_per_slot))
|
||||
};
|
||||
|
||||
let new_candidates: Vec<CandidateBeaconNode> = new_list
|
||||
.clone()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, url)| {
|
||||
CandidateBeaconNode::new(BeaconNodeHttpClient::new(url, timeouts.clone()), index)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut candidates = self.candidates.write().await;
|
||||
*candidates = new_candidates;
|
||||
|
||||
Ok(new_list)
|
||||
}
|
||||
|
||||
/// Loop through ALL candidates in `self.candidates` and update their sync status.
|
||||
///
|
||||
/// It is possible for a node to return an unsynced status while continuing to serve
|
||||
@@ -482,12 +516,26 @@ impl<T: SlotClock> BeaconNodeFallback<T> {
|
||||
|
||||
for (result, node) in results {
|
||||
if let Err(e) = result {
|
||||
if *e != CandidateError::PreGenesis {
|
||||
warn!(
|
||||
error = ?e,
|
||||
endpoint = %node,
|
||||
"A connected beacon node errored during routine health check"
|
||||
);
|
||||
match e {
|
||||
// Avoid spamming warns before genesis.
|
||||
CandidateError::PreGenesis => {}
|
||||
// Uninitialized *should* only occur during start-up before the
|
||||
// slot clock has been initialized.
|
||||
// Seeing this log in any other circumstance would indicate a serious bug.
|
||||
CandidateError::Uninitialized => {
|
||||
debug!(
|
||||
error = ?e,
|
||||
endpoint = %node,
|
||||
"A connected beacon node is uninitialized"
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
warn!(
|
||||
error = ?e,
|
||||
endpoint = %node,
|
||||
"A connected beacon node errored during routine health check"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use account_utils::{
|
||||
};
|
||||
pub use api_secret::ApiSecret;
|
||||
use beacon_node_fallback::CandidateInfo;
|
||||
use core::convert::Infallible;
|
||||
use create_validator::{
|
||||
create_validators_mnemonic, create_validators_web3signer, get_voting_password_storage,
|
||||
};
|
||||
@@ -30,7 +31,7 @@ use eth2::lighthouse_vc::{
|
||||
std_types::{AuthResponse, GetFeeRecipientResponse, GetGasLimitResponse},
|
||||
types::{
|
||||
self as api_types, GenericResponse, GetGraffitiResponse, Graffiti, PublicKey,
|
||||
PublicKeyBytes, SetGraffitiRequest,
|
||||
PublicKeyBytes, SetGraffitiRequest, UpdateCandidatesRequest, UpdateCandidatesResponse,
|
||||
},
|
||||
};
|
||||
use health_metrics::observe::Observe;
|
||||
@@ -38,6 +39,7 @@ use lighthouse_version::version_with_platform;
|
||||
use logging::crit;
|
||||
use logging::SSELoggingComponents;
|
||||
use parking_lot::RwLock;
|
||||
use sensitive_url::SensitiveUrl;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slot_clock::SlotClock;
|
||||
use std::collections::HashMap;
|
||||
@@ -53,7 +55,8 @@ use tracing::{info, warn};
|
||||
use types::{ChainSpec, ConfigAndPreset, EthSpec};
|
||||
use validator_dir::Builder as ValidatorDirBuilder;
|
||||
use validator_services::block_service::BlockService;
|
||||
use warp::{sse::Event, Filter};
|
||||
use warp::{reply::Response, sse::Event, Filter};
|
||||
use warp_utils::reject::convert_rejection;
|
||||
use warp_utils::task::blocking_json_task;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -102,6 +105,7 @@ pub struct Config {
|
||||
pub allow_keystore_export: bool,
|
||||
pub store_passwords_in_secrets_dir: bool,
|
||||
pub http_token_path: PathBuf,
|
||||
pub bn_long_timeouts: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -121,6 +125,7 @@ impl Default for Config {
|
||||
allow_keystore_export: false,
|
||||
store_passwords_in_secrets_dir: false,
|
||||
http_token_path,
|
||||
bn_long_timeouts: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,6 +152,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
let config = &ctx.config;
|
||||
let allow_keystore_export = config.allow_keystore_export;
|
||||
let store_passwords_in_secrets_dir = config.store_passwords_in_secrets_dir;
|
||||
let use_long_timeouts = config.bn_long_timeouts;
|
||||
|
||||
// Configure CORS.
|
||||
let cors_builder = {
|
||||
@@ -839,6 +845,59 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
})
|
||||
});
|
||||
|
||||
// POST /lighthouse/beacon/update
|
||||
let post_lighthouse_beacon_update = warp::path("lighthouse")
|
||||
.and(warp::path("beacon"))
|
||||
.and(warp::path("update"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::body::json())
|
||||
.and(block_service_filter.clone())
|
||||
.then(
|
||||
move |request: UpdateCandidatesRequest,
|
||||
block_service: BlockService<LighthouseValidatorStore<T, E>, T>| async move {
|
||||
async fn parse_urls(urls: &[String]) -> Result<Vec<SensitiveUrl>, Response> {
|
||||
match urls
|
||||
.iter()
|
||||
.map(|url| SensitiveUrl::parse(url).map_err(|e| e.to_string()))
|
||||
.collect()
|
||||
{
|
||||
Ok(sensitive_urls) => Ok(sensitive_urls),
|
||||
Err(_) => Err(convert_rejection::<Infallible>(Err(
|
||||
warp_utils::reject::custom_bad_request(
|
||||
"one or more urls could not be parsed".to_string(),
|
||||
),
|
||||
))
|
||||
.await),
|
||||
}
|
||||
}
|
||||
|
||||
let beacons: Vec<SensitiveUrl> = match parse_urls(&request.beacon_nodes).await {
|
||||
Ok(new_beacons) => {
|
||||
match block_service
|
||||
.beacon_nodes
|
||||
.update_candidates_list(new_beacons, use_long_timeouts)
|
||||
.await
|
||||
{
|
||||
Ok(beacons) => beacons,
|
||||
Err(e) => {
|
||||
return convert_rejection::<Infallible>(Err(
|
||||
warp_utils::reject::custom_bad_request(e.to_string()),
|
||||
))
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
let response: UpdateCandidatesResponse = UpdateCandidatesResponse {
|
||||
new_beacon_nodes_list: beacons.iter().map(|surl| surl.to_string()).collect(),
|
||||
};
|
||||
|
||||
blocking_json_task(move || Ok(api_types::GenericResponse::from(response))).await
|
||||
},
|
||||
);
|
||||
|
||||
// Standard key-manager endpoints.
|
||||
let eth_v1 = warp::path("eth").and(warp::path("v1"));
|
||||
let std_keystores = eth_v1.and(warp::path("keystores")).and(warp::path::end());
|
||||
@@ -1316,6 +1375,7 @@ pub fn serve<T: 'static + SlotClock + Clone, E: EthSpec>(
|
||||
.or(post_std_keystores)
|
||||
.or(post_std_remotekeys)
|
||||
.or(post_graffiti)
|
||||
.or(post_lighthouse_beacon_update)
|
||||
.recover(warp_utils::reject::handle_rejection),
|
||||
))
|
||||
.or(warp::patch()
|
||||
|
||||
@@ -173,6 +173,7 @@ impl ApiTester {
|
||||
allow_keystore_export: true,
|
||||
store_passwords_in_secrets_dir: false,
|
||||
http_token_path: tempdir().unwrap().path().join(PK_FILENAME),
|
||||
bn_long_timeouts: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ impl ApiTester {
|
||||
allow_keystore_export: true,
|
||||
store_passwords_in_secrets_dir: false,
|
||||
http_token_path: token_path,
|
||||
bn_long_timeouts: false,
|
||||
},
|
||||
sse_logging_components: None,
|
||||
slot_clock: slot_clock.clone(),
|
||||
|
||||
@@ -159,7 +159,7 @@ pub struct InitializedValidator {
|
||||
|
||||
impl InitializedValidator {
|
||||
/// Return a reference to this validator's lockfile if it has one.
|
||||
pub fn keystore_lockfile(&self) -> Option<MappedMutexGuard<Lockfile>> {
|
||||
pub fn keystore_lockfile(&self) -> Option<MappedMutexGuard<'_, Lockfile>> {
|
||||
match self.signing_method.as_ref() {
|
||||
SigningMethod::LocalKeystore {
|
||||
ref voting_keystore_lockfile,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use account_utils::validator_definitions::{PasswordStorage, ValidatorDefinition};
|
||||
use doppelganger_service::DoppelgangerService;
|
||||
use eth2::types::PublishBlockRequest;
|
||||
use initialized_validators::InitializedValidators;
|
||||
use logging::crit;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
@@ -733,14 +734,18 @@ impl<T: SlotClock + 'static, E: EthSpec> ValidatorStore for LighthouseValidatorS
|
||||
current_slot: Slot,
|
||||
) -> Result<SignedBlock<E>, Error> {
|
||||
match block {
|
||||
UnsignedBlock::Full(block) => self
|
||||
.sign_abstract_block(validator_pubkey, block, current_slot)
|
||||
.await
|
||||
.map(SignedBlock::Full),
|
||||
UnsignedBlock::Full(block) => {
|
||||
let (block, blobs) = block.deconstruct();
|
||||
self.sign_abstract_block(validator_pubkey, block, current_slot)
|
||||
.await
|
||||
.map(|block| {
|
||||
SignedBlock::Full(PublishBlockRequest::new(Arc::new(block), blobs))
|
||||
})
|
||||
}
|
||||
UnsignedBlock::Blinded(block) => self
|
||||
.sign_abstract_block(validator_pubkey, block, current_slot)
|
||||
.await
|
||||
.map(SignedBlock::Blinded),
|
||||
.map(|block| SignedBlock::Blinded(Arc::new(block))),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ authors = ["Michael Sproul <michael@sigmaprime.io>", "pscott <scottpiriou@gmail.
|
||||
edition = { workspace = true }
|
||||
autotests = false
|
||||
|
||||
[[test]]
|
||||
name = "slashing_protection_tests"
|
||||
path = "tests/main.rs"
|
||||
[features]
|
||||
arbitrary-fuzz = ["types/arbitrary-fuzz"]
|
||||
portable = ["types/portable"]
|
||||
|
||||
[dependencies]
|
||||
arbitrary = { workspace = true, features = ["derive"] }
|
||||
@@ -25,6 +25,6 @@ types = { workspace = true }
|
||||
[dev-dependencies]
|
||||
rayon = { workspace = true }
|
||||
|
||||
[features]
|
||||
arbitrary-fuzz = ["types/arbitrary-fuzz"]
|
||||
portable = ["types/portable"]
|
||||
[[test]]
|
||||
name = "slashing_protection_tests"
|
||||
path = "tests/main.rs"
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
mod latency;
|
||||
mod notifier;
|
||||
|
||||
use crate::cli::ValidatorClient;
|
||||
use crate::duties_service::SelectionProofConfig;
|
||||
@@ -22,7 +20,6 @@ use environment::RuntimeContext;
|
||||
use eth2::{reqwest::ClientBuilder, BeaconNodeHttpClient, StatusCode, Timeouts};
|
||||
use initialized_validators::Error::UnableToOpenVotingKeystore;
|
||||
use lighthouse_validator_store::LighthouseValidatorStore;
|
||||
use notifier::spawn_notifier;
|
||||
use parking_lot::RwLock;
|
||||
use reqwest::Certificate;
|
||||
use slot_clock::SlotClock;
|
||||
@@ -40,10 +37,12 @@ use tokio::{
|
||||
use tracing::{debug, error, info, warn};
|
||||
use types::{EthSpec, Hash256};
|
||||
use validator_http_api::ApiSecret;
|
||||
use validator_services::notifier_service::spawn_notifier;
|
||||
use validator_services::{
|
||||
attestation_service::{AttestationService, AttestationServiceBuilder},
|
||||
block_service::{BlockService, BlockServiceBuilder},
|
||||
duties_service::{self, DutiesService, DutiesServiceBuilder},
|
||||
latency_service,
|
||||
preparation_service::{PreparationService, PreparationServiceBuilder},
|
||||
sync_committee_service::SyncCommitteeService,
|
||||
};
|
||||
@@ -55,24 +54,6 @@ const RETRY_DELAY: Duration = Duration::from_secs(2);
|
||||
/// The time between polls when waiting for genesis.
|
||||
const WAITING_FOR_GENESIS_POLL_TIME: Duration = Duration::from_secs(12);
|
||||
|
||||
/// Specific timeout constants for HTTP requests involved in different validator duties.
|
||||
/// This can help ensure that proper endpoint fallback occurs.
|
||||
const HTTP_ATTESTATION_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_ATTESTATION_SUBSCRIPTIONS_TIMEOUT_QUOTIENT: u32 = 24;
|
||||
const HTTP_ATTESTATION_AGGREGATOR_TIMEOUT_QUOTIENT: u32 = 24; // For DVT involving middleware only
|
||||
const HTTP_LIVENESS_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_PROPOSAL_TIMEOUT_QUOTIENT: u32 = 2;
|
||||
const HTTP_PROPOSER_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_SYNC_AGGREGATOR_TIMEOUT_QUOTIENT: u32 = 24; // For DVT involving middleware only
|
||||
const HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT: u32 = 4;
|
||||
const HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT: u32 = 4;
|
||||
const HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_DEFAULT_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
|
||||
const DOPPELGANGER_SERVICE_NAME: &str = "doppelganger";
|
||||
|
||||
/// Compute attestation selection proofs this many slots before they are required.
|
||||
@@ -106,7 +87,6 @@ pub struct ProductionValidatorClient<E: EthSpec> {
|
||||
slot_clock: SystemTimeSlotClock,
|
||||
http_api_listen_addr: Option<SocketAddr>,
|
||||
config: Config,
|
||||
beacon_nodes: Arc<BeaconNodeFallback<SystemTimeSlotClock>>,
|
||||
genesis_time: u64,
|
||||
}
|
||||
|
||||
@@ -311,27 +291,7 @@ impl<E: EthSpec> ProductionValidatorClient<E> {
|
||||
// Use quicker timeouts if a fallback beacon node exists.
|
||||
let timeouts = if i < last_beacon_node_index && !config.use_long_timeouts {
|
||||
info!("Fallback endpoints are available, using optimized timeouts.");
|
||||
Timeouts {
|
||||
attestation: slot_duration / HTTP_ATTESTATION_TIMEOUT_QUOTIENT,
|
||||
attester_duties: slot_duration / HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT,
|
||||
attestation_subscriptions: slot_duration
|
||||
/ HTTP_ATTESTATION_SUBSCRIPTIONS_TIMEOUT_QUOTIENT,
|
||||
attestation_aggregators: slot_duration
|
||||
/ HTTP_ATTESTATION_AGGREGATOR_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,
|
||||
sync_aggregators: slot_duration / HTTP_SYNC_AGGREGATOR_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,
|
||||
get_validator_block: slot_duration / HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT,
|
||||
default: slot_duration / HTTP_DEFAULT_TIMEOUT_QUOTIENT,
|
||||
}
|
||||
Timeouts::use_optimized_timeouts(slot_duration)
|
||||
} else {
|
||||
Timeouts::set_all(slot_duration.saturating_mul(config.long_timeouts_multiplier))
|
||||
};
|
||||
@@ -575,7 +535,6 @@ impl<E: EthSpec> ProductionValidatorClient<E> {
|
||||
slot_clock,
|
||||
http_api_listen_addr: None,
|
||||
genesis_time,
|
||||
beacon_nodes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -621,7 +580,7 @@ impl<E: EthSpec> ProductionValidatorClient<E> {
|
||||
};
|
||||
|
||||
// Wait until genesis has occurred.
|
||||
wait_for_genesis(&self.beacon_nodes, self.genesis_time).await?;
|
||||
wait_for_genesis(self.genesis_time).await?;
|
||||
|
||||
duties_service::start_update_service(self.duties_service.clone(), block_service_tx);
|
||||
|
||||
@@ -659,11 +618,17 @@ impl<E: EthSpec> ProductionValidatorClient<E> {
|
||||
info!("Doppelganger protection disabled.")
|
||||
}
|
||||
|
||||
spawn_notifier(self).map_err(|e| format!("Failed to start notifier: {}", e))?;
|
||||
let context = self.context.service_context("notifier".into());
|
||||
spawn_notifier(
|
||||
self.duties_service.clone(),
|
||||
context.executor,
|
||||
&self.context.eth2_config.spec,
|
||||
)
|
||||
.map_err(|e| format!("Failed to start notifier: {}", e))?;
|
||||
|
||||
if self.config.enable_latency_measurement_service {
|
||||
latency::start_latency_service(
|
||||
self.context.clone(),
|
||||
latency_service::start_latency_service(
|
||||
self.context.executor.clone(),
|
||||
self.duties_service.slot_clock.clone(),
|
||||
self.duties_service.beacon_nodes.clone(),
|
||||
);
|
||||
@@ -756,10 +721,7 @@ async fn init_from_beacon_node<E: EthSpec>(
|
||||
Ok((genesis.genesis_time, genesis.genesis_validators_root))
|
||||
}
|
||||
|
||||
async fn wait_for_genesis(
|
||||
beacon_nodes: &BeaconNodeFallback<SystemTimeSlotClock>,
|
||||
genesis_time: u64,
|
||||
) -> Result<(), String> {
|
||||
async fn wait_for_genesis(genesis_time: u64) -> Result<(), String> {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| format!("Unable to read system time: {:?}", e))?;
|
||||
@@ -779,7 +741,7 @@ async fn wait_for_genesis(
|
||||
// Start polling the node for pre-genesis information, cancelling the polling as soon as the
|
||||
// timer runs out.
|
||||
tokio::select! {
|
||||
result = poll_whilst_waiting_for_genesis(beacon_nodes, genesis_time) => result?,
|
||||
result = poll_whilst_waiting_for_genesis(genesis_time) => result?,
|
||||
() = sleep(genesis_time - now) => ()
|
||||
};
|
||||
|
||||
@@ -799,46 +761,20 @@ async fn wait_for_genesis(
|
||||
|
||||
/// Request the version from the node, looping back and trying again on failure. Exit once the node
|
||||
/// has been contacted.
|
||||
async fn poll_whilst_waiting_for_genesis(
|
||||
beacon_nodes: &BeaconNodeFallback<SystemTimeSlotClock>,
|
||||
genesis_time: Duration,
|
||||
) -> Result<(), String> {
|
||||
async fn poll_whilst_waiting_for_genesis(genesis_time: Duration) -> Result<(), String> {
|
||||
loop {
|
||||
match beacon_nodes
|
||||
.first_success(|beacon_node| async move { beacon_node.get_lighthouse_staking().await })
|
||||
.await
|
||||
{
|
||||
Ok(is_staking) => {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| format!("Unable to read system time: {:?}", e))?;
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| format!("Unable to read system time: {:?}", e))?;
|
||||
|
||||
if !is_staking {
|
||||
error!(
|
||||
msg = "this will caused missed duties",
|
||||
info = "see the --staking CLI flag on the beacon node",
|
||||
"Staking is disabled for beacon node"
|
||||
);
|
||||
}
|
||||
|
||||
if now < genesis_time {
|
||||
info!(
|
||||
bn_staking_enabled = is_staking,
|
||||
seconds_to_wait = (genesis_time - now).as_secs(),
|
||||
"Waiting for genesis"
|
||||
);
|
||||
} else {
|
||||
break Ok(());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
error = %e,
|
||||
"Error polling beacon node"
|
||||
);
|
||||
}
|
||||
if now < genesis_time {
|
||||
info!(
|
||||
seconds_to_wait = (genesis_time - now).as_secs(),
|
||||
"Waiting for genesis"
|
||||
);
|
||||
} else {
|
||||
break Ok(());
|
||||
}
|
||||
|
||||
sleep(WAITING_FOR_GENESIS_POLL_TIME).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ graffiti_file = { workspace = true }
|
||||
logging = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
safe_arith = { workspace = true }
|
||||
slot_clock = { workspace = true }
|
||||
slot_clock = { workspace = true }
|
||||
task_executor = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tree_hash = { workspace = true }
|
||||
types = { workspace = true }
|
||||
tree_hash = { workspace = true }
|
||||
types = { workspace = true }
|
||||
validator_metrics = { workspace = true }
|
||||
validator_store = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::duties_service::{DutiesService, DutyAndProof};
|
||||
use beacon_node_fallback::{ApiTopic, BeaconNodeFallback};
|
||||
use either::Either;
|
||||
use futures::future::join_all;
|
||||
use logging::crit;
|
||||
use slot_clock::SlotClock;
|
||||
@@ -461,40 +460,32 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> AttestationService<S,
|
||||
&validator_metrics::ATTESTATION_SERVICE_TIMES,
|
||||
&[validator_metrics::ATTESTATIONS_HTTP_POST],
|
||||
);
|
||||
if fork_name.electra_enabled() {
|
||||
let single_attestations = attestations
|
||||
.iter()
|
||||
.zip(validator_indices)
|
||||
.filter_map(|(a, i)| {
|
||||
match a.to_single_attestation_with_attester_index(*i) {
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
// This shouldn't happen unless BN and VC are out of sync with
|
||||
// respect to the Electra fork.
|
||||
error!(
|
||||
error = ?e,
|
||||
committee_index = attestation_data.index,
|
||||
slot = slot.as_u64(),
|
||||
"type" = "unaggregated",
|
||||
"Unable to convert to SingleAttestation"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
beacon_node
|
||||
.post_beacon_pool_attestations_v2::<S::E>(
|
||||
Either::Right(single_attestations),
|
||||
fork_name,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
beacon_node
|
||||
.post_beacon_pool_attestations_v1(attestations)
|
||||
.await
|
||||
}
|
||||
let single_attestations = attestations
|
||||
.iter()
|
||||
.zip(validator_indices)
|
||||
.filter_map(|(a, i)| {
|
||||
match a.to_single_attestation_with_attester_index(*i) {
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
// This shouldn't happen unless BN and VC are out of sync with
|
||||
// respect to the Electra fork.
|
||||
error!(
|
||||
error = ?e,
|
||||
committee_index = attestation_data.index,
|
||||
slot = slot.as_u64(),
|
||||
"type" = "unaggregated",
|
||||
"Unable to convert to SingleAttestation"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
beacon_node
|
||||
.post_beacon_pool_attestations_v2::<S::E>(single_attestations, fork_name)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -569,7 +560,7 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> AttestationService<S,
|
||||
format!("Failed to produce an aggregate attestation: {:?}", e)
|
||||
})?
|
||||
.ok_or_else(|| format!("No aggregate available for {:?}", attestation_data))
|
||||
.map(|result| result.data)
|
||||
.map(|result| result.into_data())
|
||||
} else {
|
||||
beacon_node
|
||||
.get_validator_aggregate_attestation_v1(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use beacon_node_fallback::{ApiTopic, BeaconNodeFallback, Error as FallbackError, Errors};
|
||||
use bls::SignatureBytes;
|
||||
use eth2::types::{FullBlockContents, PublishBlockRequest};
|
||||
use eth2::{BeaconNodeHttpClient, StatusCode};
|
||||
use graffiti_file::{determine_graffiti, GraffitiFile};
|
||||
use logging::crit;
|
||||
@@ -13,11 +12,8 @@ use std::time::Duration;
|
||||
use task_executor::TaskExecutor;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
use types::{
|
||||
BlindedBeaconBlock, BlockType, ChainSpec, EthSpec, Graffiti, PublicKeyBytes,
|
||||
SignedBlindedBeaconBlock, Slot,
|
||||
};
|
||||
use validator_store::{Error as ValidatorStoreError, ValidatorStore};
|
||||
use types::{BlockType, ChainSpec, EthSpec, Graffiti, PublicKeyBytes, Slot};
|
||||
use validator_store::{Error as ValidatorStoreError, SignedBlock, UnsignedBlock, ValidatorStore};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BlockError {
|
||||
@@ -335,26 +331,10 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> BlockService<S, T> {
|
||||
) -> Result<(), BlockError> {
|
||||
let signing_timer = validator_metrics::start_timer(&validator_metrics::BLOCK_SIGNING_TIMES);
|
||||
|
||||
let (block, maybe_blobs) = match unsigned_block {
|
||||
UnsignedBlock::Full(block_contents) => {
|
||||
let (block, maybe_blobs) = block_contents.deconstruct();
|
||||
(block.into(), maybe_blobs)
|
||||
}
|
||||
UnsignedBlock::Blinded(block) => (block.into(), None),
|
||||
};
|
||||
|
||||
let res = self
|
||||
.validator_store
|
||||
.sign_block(*validator_pubkey, block, slot)
|
||||
.await
|
||||
.map(|block| match block {
|
||||
validator_store::SignedBlock::Full(block) => {
|
||||
SignedBlock::Full(PublishBlockRequest::new(Arc::new(block), maybe_blobs))
|
||||
}
|
||||
validator_store::SignedBlock::Blinded(block) => {
|
||||
SignedBlock::Blinded(Arc::new(block))
|
||||
}
|
||||
});
|
||||
.sign_block(*validator_pubkey, unsigned_block, slot)
|
||||
.await;
|
||||
|
||||
let signed_block = match res {
|
||||
Ok(block) => block,
|
||||
@@ -398,12 +378,13 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> BlockService<S, T> {
|
||||
})
|
||||
.await?;
|
||||
|
||||
let metadata = BlockMetadata::from(&signed_block);
|
||||
info!(
|
||||
block_type = ?signed_block.block_type(),
|
||||
deposits = signed_block.num_deposits(),
|
||||
attestations = signed_block.num_attestations(),
|
||||
block_type = ?metadata.block_type,
|
||||
deposits = metadata.num_deposits,
|
||||
attestations = metadata.num_attestations,
|
||||
graffiti = ?graffiti.map(|g| g.as_utf8_lossy()),
|
||||
slot = signed_block.slot().as_u64(),
|
||||
slot = metadata.slot.as_u64(),
|
||||
"Successfully published block"
|
||||
);
|
||||
Ok(())
|
||||
@@ -508,7 +489,6 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> BlockService<S, T> {
|
||||
signed_block: &SignedBlock<S::E>,
|
||||
beacon_node: BeaconNodeHttpClient,
|
||||
) -> Result<(), BlockError> {
|
||||
let slot = signed_block.slot();
|
||||
match signed_block {
|
||||
SignedBlock::Full(signed_block) => {
|
||||
let _post_timer = validator_metrics::start_timer_vec(
|
||||
@@ -518,7 +498,9 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> BlockService<S, T> {
|
||||
beacon_node
|
||||
.post_beacon_blocks_v2_ssz(signed_block, None)
|
||||
.await
|
||||
.or_else(|e| handle_block_post_error(e, slot))?
|
||||
.or_else(|e| {
|
||||
handle_block_post_error(e, signed_block.signed_block().message().slot())
|
||||
})?
|
||||
}
|
||||
SignedBlock::Blinded(signed_block) => {
|
||||
let _post_timer = validator_metrics::start_timer_vec(
|
||||
@@ -528,7 +510,7 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> BlockService<S, T> {
|
||||
beacon_node
|
||||
.post_beacon_blinded_blocks_v2_ssz(signed_block, None)
|
||||
.await
|
||||
.or_else(|e| handle_block_post_error(e, slot))?
|
||||
.or_else(|e| handle_block_post_error(e, signed_block.message().slot()))?
|
||||
}
|
||||
}
|
||||
Ok::<_, BlockError>(())
|
||||
@@ -557,13 +539,17 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> BlockService<S, T> {
|
||||
))
|
||||
})?;
|
||||
|
||||
let unsigned_block = match block_response.data {
|
||||
eth2::types::ProduceBlockV3Response::Full(block) => UnsignedBlock::Full(block),
|
||||
eth2::types::ProduceBlockV3Response::Blinded(block) => UnsignedBlock::Blinded(block),
|
||||
let (block_proposer, unsigned_block) = match block_response.data {
|
||||
eth2::types::ProduceBlockV3Response::Full(block) => {
|
||||
(block.block().proposer_index(), UnsignedBlock::Full(block))
|
||||
}
|
||||
eth2::types::ProduceBlockV3Response::Blinded(block) => {
|
||||
(block.proposer_index(), UnsignedBlock::Blinded(block))
|
||||
}
|
||||
};
|
||||
|
||||
info!(slot = slot.as_u64(), "Received unsigned block");
|
||||
if proposer_index != Some(unsigned_block.proposer_index()) {
|
||||
if proposer_index != Some(block_proposer) {
|
||||
return Err(BlockError::Recoverable(
|
||||
"Proposer index does not match block proposer. Beacon chain re-orged".to_string(),
|
||||
));
|
||||
@@ -573,49 +559,30 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> BlockService<S, T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum UnsignedBlock<E: EthSpec> {
|
||||
Full(FullBlockContents<E>),
|
||||
Blinded(BlindedBeaconBlock<E>),
|
||||
/// Wrapper for values we want to log about a block we signed, for easy extraction from the possible
|
||||
/// variants.
|
||||
struct BlockMetadata {
|
||||
block_type: BlockType,
|
||||
slot: Slot,
|
||||
num_deposits: usize,
|
||||
num_attestations: usize,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> UnsignedBlock<E> {
|
||||
pub fn proposer_index(&self) -> u64 {
|
||||
match self {
|
||||
UnsignedBlock::Full(block) => block.block().proposer_index(),
|
||||
UnsignedBlock::Blinded(block) => block.proposer_index(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SignedBlock<E: EthSpec> {
|
||||
Full(PublishBlockRequest<E>),
|
||||
Blinded(Arc<SignedBlindedBeaconBlock<E>>),
|
||||
}
|
||||
|
||||
impl<E: EthSpec> SignedBlock<E> {
|
||||
pub fn block_type(&self) -> BlockType {
|
||||
match self {
|
||||
SignedBlock::Full(_) => BlockType::Full,
|
||||
SignedBlock::Blinded(_) => BlockType::Blinded,
|
||||
}
|
||||
}
|
||||
pub fn slot(&self) -> Slot {
|
||||
match self {
|
||||
SignedBlock::Full(block) => block.signed_block().message().slot(),
|
||||
SignedBlock::Blinded(block) => block.message().slot(),
|
||||
}
|
||||
}
|
||||
pub fn num_deposits(&self) -> usize {
|
||||
match self {
|
||||
SignedBlock::Full(block) => block.signed_block().message().body().deposits().len(),
|
||||
SignedBlock::Blinded(block) => block.message().body().deposits().len(),
|
||||
}
|
||||
}
|
||||
pub fn num_attestations(&self) -> usize {
|
||||
match self {
|
||||
SignedBlock::Full(block) => block.signed_block().message().body().attestations_len(),
|
||||
SignedBlock::Blinded(block) => block.message().body().attestations_len(),
|
||||
impl<E: EthSpec> From<&SignedBlock<E>> for BlockMetadata {
|
||||
fn from(value: &SignedBlock<E>) -> Self {
|
||||
match value {
|
||||
SignedBlock::Full(block) => BlockMetadata {
|
||||
block_type: BlockType::Full,
|
||||
slot: block.signed_block().message().slot(),
|
||||
num_deposits: block.signed_block().message().body().deposits().len(),
|
||||
num_attestations: block.signed_block().message().body().attestations_len(),
|
||||
},
|
||||
SignedBlock::Blinded(block) => BlockMetadata {
|
||||
block_type: BlockType::Blinded,
|
||||
slot: block.message().slot(),
|
||||
num_deposits: block.message().body().deposits().len(),
|
||||
num_attestations: block.message().body().attestations_len(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use beacon_node_fallback::BeaconNodeFallback;
|
||||
use environment::RuntimeContext;
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use task_executor::TaskExecutor;
|
||||
use tokio::time::sleep;
|
||||
use tracing::debug;
|
||||
use types::EthSpec;
|
||||
|
||||
/// The latency service will run 11/12ths of the way through the slot.
|
||||
pub const SLOT_DELAY_MULTIPLIER: u32 = 11;
|
||||
@@ -12,8 +11,8 @@ pub const SLOT_DELAY_DENOMINATOR: u32 = 12;
|
||||
|
||||
/// Starts a service that periodically checks the latency between the VC and the
|
||||
/// candidate BNs.
|
||||
pub fn start_latency_service<T: SlotClock + 'static, E: EthSpec>(
|
||||
context: RuntimeContext<E>,
|
||||
pub fn start_latency_service<T: SlotClock + 'static>(
|
||||
executor: TaskExecutor,
|
||||
slot_clock: T,
|
||||
beacon_nodes: Arc<BeaconNodeFallback<T>>,
|
||||
) {
|
||||
@@ -57,5 +56,5 @@ pub fn start_latency_service<T: SlotClock + 'static, E: EthSpec>(
|
||||
}
|
||||
};
|
||||
|
||||
context.executor.spawn(future, "latency");
|
||||
executor.spawn(future, "latency");
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
pub mod attestation_service;
|
||||
pub mod block_service;
|
||||
pub mod duties_service;
|
||||
pub mod latency_service;
|
||||
pub mod notifier_service;
|
||||
pub mod preparation_service;
|
||||
pub mod sync;
|
||||
pub mod sync_committee_service;
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
use crate::{DutiesService, ProductionValidatorClient};
|
||||
use lighthouse_validator_store::LighthouseValidatorStore;
|
||||
use metrics::set_gauge;
|
||||
use crate::duties_service::DutiesService;
|
||||
use slot_clock::SlotClock;
|
||||
use std::sync::Arc;
|
||||
use task_executor::TaskExecutor;
|
||||
use tokio::time::{sleep, Duration};
|
||||
use tracing::{debug, error, info};
|
||||
use types::EthSpec;
|
||||
use types::{ChainSpec, EthSpec};
|
||||
use validator_metrics::set_gauge;
|
||||
use validator_store::ValidatorStore;
|
||||
|
||||
/// Spawns a notifier service which periodically logs information about the node.
|
||||
pub fn spawn_notifier<E: EthSpec>(client: &ProductionValidatorClient<E>) -> Result<(), String> {
|
||||
let context = client.context.service_context("notifier".into());
|
||||
let executor = context.executor.clone();
|
||||
let duties_service = client.duties_service.clone();
|
||||
|
||||
let slot_duration = Duration::from_secs(context.eth2_config.spec.seconds_per_slot);
|
||||
pub fn spawn_notifier<S: ValidatorStore + 'static, T: SlotClock + 'static>(
|
||||
duties_service: Arc<DutiesService<S, T>>,
|
||||
executor: TaskExecutor,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), String> {
|
||||
let slot_duration = Duration::from_secs(spec.seconds_per_slot);
|
||||
|
||||
let interval_fut = async move {
|
||||
loop {
|
||||
@@ -33,9 +35,7 @@ pub fn spawn_notifier<E: EthSpec>(client: &ProductionValidatorClient<E>) -> Resu
|
||||
}
|
||||
|
||||
/// Performs a single notification routine.
|
||||
async fn notify<T: SlotClock + 'static, E: EthSpec>(
|
||||
duties_service: &DutiesService<LighthouseValidatorStore<T, E>, T>,
|
||||
) {
|
||||
async fn notify<S: ValidatorStore, T: SlotClock + 'static>(duties_service: &DutiesService<S, T>) {
|
||||
let (candidate_info, num_available, num_synced) =
|
||||
duties_service.beacon_nodes.get_notifier_info().await;
|
||||
let num_total = candidate_info.len();
|
||||
@@ -102,7 +102,7 @@ async fn notify<T: SlotClock + 'static, E: EthSpec>(
|
||||
}
|
||||
|
||||
if let Some(slot) = duties_service.slot_clock.now() {
|
||||
let epoch = slot.epoch(E::slots_per_epoch());
|
||||
let epoch = slot.epoch(S::E::slots_per_epoch());
|
||||
|
||||
let total_validators = duties_service.total_validator_count();
|
||||
let proposing_validators = duties_service.proposer_count(epoch);
|
||||
@@ -595,8 +595,12 @@ pub async fn fill_in_aggregation_proofs<S: ValidatorStore, T: SlotClock + 'stati
|
||||
current_slot: Slot,
|
||||
pre_compute_slot: Slot,
|
||||
) {
|
||||
// Start at the next slot, as aggregation proofs for the duty at the current slot are no longer
|
||||
// required since we do the actual aggregation in the slot before the duty slot.
|
||||
let start_slot = current_slot.as_u64() + 1;
|
||||
|
||||
// Generate selection proofs for each validator at each slot, one slot at a time.
|
||||
for slot in (current_slot.as_u64()..=pre_compute_slot.as_u64()).map(Slot::new) {
|
||||
for slot in (start_slot..=pre_compute_slot.as_u64()).map(Slot::new) {
|
||||
// For distributed mode
|
||||
if duties_service
|
||||
.sync_duties
|
||||
|
||||
@@ -5,5 +5,6 @@ edition = { workspace = true }
|
||||
authors = ["Sigma Prime <contact@sigmaprime.io>"]
|
||||
|
||||
[dependencies]
|
||||
eth2 = { workspace = true }
|
||||
slashing_protection = { workspace = true }
|
||||
types = { workspace = true }
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use eth2::types::{FullBlockContents, PublishBlockRequest};
|
||||
use slashing_protection::NotSafe;
|
||||
use std::fmt::Debug;
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
Address, Attestation, AttestationError, BeaconBlock, BlindedBeaconBlock, Epoch, EthSpec,
|
||||
Graffiti, Hash256, PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof,
|
||||
SignedBeaconBlock, SignedBlindedBeaconBlock, SignedContributionAndProof,
|
||||
SignedValidatorRegistrationData, Slot, SyncCommitteeContribution, SyncCommitteeMessage,
|
||||
SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData,
|
||||
Address, Attestation, AttestationError, BlindedBeaconBlock, Epoch, EthSpec, Graffiti, Hash256,
|
||||
PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof, SignedBlindedBeaconBlock,
|
||||
SignedContributionAndProof, SignedValidatorRegistrationData, Slot, SyncCommitteeContribution,
|
||||
SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@@ -171,40 +172,16 @@ pub trait ValidatorStore: Send + Sync {
|
||||
fn proposal_data(&self, pubkey: &PublicKeyBytes) -> Option<ProposalData>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum UnsignedBlock<E: EthSpec> {
|
||||
Full(BeaconBlock<E>),
|
||||
Full(FullBlockContents<E>),
|
||||
Blinded(BlindedBeaconBlock<E>),
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<BeaconBlock<E>> for UnsignedBlock<E> {
|
||||
fn from(block: BeaconBlock<E>) -> Self {
|
||||
UnsignedBlock::Full(block)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<BlindedBeaconBlock<E>> for UnsignedBlock<E> {
|
||||
fn from(block: BlindedBeaconBlock<E>) -> Self {
|
||||
UnsignedBlock::Blinded(block)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum SignedBlock<E: EthSpec> {
|
||||
Full(SignedBeaconBlock<E>),
|
||||
Blinded(SignedBlindedBeaconBlock<E>),
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<SignedBeaconBlock<E>> for SignedBlock<E> {
|
||||
fn from(block: SignedBeaconBlock<E>) -> Self {
|
||||
SignedBlock::Full(block)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<SignedBlindedBeaconBlock<E>> for SignedBlock<E> {
|
||||
fn from(block: SignedBlindedBeaconBlock<E>) -> Self {
|
||||
SignedBlock::Blinded(block)
|
||||
}
|
||||
Full(PublishBlockRequest<E>),
|
||||
Blinded(Arc<SignedBlindedBeaconBlock<E>>),
|
||||
}
|
||||
|
||||
/// A wrapper around `PublicKeyBytes` which encodes information about the status of a validator
|
||||
|
||||
Reference in New Issue
Block a user