mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-03 00:31:50 +00:00
Remove certain dependencies from `eth2`, and feature-gate others which are only used by certain endpoints. | Removed | Optional | Dev only | | -------- | -------- | -------- | | `either` `enr` `libp2p-identity` `multiaddr` | `protoarray` `eth2_keystore` `eip_3076` `zeroize` `reqwest-eventsource` `futures` `futures-util` | `rand` `test_random_derive` | This is done by adding an `events` feature which enables the events endpoint and its associated dependencies. The `lighthouse` feature also enables its associated dependencies making them optional. The networking-adjacent dependencies were removed by just having certain fields use a `String` instead of an explicit network type. This means the user should handle conversion at the call site instead. This is a bit spicy, but I believe `PeerId`, `Enr` and `Multiaddr` are easily converted to and from `String`s so I think it's fine and reduces our dependency space by a lot. The alternative is to feature gate these types behind a `network` feature instead. Co-Authored-By: Mac L <mjladson@pm.me>
3424 lines
143 KiB
Rust
3424 lines
143 KiB
Rust
//! This crate contains a HTTP server which serves the endpoints listed here:
|
|
//!
|
|
//! https://github.com/ethereum/beacon-APIs
|
|
//!
|
|
//! There are also some additional, non-standard endpoints behind the `/lighthouse/` path which are
|
|
//! used for development.
|
|
|
|
mod aggregate_attestation;
|
|
mod attestation_performance;
|
|
mod attester_duties;
|
|
mod beacon;
|
|
mod block_id;
|
|
mod block_packing_efficiency;
|
|
mod block_rewards;
|
|
mod build_block_contents;
|
|
mod builder_states;
|
|
mod custody;
|
|
mod database;
|
|
mod light_client;
|
|
mod metrics;
|
|
mod peer;
|
|
mod produce_block;
|
|
mod proposer_duties;
|
|
mod publish_attestations;
|
|
mod publish_blocks;
|
|
mod standard_block_rewards;
|
|
mod state_id;
|
|
mod sync_committee_rewards;
|
|
mod sync_committees;
|
|
mod task_spawner;
|
|
pub mod test_utils;
|
|
mod ui;
|
|
mod utils;
|
|
mod validator;
|
|
mod validator_inclusion;
|
|
mod validators;
|
|
mod version;
|
|
|
|
use crate::beacon::pool::*;
|
|
use crate::light_client::{get_light_client_bootstrap, get_light_client_updates};
|
|
use crate::utils::{AnyVersionFilter, EthV1Filter};
|
|
use crate::validator::post_validator_liveness_epoch;
|
|
use crate::validator::*;
|
|
use crate::version::beacon_response;
|
|
use beacon::states;
|
|
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped};
|
|
use beacon_processor::BeaconProcessorSend;
|
|
pub use block_id::BlockId;
|
|
use builder_states::get_next_withdrawals;
|
|
use bytes::Bytes;
|
|
use directory::DEFAULT_ROOT_DIR;
|
|
use eth2::StatusCode;
|
|
use eth2::lighthouse::sync_state::SyncState;
|
|
use eth2::types::{
|
|
self as api_types, BroadcastValidation, ContextDeserialize, EndpointVersion, ForkChoice,
|
|
ForkChoiceExtraData, ForkChoiceNode, LightClientUpdatesQuery, PublishBlockRequest, ValidatorId,
|
|
};
|
|
use eth2::{CONSENSUS_VERSION_HEADER, CONTENT_TYPE_HEADER, SSZ_CONTENT_TYPE_HEADER};
|
|
use health_metrics::observe::Observe;
|
|
use lighthouse_network::Enr;
|
|
use lighthouse_network::NetworkGlobals;
|
|
use lighthouse_network::PeerId;
|
|
use lighthouse_version::version_with_platform;
|
|
use logging::{SSELoggingComponents, crit};
|
|
use network::{NetworkMessage, NetworkSenders};
|
|
use network_utils::enr_ext::EnrExt;
|
|
use parking_lot::RwLock;
|
|
pub use publish_blocks::{
|
|
ProvenancedBlock, publish_blinded_block, publish_block, reconstruct_block,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use slot_clock::SlotClock;
|
|
use ssz::Encode;
|
|
pub use state_id::StateId;
|
|
use std::future::Future;
|
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
|
use std::path::PathBuf;
|
|
use std::pin::Pin;
|
|
use std::str::FromStr;
|
|
use std::sync::Arc;
|
|
use sysinfo::{System, SystemExt};
|
|
use system_health::{observe_nat, observe_system_health_bn};
|
|
use task_spawner::{Priority, TaskSpawner};
|
|
use tokio::sync::mpsc::UnboundedSender;
|
|
use tokio_stream::{
|
|
StreamExt,
|
|
wrappers::{BroadcastStream, errors::BroadcastStreamRecvError},
|
|
};
|
|
use tracing::{debug, info, warn};
|
|
use types::{
|
|
BeaconStateError, Checkpoint, ConfigAndPreset, Epoch, EthSpec, ForkName, Hash256,
|
|
SignedBlindedBeaconBlock, Slot,
|
|
};
|
|
use version::{
|
|
ResponseIncludesVersion, V1, V2, add_consensus_version_header, add_ssz_content_type_header,
|
|
execution_optimistic_finalized_beacon_response, inconsistent_fork_rejection,
|
|
unsupported_version_rejection,
|
|
};
|
|
use warp::Reply;
|
|
use warp::hyper::Body;
|
|
use warp::sse::Event;
|
|
use warp::{Filter, Rejection, http::Response};
|
|
use warp_utils::{query::multi_key_query, uor::UnifyingOrFilter};
|
|
|
|
const API_PREFIX: &str = "eth";
|
|
|
|
/// A custom type which allows for both unsecured and TLS-enabled HTTP servers.
|
|
type HttpServer = (SocketAddr, Pin<Box<dyn Future<Output = ()> + Send>>);
|
|
|
|
/// Alias for readability.
|
|
pub type ExecutionOptimistic = bool;
|
|
|
|
/// Configuration used when serving the HTTP server over TLS.
|
|
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TlsConfig {
|
|
pub cert: PathBuf,
|
|
pub key: PathBuf,
|
|
}
|
|
|
|
/// A wrapper around all the items required to spawn the HTTP server.
|
|
///
|
|
/// The server will gracefully handle the case where any fields are `None`.
|
|
pub struct Context<T: BeaconChainTypes> {
|
|
pub config: Config,
|
|
pub chain: Option<Arc<BeaconChain<T>>>,
|
|
pub network_senders: Option<NetworkSenders<T::EthSpec>>,
|
|
pub network_globals: Option<Arc<NetworkGlobals<T::EthSpec>>>,
|
|
pub beacon_processor_send: Option<BeaconProcessorSend<T::EthSpec>>,
|
|
pub sse_logging_components: Option<SSELoggingComponents>,
|
|
}
|
|
|
|
/// Configuration for the HTTP server.
|
|
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Config {
|
|
pub enabled: bool,
|
|
pub listen_addr: IpAddr,
|
|
pub listen_port: u16,
|
|
pub allow_origin: Option<String>,
|
|
pub tls_config: Option<TlsConfig>,
|
|
pub data_dir: PathBuf,
|
|
pub sse_capacity_multiplier: usize,
|
|
pub enable_beacon_processor: bool,
|
|
#[serde(with = "eth2::types::serde_status_code")]
|
|
pub duplicate_block_status_code: StatusCode,
|
|
pub target_peers: usize,
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default() -> Self {
|
|
Self {
|
|
enabled: false,
|
|
listen_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
|
listen_port: 5052,
|
|
allow_origin: None,
|
|
tls_config: None,
|
|
data_dir: PathBuf::from(DEFAULT_ROOT_DIR),
|
|
sse_capacity_multiplier: 1,
|
|
enable_beacon_processor: true,
|
|
duplicate_block_status_code: StatusCode::ACCEPTED,
|
|
target_peers: 100,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Error {
|
|
Warp(warp::Error),
|
|
Other(String),
|
|
}
|
|
|
|
impl From<warp::Error> for Error {
|
|
fn from(e: warp::Error) -> Self {
|
|
Error::Warp(e)
|
|
}
|
|
}
|
|
|
|
impl From<String> for Error {
|
|
fn from(e: String) -> Self {
|
|
Error::Other(e)
|
|
}
|
|
}
|
|
|
|
/// Creates a `warp` logging wrapper which we use for Prometheus metrics (not necessarily logging,
|
|
/// per say).
|
|
pub fn prometheus_metrics() -> warp::filters::log::Log<impl Fn(warp::filters::log::Info) + Clone> {
|
|
warp::log::custom(move |info| {
|
|
// Here we restrict the `info.path()` value to some predefined values. Without this, we end
|
|
// up with a new metric type each time someone includes something unique in the path (e.g.,
|
|
// a block hash).
|
|
let path = {
|
|
let equals = |s: &'static str| -> Option<&'static str> {
|
|
if info.path() == format!("/{}/{}", API_PREFIX, s) {
|
|
Some(s)
|
|
} else {
|
|
None
|
|
}
|
|
};
|
|
|
|
let starts_with = |s: &'static str| -> Option<&'static str> {
|
|
if info.path().starts_with(&format!("/{}/{}", API_PREFIX, s)) {
|
|
Some(s)
|
|
} else {
|
|
None
|
|
}
|
|
};
|
|
|
|
// First line covers `POST /v1/beacon/blocks` only
|
|
equals("v1/beacon/blocks")
|
|
.or_else(|| starts_with("v2/beacon/blocks"))
|
|
.or_else(|| starts_with("v1/beacon/blob_sidecars"))
|
|
.or_else(|| starts_with("v1/beacon/blobs"))
|
|
.or_else(|| starts_with("v1/beacon/blocks/head/root"))
|
|
.or_else(|| starts_with("v1/beacon/blinded_blocks"))
|
|
.or_else(|| starts_with("v2/beacon/blinded_blocks"))
|
|
.or_else(|| starts_with("v1/beacon/headers"))
|
|
.or_else(|| starts_with("v1/beacon/light_client"))
|
|
.or_else(|| starts_with("v1/beacon/pool/attestations"))
|
|
.or_else(|| starts_with("v2/beacon/pool/attestations"))
|
|
.or_else(|| starts_with("v1/beacon/pool/attester_slashings"))
|
|
.or_else(|| starts_with("v1/beacon/pool/bls_to_execution_changes"))
|
|
.or_else(|| starts_with("v1/beacon/pool/proposer_slashings"))
|
|
.or_else(|| starts_with("v1/beacon/pool/sync_committees"))
|
|
.or_else(|| starts_with("v1/beacon/pool/voluntary_exits"))
|
|
.or_else(|| starts_with("v1/beacon/rewards/blocks"))
|
|
.or_else(|| starts_with("v1/beacon/rewards/attestations"))
|
|
.or_else(|| starts_with("v1/beacon/rewards/sync_committee"))
|
|
.or_else(|| starts_with("v1/beacon/rewards"))
|
|
.or_else(|| starts_with("v1/beacon/states"))
|
|
.or_else(|| starts_with("v1/beacon/"))
|
|
.or_else(|| starts_with("v2/beacon/"))
|
|
.or_else(|| starts_with("v1/builder/states"))
|
|
.or_else(|| starts_with("v1/config/deposit_contract"))
|
|
.or_else(|| starts_with("v1/config/fork_schedule"))
|
|
.or_else(|| starts_with("v1/config/spec"))
|
|
.or_else(|| starts_with("v1/config/"))
|
|
.or_else(|| starts_with("v1/debug/"))
|
|
.or_else(|| starts_with("v2/debug/"))
|
|
.or_else(|| starts_with("v1/events"))
|
|
.or_else(|| starts_with("v1/events/"))
|
|
.or_else(|| starts_with("v1/node/health"))
|
|
.or_else(|| starts_with("v1/node/identity"))
|
|
.or_else(|| starts_with("v1/node/peers"))
|
|
.or_else(|| starts_with("v1/node/peer_count"))
|
|
.or_else(|| starts_with("v1/node/syncing"))
|
|
.or_else(|| starts_with("v1/node/version"))
|
|
.or_else(|| starts_with("v1/node"))
|
|
.or_else(|| starts_with("v1/validator/aggregate_and_proofs"))
|
|
.or_else(|| starts_with("v2/validator/aggregate_and_proofs"))
|
|
.or_else(|| starts_with("v1/validator/aggregate_attestation"))
|
|
.or_else(|| starts_with("v2/validator/aggregate_attestation"))
|
|
.or_else(|| starts_with("v1/validator/attestation_data"))
|
|
.or_else(|| starts_with("v1/validator/beacon_committee_subscriptions"))
|
|
.or_else(|| starts_with("v1/validator/blinded_blocks"))
|
|
.or_else(|| starts_with("v2/validator/blinded_blocks"))
|
|
.or_else(|| starts_with("v1/validator/blocks"))
|
|
.or_else(|| starts_with("v2/validator/blocks"))
|
|
.or_else(|| starts_with("v3/validator/blocks"))
|
|
.or_else(|| starts_with("v1/validator/contribution_and_proofs"))
|
|
.or_else(|| starts_with("v1/validator/duties/attester"))
|
|
.or_else(|| starts_with("v1/validator/duties/proposer"))
|
|
.or_else(|| starts_with("v1/validator/duties/sync"))
|
|
.or_else(|| starts_with("v1/validator/liveness"))
|
|
.or_else(|| starts_with("v1/validator/prepare_beacon_proposer"))
|
|
.or_else(|| starts_with("v1/validator/register_validator"))
|
|
.or_else(|| starts_with("v1/validator/sync_committee_contribution"))
|
|
.or_else(|| starts_with("v1/validator/sync_committee_subscriptions"))
|
|
.or_else(|| starts_with("v1/validator/"))
|
|
.or_else(|| starts_with("v2/validator/"))
|
|
.or_else(|| starts_with("v3/validator/"))
|
|
.or_else(|| starts_with("lighthouse"))
|
|
.unwrap_or("other")
|
|
};
|
|
|
|
metrics::inc_counter_vec(&metrics::HTTP_API_PATHS_TOTAL, &[path]);
|
|
metrics::inc_counter_vec(
|
|
&metrics::HTTP_API_STATUS_CODES_TOTAL,
|
|
&[&info.status().to_string()],
|
|
);
|
|
metrics::observe_timer_vec(&metrics::HTTP_API_PATHS_TIMES, &[path], info.elapsed());
|
|
})
|
|
}
|
|
|
|
/// Creates a `warp` logging wrapper which we use to create `tracing` logs.
|
|
pub fn tracing_logging() -> warp::filters::log::Log<impl Fn(warp::filters::log::Info) + Clone> {
|
|
warp::log::custom(move |info| {
|
|
let status = info.status();
|
|
// Ensure elapsed time is in milliseconds.
|
|
let elapsed = info.elapsed().as_secs_f64() * 1000.0;
|
|
let path = info.path();
|
|
let method = info.method().to_string();
|
|
|
|
if status.is_success() {
|
|
debug!(
|
|
elapsed_ms = %elapsed,
|
|
status = %status,
|
|
path = %path,
|
|
method = %method,
|
|
"Processed HTTP API request"
|
|
);
|
|
} else {
|
|
warn!(
|
|
elapsed_ms = %elapsed,
|
|
status = %status,
|
|
path = %path,
|
|
method = %method,
|
|
"Error processing HTTP API request"
|
|
);
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Creates a server that will serve requests using information from `ctx`.
|
|
///
|
|
/// The server will shut down gracefully when the `shutdown` future resolves.
|
|
///
|
|
/// ## Returns
|
|
///
|
|
/// This function will bind the server to the provided address and then return a tuple of:
|
|
///
|
|
/// - `SocketAddr`: the address that the HTTP server will listen on.
|
|
/// - `Future`: the actual server future that will need to be awaited.
|
|
///
|
|
/// ## Errors
|
|
///
|
|
/// Returns an error if the server is unable to bind or there is another error during
|
|
/// configuration.
|
|
pub fn serve<T: BeaconChainTypes>(
|
|
ctx: Arc<Context<T>>,
|
|
shutdown: impl Future<Output = ()> + Send + Sync + 'static,
|
|
) -> Result<HttpServer, Error> {
|
|
let config = ctx.config.clone();
|
|
|
|
// Configure CORS.
|
|
let cors_builder = {
|
|
let builder = warp::cors()
|
|
.allow_methods(vec!["GET", "POST"])
|
|
.allow_headers(vec!["Content-Type"]);
|
|
|
|
warp_utils::cors::set_builder_origins(
|
|
builder,
|
|
config.allow_origin.as_deref(),
|
|
(config.listen_addr, config.listen_port),
|
|
)?
|
|
};
|
|
|
|
// Sanity check.
|
|
if !config.enabled {
|
|
crit!("Cannot start disabled HTTP server");
|
|
return Err(Error::Other(
|
|
"A disabled server should not be started".to_string(),
|
|
));
|
|
}
|
|
|
|
// Create a filter that extracts the endpoint version.
|
|
let any_version = warp::path(API_PREFIX)
|
|
.and(
|
|
warp::path::param::<EndpointVersion>().or_else(|_| async move {
|
|
Err(warp_utils::reject::custom_bad_request(
|
|
"Invalid version identifier".to_string(),
|
|
))
|
|
}),
|
|
)
|
|
.boxed();
|
|
|
|
// Filter that enforces a single endpoint version and then discards the `EndpointVersion`.
|
|
fn single_version(any_version: AnyVersionFilter, reqd: EndpointVersion) -> EthV1Filter {
|
|
any_version
|
|
.and_then(move |version| async move {
|
|
if version == reqd {
|
|
Ok(())
|
|
} else {
|
|
Err(unsupported_version_rejection(version))
|
|
}
|
|
})
|
|
.untuple_one()
|
|
.boxed()
|
|
}
|
|
|
|
let eth_v1 = single_version(any_version.clone(), V1);
|
|
let eth_v2 = single_version(any_version.clone(), V2);
|
|
|
|
// Create a `warp` filter that provides access to the network globals.
|
|
let inner_network_globals = ctx.network_globals.clone();
|
|
let network_globals = warp::any()
|
|
.map(move || inner_network_globals.clone())
|
|
.and_then(|network_globals| async move {
|
|
match network_globals {
|
|
Some(globals) => Ok(globals),
|
|
None => Err(warp_utils::reject::custom_not_found(
|
|
"network globals are not initialized.".to_string(),
|
|
)),
|
|
}
|
|
});
|
|
|
|
// Create a `warp` filter for the data_dir.
|
|
let inner_data_dir = ctx.config.data_dir.clone();
|
|
let data_dir_filter = warp::any().map(move || inner_data_dir.clone());
|
|
|
|
// Create a `warp` filter that provides access to the beacon chain.
|
|
let inner_ctx = ctx.clone();
|
|
let chain_filter = warp::any()
|
|
.map(move || inner_ctx.chain.clone())
|
|
.and_then(|chain| async move {
|
|
match chain {
|
|
Some(chain) => Ok(chain),
|
|
None => Err(warp_utils::reject::custom_not_found(
|
|
"Beacon chain genesis has not yet been observed.".to_string(),
|
|
)),
|
|
}
|
|
})
|
|
.boxed();
|
|
|
|
// Create a `warp` filter that provides access to the network sender channel.
|
|
let network_tx = ctx
|
|
.network_senders
|
|
.as_ref()
|
|
.map(|senders| senders.network_send());
|
|
let network_tx_filter = warp::any()
|
|
.map(move || network_tx.clone())
|
|
.and_then(|network_tx| async move {
|
|
match network_tx {
|
|
Some(network_tx) => Ok(network_tx),
|
|
None => Err(warp_utils::reject::custom_not_found(
|
|
"The networking stack has not yet started (network_tx).".to_string(),
|
|
)),
|
|
}
|
|
})
|
|
.boxed();
|
|
|
|
// Create a `warp` filter that provides access to the network attestation subscription channel.
|
|
let validator_subscriptions_tx = ctx
|
|
.network_senders
|
|
.as_ref()
|
|
.map(|senders| senders.validator_subscription_send());
|
|
let validator_subscription_tx_filter = warp::any()
|
|
.map(move || validator_subscriptions_tx.clone())
|
|
.and_then(|validator_subscriptions_tx| async move {
|
|
match validator_subscriptions_tx {
|
|
Some(validator_subscriptions_tx) => Ok(validator_subscriptions_tx),
|
|
None => Err(warp_utils::reject::custom_not_found(
|
|
"The networking stack has not yet started (validator_subscription_tx)."
|
|
.to_string(),
|
|
)),
|
|
}
|
|
})
|
|
.boxed();
|
|
|
|
// Create a `warp` filter that rejects requests whilst the node is syncing.
|
|
let not_while_syncing_filter =
|
|
warp::any()
|
|
.and(network_globals.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
move |network_globals: Arc<NetworkGlobals<T::EthSpec>>,
|
|
chain: Arc<BeaconChain<T>>| async move {
|
|
match *network_globals.sync_state.read() {
|
|
SyncState::SyncingFinalized { .. } | SyncState::SyncingHead { .. } => {
|
|
let head_slot = chain.canonical_head.cached_head().head_slot();
|
|
|
|
let current_slot =
|
|
chain.slot_clock.now_or_genesis().ok_or_else(|| {
|
|
warp_utils::reject::custom_server_error(
|
|
"unable to read slot clock".to_string(),
|
|
)
|
|
})?;
|
|
|
|
let tolerance =
|
|
chain.config.sync_tolerance_epochs * T::EthSpec::slots_per_epoch();
|
|
|
|
if head_slot + tolerance >= current_slot {
|
|
Ok(())
|
|
} else {
|
|
Err(warp_utils::reject::not_synced(format!(
|
|
"head slot is {}, current slot is {}",
|
|
head_slot, current_slot
|
|
)))
|
|
}
|
|
}
|
|
SyncState::SyncTransition
|
|
| SyncState::BackFillSyncing { .. }
|
|
| SyncState::CustodyBackFillSyncing { .. } => Ok(()),
|
|
SyncState::Synced => Ok(()),
|
|
SyncState::Stalled => Ok(()),
|
|
}
|
|
},
|
|
)
|
|
.boxed();
|
|
|
|
// Create a `warp` filter that returns 404s if the light client server is disabled.
|
|
let light_client_server_filter =
|
|
warp::any()
|
|
.and(chain_filter.clone())
|
|
.then(|chain: Arc<BeaconChain<T>>| async move {
|
|
if chain.config.enable_light_client_server {
|
|
Ok(())
|
|
} else {
|
|
Err(warp::reject::not_found())
|
|
}
|
|
});
|
|
|
|
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()));
|
|
{
|
|
// grab write access for initialisation
|
|
let mut system_info = system_info.write();
|
|
system_info.refresh_disks_list();
|
|
system_info.refresh_networks_list();
|
|
system_info.refresh_cpu_specifics(sysinfo::CpuRefreshKind::everything());
|
|
system_info.refresh_cpu();
|
|
} // end lock
|
|
|
|
let system_info_filter =
|
|
warp::any()
|
|
.map(move || system_info.clone())
|
|
.map(|sysinfo: Arc<RwLock<System>>| {
|
|
{
|
|
// refresh stats
|
|
let mut sysinfo_lock = sysinfo.write();
|
|
sysinfo_lock.refresh_memory();
|
|
sysinfo_lock.refresh_cpu_specifics(sysinfo::CpuRefreshKind::everything());
|
|
sysinfo_lock.refresh_cpu();
|
|
sysinfo_lock.refresh_system();
|
|
sysinfo_lock.refresh_networks();
|
|
sysinfo_lock.refresh_disks();
|
|
} // end lock
|
|
sysinfo
|
|
});
|
|
|
|
let app_start = std::time::Instant::now();
|
|
let app_start_filter = warp::any().map(move || app_start);
|
|
|
|
// Create a `warp` filter that provides access to the `TaskSpawner`.
|
|
let beacon_processor_send = ctx
|
|
.beacon_processor_send
|
|
.clone()
|
|
.filter(|_| config.enable_beacon_processor);
|
|
let task_spawner_filter = warp::any()
|
|
.map(move || TaskSpawner::new(beacon_processor_send.clone()))
|
|
.boxed();
|
|
|
|
let duplicate_block_status_code = ctx.config.duplicate_block_status_code;
|
|
|
|
/*
|
|
*
|
|
* Start of HTTP method definitions.
|
|
*
|
|
*/
|
|
|
|
// GET beacon/genesis
|
|
let get_beacon_genesis = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("genesis"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let genesis_data = api_types::GenesisData {
|
|
genesis_time: chain.genesis_time,
|
|
genesis_validators_root: chain.genesis_validators_root,
|
|
genesis_fork_version: chain.spec.genesis_fork_version,
|
|
};
|
|
Ok(api_types::GenericResponse::from(genesis_data))
|
|
})
|
|
},
|
|
);
|
|
|
|
/*
|
|
* beacon/states/{state_id}
|
|
*/
|
|
|
|
let beacon_states_path = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("states"))
|
|
.and(warp::path::param::<StateId>().or_else(|_| async {
|
|
Err(warp_utils::reject::custom_bad_request(
|
|
"Invalid state ID".to_string(),
|
|
))
|
|
}))
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.boxed();
|
|
|
|
// GET beacon/states/{state_id}/root
|
|
let get_beacon_state_root = states::get_beacon_state_root(beacon_states_path.clone());
|
|
|
|
// GET beacon/states/{state_id}/fork
|
|
let get_beacon_state_fork = states::get_beacon_state_fork(beacon_states_path.clone());
|
|
|
|
// GET beacon/states/{state_id}/finality_checkpoints
|
|
let get_beacon_state_finality_checkpoints =
|
|
states::get_beacon_state_finality_checkpoints(beacon_states_path.clone());
|
|
|
|
// GET beacon/states/{state_id}/validator_balances?id
|
|
let get_beacon_state_validator_balances =
|
|
states::get_beacon_state_validator_balances(beacon_states_path.clone());
|
|
|
|
// POST beacon/states/{state_id}/validator_balances
|
|
let post_beacon_state_validator_balances =
|
|
states::post_beacon_state_validator_balances(beacon_states_path.clone());
|
|
|
|
// POST beacon/states/{state_id}/validator_identities
|
|
let post_beacon_state_validator_identities =
|
|
states::post_beacon_state_validator_identities(beacon_states_path.clone());
|
|
|
|
// GET beacon/states/{state_id}/validators?id,status
|
|
let get_beacon_state_validators =
|
|
states::get_beacon_state_validators(beacon_states_path.clone());
|
|
|
|
// POST beacon/states/{state_id}/validators
|
|
let post_beacon_state_validators =
|
|
states::post_beacon_state_validators(beacon_states_path.clone());
|
|
|
|
// GET beacon/states/{state_id}/validators/{validator_id}
|
|
let get_beacon_state_validators_id =
|
|
states::get_beacon_state_validators_id(beacon_states_path.clone());
|
|
|
|
// GET beacon/states/{state_id}/committees?slot,index,epoch
|
|
let get_beacon_state_committees =
|
|
states::get_beacon_state_committees(beacon_states_path.clone());
|
|
|
|
// GET beacon/states/{state_id}/sync_committees?epoch
|
|
let get_beacon_state_sync_committees =
|
|
states::get_beacon_state_sync_committees(beacon_states_path.clone());
|
|
|
|
// GET beacon/states/{state_id}/randao?epoch
|
|
let get_beacon_state_randao = states::get_beacon_state_randao(beacon_states_path.clone());
|
|
|
|
// GET beacon/states/{state_id}/pending_deposits
|
|
let get_beacon_state_pending_deposits =
|
|
states::get_beacon_state_pending_deposits(beacon_states_path.clone());
|
|
|
|
// GET beacon/states/{state_id}/pending_partial_withdrawals
|
|
let get_beacon_state_pending_partial_withdrawals =
|
|
states::get_beacon_state_pending_partial_withdrawals(beacon_states_path.clone());
|
|
|
|
// GET beacon/states/{state_id}/pending_consolidations
|
|
let get_beacon_state_pending_consolidations =
|
|
states::get_beacon_state_pending_consolidations(beacon_states_path.clone());
|
|
|
|
// GET beacon/headers
|
|
//
|
|
// Note: this endpoint only returns information about blocks in the canonical chain. Given that
|
|
// there's a `canonical` flag on the response, I assume it should also return non-canonical
|
|
// things. Returning non-canonical things is hard for us since we don't already have a
|
|
// mechanism for arbitrary forwards block iteration, we only support iterating forwards along
|
|
// the canonical chain.
|
|
let get_beacon_headers = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("headers"))
|
|
.and(warp::query::<api_types::HeadersQuery>())
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|query: api_types::HeadersQuery,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let (root, block, execution_optimistic, finalized) =
|
|
match (query.slot, query.parent_root) {
|
|
// No query parameters, return the canonical head block.
|
|
(None, None) => {
|
|
let (cached_head, execution_status) = chain
|
|
.canonical_head
|
|
.head_and_execution_status()
|
|
.map_err(warp_utils::reject::unhandled_error)?;
|
|
(
|
|
cached_head.head_block_root(),
|
|
cached_head.snapshot.beacon_block.clone_as_blinded(),
|
|
execution_status.is_optimistic_or_invalid(),
|
|
false,
|
|
)
|
|
}
|
|
// Only the parent root parameter, do a forwards-iterator lookup.
|
|
(None, Some(parent_root)) => {
|
|
let (parent, execution_optimistic, _parent_finalized) =
|
|
BlockId::from_root(parent_root).blinded_block(&chain)?;
|
|
let (root, _slot) = chain
|
|
.forwards_iter_block_roots(parent.slot())
|
|
.map_err(warp_utils::reject::unhandled_error)?
|
|
// Ignore any skip-slots immediately following the parent.
|
|
.find(|res| {
|
|
res.as_ref().is_ok_and(|(root, _)| *root != parent_root)
|
|
})
|
|
.transpose()
|
|
.map_err(warp_utils::reject::unhandled_error)?
|
|
.ok_or_else(|| {
|
|
warp_utils::reject::custom_not_found(format!(
|
|
"child of block with root {}",
|
|
parent_root
|
|
))
|
|
})?;
|
|
|
|
BlockId::from_root(root)
|
|
.blinded_block(&chain)
|
|
// Ignore this `execution_optimistic` since the first value has
|
|
// more information about the original request.
|
|
.map(|(block, _execution_optimistic, finalized)| {
|
|
(root, block, execution_optimistic, finalized)
|
|
})?
|
|
}
|
|
// Slot is supplied, search by slot and optionally filter by
|
|
// parent root.
|
|
(Some(slot), parent_root_opt) => {
|
|
let (root, execution_optimistic, finalized) =
|
|
BlockId::from_slot(slot).root(&chain)?;
|
|
// Ignore the second `execution_optimistic`, the first one is the
|
|
// most relevant since it knows that we queried by slot.
|
|
let (block, _execution_optimistic, _finalized) =
|
|
BlockId::from_root(root).blinded_block(&chain)?;
|
|
|
|
// If the parent root was supplied, check that it matches the block
|
|
// obtained via a slot lookup.
|
|
if let Some(parent_root) = parent_root_opt
|
|
&& block.parent_root() != parent_root
|
|
{
|
|
return Err(warp_utils::reject::custom_not_found(format!(
|
|
"no canonical block at slot {} with parent root {}",
|
|
slot, parent_root
|
|
)));
|
|
}
|
|
|
|
(root, block, execution_optimistic, finalized)
|
|
}
|
|
};
|
|
|
|
let data = api_types::BlockHeaderData {
|
|
root,
|
|
canonical: true,
|
|
header: api_types::BlockHeaderAndSignature {
|
|
message: block.message().block_header(),
|
|
signature: block.signature().clone().into(),
|
|
},
|
|
};
|
|
|
|
Ok(api_types::GenericResponse::from(vec![data])
|
|
.add_execution_optimistic_finalized(execution_optimistic, finalized))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET beacon/headers/{block_id}
|
|
let get_beacon_headers_block_id = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("headers"))
|
|
.and(warp::path::param::<BlockId>().or_else(|_| async {
|
|
Err(warp_utils::reject::custom_bad_request(
|
|
"Invalid block ID".to_string(),
|
|
))
|
|
}))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|block_id: BlockId,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let (root, execution_optimistic, finalized) = block_id.root(&chain)?;
|
|
// Ignore the second `execution_optimistic` since the first one has more
|
|
// information about the original request.
|
|
let (block, _execution_optimistic, _finalized) =
|
|
BlockId::from_root(root).blinded_block(&chain)?;
|
|
|
|
let canonical = chain
|
|
.block_root_at_slot(block.slot(), WhenSlotSkipped::None)
|
|
.map_err(warp_utils::reject::unhandled_error)?
|
|
.is_some_and(|canonical| root == canonical);
|
|
|
|
let data = api_types::BlockHeaderData {
|
|
root,
|
|
canonical,
|
|
header: api_types::BlockHeaderAndSignature {
|
|
message: block.message().block_header(),
|
|
signature: block.signature().clone().into(),
|
|
},
|
|
};
|
|
|
|
Ok(api_types::ExecutionOptimisticFinalizedResponse {
|
|
execution_optimistic: Some(execution_optimistic),
|
|
finalized: Some(finalized),
|
|
data,
|
|
})
|
|
})
|
|
},
|
|
);
|
|
|
|
/*
|
|
* beacon/blocks
|
|
*/
|
|
let consensus_version_header_filter =
|
|
warp::header::header::<ForkName>(CONSENSUS_VERSION_HEADER).boxed();
|
|
|
|
let optional_consensus_version_header_filter =
|
|
warp::header::optional::<ForkName>(CONSENSUS_VERSION_HEADER).boxed();
|
|
|
|
// POST beacon/blocks
|
|
let post_beacon_blocks = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blocks"))
|
|
.and(warp::path::end())
|
|
.and(warp::body::json())
|
|
.and(consensus_version_header_filter.clone())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(network_tx_filter.clone())
|
|
.then(
|
|
move |value: serde_json::Value,
|
|
consensus_version: ForkName,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
|
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
|
|
let request = PublishBlockRequest::<T::EthSpec>::context_deserialize(
|
|
&value,
|
|
consensus_version,
|
|
)
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_bad_request(format!("invalid JSON: {e:?}"))
|
|
})?;
|
|
publish_blocks::publish_block(
|
|
None,
|
|
ProvenancedBlock::local_from_publish_request(request),
|
|
chain,
|
|
&network_tx,
|
|
BroadcastValidation::default(),
|
|
duplicate_block_status_code,
|
|
)
|
|
.await
|
|
})
|
|
},
|
|
);
|
|
|
|
let post_beacon_blocks_ssz = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blocks"))
|
|
.and(warp::path::end())
|
|
.and(warp::body::bytes())
|
|
.and(consensus_version_header_filter.clone())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(network_tx_filter.clone())
|
|
.then(
|
|
move |block_bytes: Bytes,
|
|
consensus_version: ForkName,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
|
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
|
|
let block_contents = PublishBlockRequest::<T::EthSpec>::from_ssz_bytes(
|
|
&block_bytes,
|
|
consensus_version,
|
|
)
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}"))
|
|
})?;
|
|
publish_blocks::publish_block(
|
|
None,
|
|
ProvenancedBlock::local_from_publish_request(block_contents),
|
|
chain,
|
|
&network_tx,
|
|
BroadcastValidation::default(),
|
|
duplicate_block_status_code,
|
|
)
|
|
.await
|
|
})
|
|
},
|
|
);
|
|
|
|
let post_beacon_blocks_v2 = eth_v2
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blocks"))
|
|
.and(warp::query::<api_types::BroadcastValidationQuery>())
|
|
.and(warp::path::end())
|
|
.and(warp::body::json())
|
|
.and(consensus_version_header_filter.clone())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(network_tx_filter.clone())
|
|
.then(
|
|
move |validation_level: api_types::BroadcastValidationQuery,
|
|
value: serde_json::Value,
|
|
consensus_version: ForkName,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
|
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
|
|
let request = PublishBlockRequest::<T::EthSpec>::context_deserialize(
|
|
&value,
|
|
consensus_version,
|
|
)
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_bad_request(format!("invalid JSON: {e:?}"))
|
|
})?;
|
|
|
|
publish_blocks::publish_block(
|
|
None,
|
|
ProvenancedBlock::local_from_publish_request(request),
|
|
chain,
|
|
&network_tx,
|
|
validation_level.broadcast_validation,
|
|
duplicate_block_status_code,
|
|
)
|
|
.await
|
|
})
|
|
},
|
|
);
|
|
|
|
let post_beacon_blocks_v2_ssz = eth_v2
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blocks"))
|
|
.and(warp::query::<api_types::BroadcastValidationQuery>())
|
|
.and(warp::path::end())
|
|
.and(warp::body::bytes())
|
|
.and(consensus_version_header_filter.clone())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(network_tx_filter.clone())
|
|
.then(
|
|
move |validation_level: api_types::BroadcastValidationQuery,
|
|
block_bytes: Bytes,
|
|
consensus_version: ForkName,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
|
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
|
|
let block_contents = PublishBlockRequest::<T::EthSpec>::from_ssz_bytes(
|
|
&block_bytes,
|
|
consensus_version,
|
|
)
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}"))
|
|
})?;
|
|
publish_blocks::publish_block(
|
|
None,
|
|
ProvenancedBlock::local_from_publish_request(block_contents),
|
|
chain,
|
|
&network_tx,
|
|
validation_level.broadcast_validation,
|
|
duplicate_block_status_code,
|
|
)
|
|
.await
|
|
})
|
|
},
|
|
);
|
|
|
|
/*
|
|
* beacon/blinded_blocks
|
|
*/
|
|
|
|
// POST beacon/blinded_blocks
|
|
let post_beacon_blinded_blocks = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blinded_blocks"))
|
|
.and(warp::path::end())
|
|
.and(warp_utils::json::json())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(network_tx_filter.clone())
|
|
.then(
|
|
move |block_contents: Arc<SignedBlindedBeaconBlock<T::EthSpec>>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
|
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
|
|
publish_blocks::publish_blinded_block(
|
|
block_contents,
|
|
chain,
|
|
&network_tx,
|
|
BroadcastValidation::default(),
|
|
duplicate_block_status_code,
|
|
)
|
|
.await
|
|
})
|
|
},
|
|
);
|
|
|
|
// POST beacon/blocks
|
|
let post_beacon_blinded_blocks_ssz = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blinded_blocks"))
|
|
.and(warp::path::end())
|
|
.and(warp::body::bytes())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(network_tx_filter.clone())
|
|
.then(
|
|
move |block_bytes: Bytes,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
|
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
|
|
let block = SignedBlindedBeaconBlock::<T::EthSpec>::from_ssz_bytes(
|
|
&block_bytes,
|
|
&chain.spec,
|
|
)
|
|
.map(Arc::new)
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}"))
|
|
})?;
|
|
publish_blocks::publish_blinded_block(
|
|
block,
|
|
chain,
|
|
&network_tx,
|
|
BroadcastValidation::default(),
|
|
duplicate_block_status_code,
|
|
)
|
|
.await
|
|
})
|
|
},
|
|
);
|
|
|
|
let post_beacon_blinded_blocks_v2 = eth_v2
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blinded_blocks"))
|
|
.and(warp::query::<api_types::BroadcastValidationQuery>())
|
|
.and(warp::path::end())
|
|
.and(warp_utils::json::json())
|
|
.and(consensus_version_header_filter)
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(network_tx_filter.clone())
|
|
.then(
|
|
move |validation_level: api_types::BroadcastValidationQuery,
|
|
blinded_block_json: serde_json::Value,
|
|
consensus_version: ForkName,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
|
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
|
|
let blinded_block =
|
|
SignedBlindedBeaconBlock::<T::EthSpec>::context_deserialize(
|
|
&blinded_block_json,
|
|
consensus_version,
|
|
)
|
|
.map(Arc::new)
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_bad_request(format!("invalid JSON: {e:?}"))
|
|
})?;
|
|
publish_blocks::publish_blinded_block(
|
|
blinded_block,
|
|
chain,
|
|
&network_tx,
|
|
validation_level.broadcast_validation,
|
|
duplicate_block_status_code,
|
|
)
|
|
.await
|
|
})
|
|
},
|
|
);
|
|
|
|
let post_beacon_blinded_blocks_v2_ssz = eth_v2
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blinded_blocks"))
|
|
.and(warp::query::<api_types::BroadcastValidationQuery>())
|
|
.and(warp::path::end())
|
|
.and(warp::body::bytes())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(network_tx_filter.clone())
|
|
.then(
|
|
move |validation_level: api_types::BroadcastValidationQuery,
|
|
block_bytes: Bytes,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
|
task_spawner.spawn_async_with_rejection(Priority::P0, async move {
|
|
let block = SignedBlindedBeaconBlock::<T::EthSpec>::from_ssz_bytes(
|
|
&block_bytes,
|
|
&chain.spec,
|
|
)
|
|
.map(Arc::new)
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}"))
|
|
})?;
|
|
publish_blocks::publish_blinded_block(
|
|
block,
|
|
chain,
|
|
&network_tx,
|
|
validation_level.broadcast_validation,
|
|
duplicate_block_status_code,
|
|
)
|
|
.await
|
|
})
|
|
},
|
|
);
|
|
|
|
let block_id_or_err = warp::path::param::<BlockId>().or_else(|_| async {
|
|
Err(warp_utils::reject::custom_bad_request(
|
|
"Invalid block ID".to_string(),
|
|
))
|
|
});
|
|
|
|
let beacon_blocks_path_v1 = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blocks"))
|
|
.and(block_id_or_err)
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone());
|
|
|
|
let beacon_blocks_path_any = any_version
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blocks"))
|
|
.and(block_id_or_err)
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone());
|
|
|
|
// GET beacon/blocks/{block_id}
|
|
let get_beacon_block = beacon_blocks_path_any
|
|
.clone()
|
|
.and(warp::path::end())
|
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
|
.then(
|
|
|endpoint_version: EndpointVersion,
|
|
block_id: BlockId,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
accept_header: Option<api_types::Accept>| {
|
|
task_spawner.spawn_async_with_rejection(Priority::P1, async move {
|
|
let (block, execution_optimistic, finalized) =
|
|
block_id.full_block(&chain).await?;
|
|
let fork_name = block
|
|
.fork_name(&chain.spec)
|
|
.map_err(inconsistent_fork_rejection)?;
|
|
|
|
let require_version = match endpoint_version {
|
|
V1 => ResponseIncludesVersion::No,
|
|
V2 => ResponseIncludesVersion::Yes(fork_name),
|
|
_ => return Err(unsupported_version_rejection(endpoint_version)),
|
|
};
|
|
|
|
match accept_header {
|
|
Some(api_types::Accept::Ssz) => Response::builder()
|
|
.status(200)
|
|
.body(block.as_ssz_bytes().into())
|
|
.map(|res: Response<Body>| add_ssz_content_type_header(res))
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_server_error(format!(
|
|
"failed to create response: {}",
|
|
e
|
|
))
|
|
}),
|
|
_ => execution_optimistic_finalized_beacon_response(
|
|
require_version,
|
|
execution_optimistic,
|
|
finalized,
|
|
block,
|
|
)
|
|
.map(|res| warp::reply::json(&res).into_response()),
|
|
}
|
|
.map(|resp| add_consensus_version_header(resp, fork_name))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET beacon/blocks/{block_id}/root
|
|
let get_beacon_block_root = beacon_blocks_path_v1
|
|
.clone()
|
|
.and(warp::path("root"))
|
|
.and(warp::path::end())
|
|
.then(
|
|
|block_id: BlockId,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
// Prioritise requests for the head block root, as it is used by some VCs (including
|
|
// the Lighthouse VC) to create sync committee messages.
|
|
let priority = if let BlockId(eth2::types::BlockId::Head) = block_id {
|
|
Priority::P0
|
|
} else {
|
|
Priority::P1
|
|
};
|
|
task_spawner.blocking_json_task(priority, move || {
|
|
let (block_root, execution_optimistic, finalized) = block_id.root(&chain)?;
|
|
Ok(
|
|
api_types::GenericResponse::from(api_types::RootData::from(block_root))
|
|
.add_execution_optimistic_finalized(execution_optimistic, finalized),
|
|
)
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET beacon/blocks/{block_id}/attestations
|
|
let get_beacon_block_attestations = beacon_blocks_path_any
|
|
.clone()
|
|
.and(warp::path("attestations"))
|
|
.and(warp::path::end())
|
|
.then(
|
|
|endpoint_version: EndpointVersion,
|
|
block_id: BlockId,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || {
|
|
let (block, execution_optimistic, finalized) =
|
|
block_id.blinded_block(&chain)?;
|
|
let fork_name = block
|
|
.fork_name(&chain.spec)
|
|
.map_err(inconsistent_fork_rejection)?;
|
|
let atts = block
|
|
.message()
|
|
.body()
|
|
.attestations()
|
|
.map(|att| att.clone_as_attestation())
|
|
.collect::<Vec<_>>();
|
|
|
|
let require_version = match endpoint_version {
|
|
V1 => ResponseIncludesVersion::No,
|
|
V2 => ResponseIncludesVersion::Yes(fork_name),
|
|
_ => return Err(unsupported_version_rejection(endpoint_version)),
|
|
};
|
|
|
|
let res = execution_optimistic_finalized_beacon_response(
|
|
require_version,
|
|
execution_optimistic,
|
|
finalized,
|
|
&atts,
|
|
)?;
|
|
Ok(add_consensus_version_header(
|
|
warp::reply::json(&res).into_response(),
|
|
fork_name,
|
|
))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET beacon/blinded_blocks/{block_id}
|
|
let get_beacon_blinded_block = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blinded_blocks"))
|
|
.and(block_id_or_err)
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(warp::path::end())
|
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
|
.then(
|
|
|block_id: BlockId,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
accept_header: Option<api_types::Accept>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || {
|
|
let (block, execution_optimistic, finalized) =
|
|
block_id.blinded_block(&chain)?;
|
|
let fork_name = block
|
|
.fork_name(&chain.spec)
|
|
.map_err(inconsistent_fork_rejection)?;
|
|
|
|
match accept_header {
|
|
Some(api_types::Accept::Ssz) => Response::builder()
|
|
.status(200)
|
|
.body(block.as_ssz_bytes().into())
|
|
.map(|res: Response<Body>| add_ssz_content_type_header(res))
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_server_error(format!(
|
|
"failed to create response: {}",
|
|
e
|
|
))
|
|
}),
|
|
_ => {
|
|
// Post as a V2 endpoint so we return the fork version.
|
|
execution_optimistic_finalized_beacon_response(
|
|
ResponseIncludesVersion::Yes(fork_name),
|
|
execution_optimistic,
|
|
finalized,
|
|
block,
|
|
)
|
|
.map(|res| warp::reply::json(&res).into_response())
|
|
}
|
|
}
|
|
.map(|resp| add_consensus_version_header(resp, fork_name))
|
|
})
|
|
},
|
|
);
|
|
|
|
/*
|
|
* beacon/blob_sidecars
|
|
*/
|
|
|
|
// GET beacon/blob_sidecars/{block_id}
|
|
let get_blob_sidecars = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blob_sidecars"))
|
|
.and(block_id_or_err)
|
|
.and(warp::path::end())
|
|
.and(multi_key_query::<api_types::BlobIndicesQuery>())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
|
.then(
|
|
|block_id: BlockId,
|
|
indices_res: Result<api_types::BlobIndicesQuery, warp::Rejection>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
accept_header: Option<api_types::Accept>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || {
|
|
let indices = indices_res?;
|
|
let (block, blob_sidecar_list_filtered, execution_optimistic, finalized) =
|
|
block_id.get_blinded_block_and_blob_list_filtered(indices, &chain)?;
|
|
let fork_name = block
|
|
.fork_name(&chain.spec)
|
|
.map_err(inconsistent_fork_rejection)?;
|
|
|
|
match accept_header {
|
|
Some(api_types::Accept::Ssz) => Response::builder()
|
|
.status(200)
|
|
.body(blob_sidecar_list_filtered.as_ssz_bytes().into())
|
|
.map(|res: Response<Body>| add_ssz_content_type_header(res))
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_server_error(format!(
|
|
"failed to create response: {}",
|
|
e
|
|
))
|
|
}),
|
|
_ => {
|
|
// Post as a V2 endpoint so we return the fork version.
|
|
let res = execution_optimistic_finalized_beacon_response(
|
|
ResponseIncludesVersion::Yes(fork_name),
|
|
execution_optimistic,
|
|
finalized,
|
|
&blob_sidecar_list_filtered,
|
|
)?;
|
|
Ok(warp::reply::json(&res).into_response())
|
|
}
|
|
}
|
|
.map(|resp| add_consensus_version_header(resp, fork_name))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET beacon/blobs/{block_id}
|
|
let get_blobs = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("blobs"))
|
|
.and(block_id_or_err)
|
|
.and(warp::path::end())
|
|
.and(multi_key_query::<api_types::BlobsVersionedHashesQuery>())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
|
.then(
|
|
|block_id: BlockId,
|
|
version_hashes_res: Result<api_types::BlobsVersionedHashesQuery, warp::Rejection>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
accept_header: Option<api_types::Accept>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || {
|
|
let versioned_hashes = version_hashes_res?;
|
|
let response =
|
|
block_id.get_blobs_by_versioned_hashes(versioned_hashes, &chain)?;
|
|
|
|
match accept_header {
|
|
Some(api_types::Accept::Ssz) => Response::builder()
|
|
.status(200)
|
|
.body(response.data.as_ssz_bytes().into())
|
|
.map(|res: Response<Body>| add_ssz_content_type_header(res))
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_server_error(format!(
|
|
"failed to create response: {}",
|
|
e
|
|
))
|
|
}),
|
|
_ => {
|
|
let res = execution_optimistic_finalized_beacon_response(
|
|
ResponseIncludesVersion::No,
|
|
response.metadata.execution_optimistic.unwrap_or(false),
|
|
response.metadata.finalized.unwrap_or(false),
|
|
response.data,
|
|
)?;
|
|
Ok(warp::reply::json(&res).into_response())
|
|
}
|
|
}
|
|
})
|
|
},
|
|
);
|
|
|
|
/*
|
|
* beacon/pool
|
|
*/
|
|
|
|
let beacon_pool_path = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("pool"))
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.boxed();
|
|
|
|
let beacon_pool_path_v2 = eth_v2
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("pool"))
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.boxed();
|
|
|
|
let beacon_pool_path_any = any_version
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("pool"))
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.boxed();
|
|
|
|
let post_beacon_pool_attestations_v2 = post_beacon_pool_attestations_v2(
|
|
&network_tx_filter,
|
|
optional_consensus_version_header_filter,
|
|
&beacon_pool_path_v2,
|
|
);
|
|
|
|
// GET beacon/pool/attestations?committee_index,slot
|
|
let get_beacon_pool_attestations = get_beacon_pool_attestations(&beacon_pool_path_any);
|
|
|
|
// POST beacon/pool/attester_slashings
|
|
let post_beacon_pool_attester_slashings =
|
|
post_beacon_pool_attester_slashings(&network_tx_filter, &beacon_pool_path_any);
|
|
|
|
// GET beacon/pool/attester_slashings
|
|
let get_beacon_pool_attester_slashings =
|
|
get_beacon_pool_attester_slashings(&beacon_pool_path_any);
|
|
|
|
// POST beacon/pool/proposer_slashings
|
|
let post_beacon_pool_proposer_slashings =
|
|
post_beacon_pool_proposer_slashings(&network_tx_filter, &beacon_pool_path);
|
|
|
|
// GET beacon/pool/proposer_slashings
|
|
let get_beacon_pool_proposer_slashings = get_beacon_pool_proposer_slashings(&beacon_pool_path);
|
|
|
|
// POST beacon/pool/voluntary_exits
|
|
let post_beacon_pool_voluntary_exits =
|
|
post_beacon_pool_voluntary_exits(&network_tx_filter, &beacon_pool_path);
|
|
|
|
// GET beacon/pool/voluntary_exits
|
|
let get_beacon_pool_voluntary_exits = get_beacon_pool_voluntary_exits(&beacon_pool_path);
|
|
|
|
// POST beacon/pool/sync_committees
|
|
let post_beacon_pool_sync_committees =
|
|
post_beacon_pool_sync_committees(&network_tx_filter, &beacon_pool_path);
|
|
|
|
// GET beacon/pool/bls_to_execution_changes
|
|
let get_beacon_pool_bls_to_execution_changes =
|
|
get_beacon_pool_bls_to_execution_changes(&beacon_pool_path);
|
|
|
|
// POST beacon/pool/bls_to_execution_changes
|
|
let post_beacon_pool_bls_to_execution_changes =
|
|
post_beacon_pool_bls_to_execution_changes(&network_tx_filter, &beacon_pool_path);
|
|
|
|
let beacon_rewards_path = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("rewards"))
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone());
|
|
|
|
// GET beacon/rewards/blocks/{block_id}
|
|
let get_beacon_rewards_blocks = beacon_rewards_path
|
|
.clone()
|
|
.and(warp::path("blocks"))
|
|
.and(block_id_or_err)
|
|
.and(warp::path::end())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
block_id: BlockId| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let (rewards, execution_optimistic, finalized) =
|
|
standard_block_rewards::compute_beacon_block_rewards(chain, block_id)?;
|
|
Ok(api_types::GenericResponse::from(rewards)).map(|resp| {
|
|
resp.add_execution_optimistic_finalized(execution_optimistic, finalized)
|
|
})
|
|
})
|
|
},
|
|
);
|
|
|
|
/*
|
|
* builder/states
|
|
*/
|
|
|
|
let builder_states_path = eth_v1
|
|
.clone()
|
|
.and(warp::path("builder"))
|
|
.and(warp::path("states"))
|
|
.and(chain_filter.clone());
|
|
|
|
// GET builder/states/{state_id}/expected_withdrawals
|
|
let get_expected_withdrawals = builder_states_path
|
|
.clone()
|
|
.and(task_spawner_filter.clone())
|
|
.and(warp::path::param::<StateId>())
|
|
.and(warp::path("expected_withdrawals"))
|
|
.and(warp::query::<api_types::ExpectedWithdrawalsQuery>())
|
|
.and(warp::path::end())
|
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
|
.then(
|
|
|chain: Arc<BeaconChain<T>>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
state_id: StateId,
|
|
query: api_types::ExpectedWithdrawalsQuery,
|
|
accept_header: Option<api_types::Accept>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || {
|
|
let (state, execution_optimistic, finalized) = state_id.state(&chain)?;
|
|
let proposal_slot = query.proposal_slot.unwrap_or(state.slot() + 1);
|
|
let withdrawals =
|
|
get_next_withdrawals::<T>(&chain, state, state_id, proposal_slot)?;
|
|
|
|
match accept_header {
|
|
Some(api_types::Accept::Ssz) => Response::builder()
|
|
.status(200)
|
|
.body(withdrawals.as_ssz_bytes().into())
|
|
.map(|res: Response<Body>| add_ssz_content_type_header(res))
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_server_error(format!(
|
|
"failed to create response: {}",
|
|
e
|
|
))
|
|
}),
|
|
_ => Ok(warp::reply::json(
|
|
&api_types::ExecutionOptimisticFinalizedResponse {
|
|
data: withdrawals,
|
|
execution_optimistic: Some(execution_optimistic),
|
|
finalized: Some(finalized),
|
|
},
|
|
)
|
|
.into_response()),
|
|
}
|
|
})
|
|
},
|
|
);
|
|
|
|
/*
|
|
* beacon/light_client
|
|
*/
|
|
|
|
let beacon_light_client_path = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("light_client"))
|
|
.and(light_client_server_filter)
|
|
.and(chain_filter.clone());
|
|
|
|
// GET beacon/light_client/bootstrap/{block_root}
|
|
let get_beacon_light_client_bootstrap = beacon_light_client_path
|
|
.clone()
|
|
.and(task_spawner_filter.clone())
|
|
.and(warp::path("bootstrap"))
|
|
.and(warp::path::param::<Hash256>().or_else(|_| async {
|
|
Err(warp_utils::reject::custom_bad_request(
|
|
"Invalid block root value".to_string(),
|
|
))
|
|
}))
|
|
.and(warp::path::end())
|
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
|
.then(
|
|
|light_client_server_enabled: Result<(), Rejection>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
block_root: Hash256,
|
|
accept_header: Option<api_types::Accept>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || {
|
|
light_client_server_enabled?;
|
|
get_light_client_bootstrap::<T>(chain, &block_root, accept_header)
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET beacon/light_client/optimistic_update
|
|
let get_beacon_light_client_optimistic_update = beacon_light_client_path
|
|
.clone()
|
|
.and(task_spawner_filter.clone())
|
|
.and(warp::path("optimistic_update"))
|
|
.and(warp::path::end())
|
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
|
.then(
|
|
|light_client_server_enabled: Result<(), Rejection>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
accept_header: Option<api_types::Accept>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || {
|
|
light_client_server_enabled?;
|
|
let update = chain
|
|
.light_client_server_cache
|
|
.get_latest_optimistic_update()
|
|
.ok_or_else(|| {
|
|
warp_utils::reject::custom_not_found(
|
|
"No LightClientOptimisticUpdate is available".to_string(),
|
|
)
|
|
})?;
|
|
|
|
let fork_name = chain
|
|
.spec
|
|
.fork_name_at_slot::<T::EthSpec>(update.get_slot());
|
|
match accept_header {
|
|
Some(api_types::Accept::Ssz) => Response::builder()
|
|
.status(200)
|
|
.body(update.as_ssz_bytes().into())
|
|
.map(|res: Response<Body>| add_ssz_content_type_header(res))
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_server_error(format!(
|
|
"failed to create response: {}",
|
|
e
|
|
))
|
|
}),
|
|
_ => Ok(warp::reply::json(&beacon_response(
|
|
ResponseIncludesVersion::Yes(fork_name),
|
|
update,
|
|
))
|
|
.into_response()),
|
|
}
|
|
.map(|resp| add_consensus_version_header(resp, fork_name))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET beacon/light_client/finality_update
|
|
let get_beacon_light_client_finality_update = beacon_light_client_path
|
|
.clone()
|
|
.and(task_spawner_filter.clone())
|
|
.and(warp::path("finality_update"))
|
|
.and(warp::path::end())
|
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
|
.then(
|
|
|light_client_server_enabled: Result<(), Rejection>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
accept_header: Option<api_types::Accept>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || {
|
|
light_client_server_enabled?;
|
|
let update = chain
|
|
.light_client_server_cache
|
|
.get_latest_finality_update()
|
|
.ok_or_else(|| {
|
|
warp_utils::reject::custom_not_found(
|
|
"No LightClientFinalityUpdate is available".to_string(),
|
|
)
|
|
})?;
|
|
|
|
let fork_name = chain
|
|
.spec
|
|
.fork_name_at_slot::<T::EthSpec>(update.signature_slot());
|
|
match accept_header {
|
|
Some(api_types::Accept::Ssz) => Response::builder()
|
|
.status(200)
|
|
.body(update.as_ssz_bytes().into())
|
|
.map(|res: Response<Body>| add_ssz_content_type_header(res))
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_server_error(format!(
|
|
"failed to create response: {}",
|
|
e
|
|
))
|
|
}),
|
|
_ => Ok(warp::reply::json(&beacon_response(
|
|
ResponseIncludesVersion::Yes(fork_name),
|
|
update,
|
|
))
|
|
.into_response()),
|
|
}
|
|
.map(|resp| add_consensus_version_header(resp, fork_name))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET beacon/light_client/updates
|
|
let get_beacon_light_client_updates = beacon_light_client_path
|
|
.clone()
|
|
.and(task_spawner_filter.clone())
|
|
.and(warp::path("updates"))
|
|
.and(warp::path::end())
|
|
.and(warp::query::<api_types::LightClientUpdatesQuery>())
|
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
|
.then(
|
|
|light_client_server_enabled: Result<(), Rejection>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
query: LightClientUpdatesQuery,
|
|
accept_header: Option<api_types::Accept>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || {
|
|
light_client_server_enabled?;
|
|
get_light_client_updates::<T>(chain, query, accept_header)
|
|
})
|
|
},
|
|
);
|
|
|
|
/*
|
|
* beacon/rewards
|
|
*/
|
|
|
|
let beacon_rewards_path = eth_v1
|
|
.clone()
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("rewards"))
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone());
|
|
|
|
// POST beacon/rewards/attestations/{epoch}
|
|
let post_beacon_rewards_attestations = beacon_rewards_path
|
|
.clone()
|
|
.and(warp::path("attestations"))
|
|
.and(warp::path::param::<Epoch>())
|
|
.and(warp::path::end())
|
|
.and(warp_utils::json::json())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
epoch: Epoch,
|
|
validators: Vec<ValidatorId>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let attestation_rewards = chain
|
|
.compute_attestation_rewards(epoch, validators)
|
|
.map_err(|e| match e {
|
|
BeaconChainError::MissingBeaconState(root) => {
|
|
warp_utils::reject::custom_not_found(format!(
|
|
"missing state {root:?}",
|
|
))
|
|
}
|
|
BeaconChainError::NoStateForSlot(slot) => {
|
|
warp_utils::reject::custom_not_found(format!(
|
|
"missing state at slot {slot}"
|
|
))
|
|
}
|
|
BeaconChainError::BeaconStateError(
|
|
BeaconStateError::UnknownValidator(validator_index),
|
|
) => warp_utils::reject::custom_bad_request(format!(
|
|
"validator is unknown: {validator_index}"
|
|
)),
|
|
BeaconChainError::ValidatorPubkeyUnknown(pubkey) => {
|
|
warp_utils::reject::custom_bad_request(format!(
|
|
"validator pubkey is unknown: {pubkey:?}"
|
|
))
|
|
}
|
|
e => warp_utils::reject::custom_server_error(format!(
|
|
"unexpected error: {:?}",
|
|
e
|
|
)),
|
|
})?;
|
|
let execution_optimistic =
|
|
chain.is_optimistic_or_invalid_head().unwrap_or_default();
|
|
|
|
Ok(api_types::GenericResponse::from(attestation_rewards))
|
|
.map(|resp| resp.add_execution_optimistic(execution_optimistic))
|
|
})
|
|
},
|
|
);
|
|
|
|
// POST beacon/rewards/sync_committee/{block_id}
|
|
let post_beacon_rewards_sync_committee = beacon_rewards_path
|
|
.clone()
|
|
.and(warp::path("sync_committee"))
|
|
.and(block_id_or_err)
|
|
.and(warp::path::end())
|
|
.and(warp_utils::json::json())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
block_id: BlockId,
|
|
validators: Vec<ValidatorId>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let (rewards, execution_optimistic, finalized) =
|
|
sync_committee_rewards::compute_sync_committee_rewards(
|
|
chain, block_id, validators,
|
|
)?;
|
|
|
|
Ok(api_types::GenericResponse::from(rewards)).map(|resp| {
|
|
resp.add_execution_optimistic_finalized(execution_optimistic, finalized)
|
|
})
|
|
})
|
|
},
|
|
);
|
|
|
|
/*
|
|
* config
|
|
*/
|
|
|
|
let config_path = eth_v1.clone().and(warp::path("config"));
|
|
|
|
// GET config/fork_schedule
|
|
let get_config_fork_schedule = config_path
|
|
.clone()
|
|
.and(warp::path("fork_schedule"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let forks = ForkName::list_all()
|
|
.into_iter()
|
|
.filter_map(|fork_name| chain.spec.fork_for_name(fork_name))
|
|
.collect::<Vec<_>>();
|
|
Ok(api_types::GenericResponse::from(forks))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET config/spec
|
|
let get_config_spec = config_path
|
|
.clone()
|
|
.and(warp::path("spec"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
move |task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
|
let config_and_preset =
|
|
ConfigAndPreset::from_chain_spec::<T::EthSpec>(&chain.spec);
|
|
Ok(api_types::GenericResponse::from(config_and_preset))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET config/deposit_contract
|
|
let get_config_deposit_contract = config_path
|
|
.and(warp::path("deposit_contract"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
Ok(api_types::GenericResponse::from(
|
|
api_types::DepositContractData {
|
|
address: chain.spec.deposit_contract_address,
|
|
chain_id: chain.spec.deposit_chain_id,
|
|
},
|
|
))
|
|
})
|
|
},
|
|
);
|
|
|
|
/*
|
|
* debug
|
|
*/
|
|
|
|
// GET debug/beacon/data_column_sidecars/{block_id}
|
|
let get_debug_data_column_sidecars = eth_v1
|
|
.clone()
|
|
.and(warp::path("debug"))
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("data_column_sidecars"))
|
|
.and(block_id_or_err)
|
|
.and(warp::path::end())
|
|
.and(multi_key_query::<api_types::DataColumnIndicesQuery>())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
|
.then(
|
|
|block_id: BlockId,
|
|
indices_res: Result<api_types::DataColumnIndicesQuery, warp::Rejection>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>,
|
|
accept_header: Option<api_types::Accept>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || {
|
|
let indices = indices_res?;
|
|
let (data_columns, fork_name, execution_optimistic, finalized) =
|
|
block_id.get_data_columns(indices, &chain)?;
|
|
|
|
match accept_header {
|
|
Some(api_types::Accept::Ssz) => Response::builder()
|
|
.status(200)
|
|
.body(data_columns.as_ssz_bytes().into())
|
|
.map(|res: Response<Body>| add_ssz_content_type_header(res))
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_server_error(format!(
|
|
"failed to create response: {}",
|
|
e
|
|
))
|
|
}),
|
|
_ => {
|
|
// Post as a V2 endpoint so we return the fork version.
|
|
let res = execution_optimistic_finalized_beacon_response(
|
|
ResponseIncludesVersion::Yes(fork_name),
|
|
execution_optimistic,
|
|
finalized,
|
|
&data_columns,
|
|
)?;
|
|
Ok(warp::reply::json(&res).into_response())
|
|
}
|
|
}
|
|
.map(|resp| add_consensus_version_header(resp, fork_name))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET debug/beacon/states/{state_id}
|
|
let get_debug_beacon_states = any_version
|
|
.clone()
|
|
.and(warp::path("debug"))
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("states"))
|
|
.and(warp::path::param::<StateId>().or_else(|_| async {
|
|
Err(warp_utils::reject::custom_bad_request(
|
|
"Invalid state ID".to_string(),
|
|
))
|
|
}))
|
|
.and(warp::path::end())
|
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|_endpoint_version: EndpointVersion,
|
|
state_id: StateId,
|
|
accept_header: Option<api_types::Accept>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || match accept_header {
|
|
Some(api_types::Accept::Ssz) => {
|
|
// We can ignore the optimistic status for the "fork" since it's a
|
|
// specification constant that doesn't change across competing heads of the
|
|
// beacon chain.
|
|
let t = std::time::Instant::now();
|
|
let (state, _execution_optimistic, _finalized) = state_id.state(&chain)?;
|
|
let fork_name = state
|
|
.fork_name(&chain.spec)
|
|
.map_err(inconsistent_fork_rejection)?;
|
|
let timer = metrics::start_timer(&metrics::HTTP_API_STATE_SSZ_ENCODE_TIMES);
|
|
let response_bytes = state.as_ssz_bytes();
|
|
drop(timer);
|
|
debug!(
|
|
total_time_ms = t.elapsed().as_millis(),
|
|
target_slot = %state.slot(),
|
|
"HTTP state load"
|
|
);
|
|
|
|
Response::builder()
|
|
.status(200)
|
|
.body(response_bytes.into())
|
|
.map(|res: Response<Body>| add_ssz_content_type_header(res))
|
|
.map(|resp: warp::reply::Response| {
|
|
add_consensus_version_header(resp, fork_name)
|
|
})
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_server_error(format!(
|
|
"failed to create response: {}",
|
|
e
|
|
))
|
|
})
|
|
}
|
|
_ => state_id.map_state_and_execution_optimistic_and_finalized(
|
|
&chain,
|
|
|state, execution_optimistic, finalized| {
|
|
let fork_name = state
|
|
.fork_name(&chain.spec)
|
|
.map_err(inconsistent_fork_rejection)?;
|
|
let res = execution_optimistic_finalized_beacon_response(
|
|
ResponseIncludesVersion::Yes(fork_name),
|
|
execution_optimistic,
|
|
finalized,
|
|
&state,
|
|
)?;
|
|
Ok(add_consensus_version_header(
|
|
warp::reply::json(&res).into_response(),
|
|
fork_name,
|
|
))
|
|
},
|
|
),
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET debug/beacon/heads
|
|
let get_debug_beacon_heads = any_version
|
|
.clone()
|
|
.and(warp::path("debug"))
|
|
.and(warp::path("beacon"))
|
|
.and(warp::path("heads"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|endpoint_version: EndpointVersion,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let heads = chain
|
|
.heads()
|
|
.into_iter()
|
|
.map(|(root, slot)| {
|
|
let execution_optimistic = if endpoint_version == V1 {
|
|
None
|
|
} else if endpoint_version == V2 {
|
|
chain
|
|
.canonical_head
|
|
.fork_choice_read_lock()
|
|
.is_optimistic_or_invalid_block(&root)
|
|
.ok()
|
|
} else {
|
|
return Err(unsupported_version_rejection(endpoint_version));
|
|
};
|
|
Ok(api_types::ChainHeadData {
|
|
slot,
|
|
root,
|
|
execution_optimistic,
|
|
})
|
|
})
|
|
.collect::<Result<Vec<_>, warp::Rejection>>();
|
|
Ok(api_types::GenericResponse::from(heads?))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET debug/fork_choice
|
|
let get_debug_fork_choice = eth_v1
|
|
.clone()
|
|
.and(warp::path("debug"))
|
|
.and(warp::path("fork_choice"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let beacon_fork_choice = chain.canonical_head.fork_choice_read_lock();
|
|
|
|
let proto_array = beacon_fork_choice.proto_array().core_proto_array();
|
|
|
|
let fork_choice_nodes = proto_array
|
|
.nodes
|
|
.iter()
|
|
.map(|node| {
|
|
let execution_status = if node.execution_status.is_execution_enabled() {
|
|
Some(node.execution_status.to_string())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
ForkChoiceNode {
|
|
slot: node.slot,
|
|
block_root: node.root,
|
|
parent_root: node
|
|
.parent
|
|
.and_then(|index| proto_array.nodes.get(index))
|
|
.map(|parent| parent.root),
|
|
justified_epoch: node.justified_checkpoint.epoch,
|
|
finalized_epoch: node.finalized_checkpoint.epoch,
|
|
weight: node.weight,
|
|
validity: execution_status,
|
|
execution_block_hash: node
|
|
.execution_status
|
|
.block_hash()
|
|
.map(|block_hash| block_hash.into_root()),
|
|
extra_data: ForkChoiceExtraData {
|
|
target_root: node.target_root,
|
|
justified_root: node.justified_checkpoint.root,
|
|
finalized_root: node.finalized_checkpoint.root,
|
|
unrealized_justified_root: node
|
|
.unrealized_justified_checkpoint
|
|
.map(|checkpoint| checkpoint.root),
|
|
unrealized_finalized_root: node
|
|
.unrealized_finalized_checkpoint
|
|
.map(|checkpoint| checkpoint.root),
|
|
unrealized_justified_epoch: node
|
|
.unrealized_justified_checkpoint
|
|
.map(|checkpoint| checkpoint.epoch),
|
|
unrealized_finalized_epoch: node
|
|
.unrealized_finalized_checkpoint
|
|
.map(|checkpoint| checkpoint.epoch),
|
|
execution_status: node.execution_status.to_string(),
|
|
best_child: node
|
|
.best_child
|
|
.and_then(|index| proto_array.nodes.get(index))
|
|
.map(|child| child.root),
|
|
best_descendant: node
|
|
.best_descendant
|
|
.and_then(|index| proto_array.nodes.get(index))
|
|
.map(|descendant| descendant.root),
|
|
},
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
Ok(ForkChoice {
|
|
justified_checkpoint: beacon_fork_choice.justified_checkpoint(),
|
|
finalized_checkpoint: beacon_fork_choice.finalized_checkpoint(),
|
|
fork_choice_nodes,
|
|
})
|
|
})
|
|
},
|
|
);
|
|
|
|
/*
|
|
* node
|
|
*/
|
|
|
|
// GET node/identity
|
|
let get_node_identity = eth_v1
|
|
.clone()
|
|
.and(warp::path("node"))
|
|
.and(warp::path("identity"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(network_globals.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>,
|
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let enr = network_globals.local_enr();
|
|
let p2p_addresses = enr.multiaddr_p2p_tcp();
|
|
let discovery_addresses = enr.multiaddr_p2p_udp();
|
|
Ok(api_types::GenericResponse::from(api_types::IdentityData {
|
|
peer_id: network_globals.local_peer_id().to_base58(),
|
|
enr: enr.to_base64(),
|
|
p2p_addresses: p2p_addresses.iter().map(|a| a.to_string()).collect(),
|
|
discovery_addresses: discovery_addresses
|
|
.iter()
|
|
.map(|a| a.to_string())
|
|
.collect(),
|
|
metadata: utils::from_meta_data::<T::EthSpec>(
|
|
&network_globals.local_metadata,
|
|
&chain.spec,
|
|
),
|
|
}))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET node/version
|
|
let get_node_version = eth_v1
|
|
.clone()
|
|
.and(warp::path("node"))
|
|
.and(warp::path("version"))
|
|
.and(warp::path::end())
|
|
// Bypass the `task_spawner` since this method returns a static string.
|
|
.then(|| async {
|
|
warp::reply::json(&api_types::GenericResponse::from(api_types::VersionData {
|
|
version: version_with_platform(),
|
|
}))
|
|
.into_response()
|
|
});
|
|
|
|
// GET node/syncing
|
|
let get_node_syncing = eth_v1
|
|
.clone()
|
|
.and(warp::path("node"))
|
|
.and(warp::path("syncing"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(network_globals.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>,
|
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
async move {
|
|
let el_offline = if let Some(el) = &chain.execution_layer {
|
|
el.is_offline_or_erroring().await
|
|
} else {
|
|
true
|
|
};
|
|
|
|
task_spawner
|
|
.blocking_json_task(Priority::P0, move || {
|
|
let (head, head_execution_status) = chain
|
|
.canonical_head
|
|
.head_and_execution_status()
|
|
.map_err(warp_utils::reject::unhandled_error)?;
|
|
let head_slot = head.head_slot();
|
|
let current_slot =
|
|
chain.slot_clock.now_or_genesis().ok_or_else(|| {
|
|
warp_utils::reject::custom_server_error(
|
|
"Unable to read slot clock".into(),
|
|
)
|
|
})?;
|
|
|
|
// Taking advantage of saturating subtraction on slot.
|
|
let sync_distance = current_slot - head_slot;
|
|
|
|
let is_optimistic = head_execution_status.is_optimistic_or_invalid();
|
|
|
|
// When determining sync status, make an exception for single-node
|
|
// testnets with 0 peers.
|
|
let sync_state = network_globals.sync_state.read();
|
|
let is_synced = sync_state.is_synced()
|
|
|| (sync_state.is_stalled()
|
|
&& network_globals.config.target_peers == 0);
|
|
drop(sync_state);
|
|
|
|
let syncing_data = api_types::SyncingData {
|
|
is_syncing: !is_synced,
|
|
is_optimistic,
|
|
el_offline,
|
|
head_slot,
|
|
sync_distance,
|
|
};
|
|
|
|
Ok(api_types::GenericResponse::from(syncing_data))
|
|
})
|
|
.await
|
|
}
|
|
},
|
|
);
|
|
|
|
// GET node/health
|
|
let get_node_health = eth_v1
|
|
.clone()
|
|
.and(warp::path("node"))
|
|
.and(warp::path("health"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(network_globals.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>,
|
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
async move {
|
|
let el_offline = if let Some(el) = &chain.execution_layer {
|
|
el.is_offline_or_erroring().await
|
|
} else {
|
|
true
|
|
};
|
|
|
|
task_spawner
|
|
.blocking_response_task(Priority::P0, move || {
|
|
let is_optimistic = chain
|
|
.is_optimistic_or_invalid_head()
|
|
.map_err(warp_utils::reject::unhandled_error)?;
|
|
|
|
let is_syncing = !network_globals.sync_state.read().is_synced();
|
|
|
|
if el_offline {
|
|
Err(warp_utils::reject::not_synced(
|
|
"execution layer is offline".to_string(),
|
|
))
|
|
} else if is_syncing || is_optimistic {
|
|
Ok(warp::reply::with_status(
|
|
warp::reply(),
|
|
warp::http::StatusCode::PARTIAL_CONTENT,
|
|
))
|
|
} else {
|
|
Ok(warp::reply::with_status(
|
|
warp::reply(),
|
|
warp::http::StatusCode::OK,
|
|
))
|
|
}
|
|
})
|
|
.await
|
|
}
|
|
},
|
|
);
|
|
|
|
// GET node/peers/{peer_id}
|
|
let get_node_peers_by_id = eth_v1
|
|
.clone()
|
|
.and(warp::path("node"))
|
|
.and(warp::path("peers"))
|
|
.and(warp::path::param::<String>())
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(network_globals.clone())
|
|
.then(
|
|
|requested_peer_id: String,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let peer_id = PeerId::from_bytes(
|
|
&bs58::decode(requested_peer_id.as_str())
|
|
.into_vec()
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_bad_request(format!(
|
|
"invalid peer id: {}",
|
|
e
|
|
))
|
|
})?,
|
|
)
|
|
.map_err(|_| {
|
|
warp_utils::reject::custom_bad_request("invalid peer id.".to_string())
|
|
})?;
|
|
|
|
if let Some(peer_info) = network_globals.peers.read().peer_info(&peer_id) {
|
|
let address = if let Some(multiaddr) = peer_info.seen_multiaddrs().next() {
|
|
multiaddr.to_string()
|
|
} else if let Some(addr) = peer_info.listening_addresses().first() {
|
|
addr.to_string()
|
|
} else {
|
|
String::new()
|
|
};
|
|
|
|
// the eth2 API spec implies only peers we have been connected to at some point should be included.
|
|
if let Some(&dir) = peer_info.connection_direction() {
|
|
return Ok(api_types::GenericResponse::from(api_types::PeerData {
|
|
peer_id: peer_id.to_string(),
|
|
enr: peer_info.enr().map(|enr| enr.to_base64()),
|
|
last_seen_p2p_address: address,
|
|
direction: dir.into(),
|
|
state: peer_info.connection_status().clone().into(),
|
|
}));
|
|
}
|
|
}
|
|
Err(warp_utils::reject::custom_not_found(
|
|
"peer not found.".to_string(),
|
|
))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET node/peers
|
|
let get_node_peers = eth_v1
|
|
.clone()
|
|
.and(warp::path("node"))
|
|
.and(warp::path("peers"))
|
|
.and(warp::path::end())
|
|
.and(multi_key_query::<api_types::PeersQuery>())
|
|
.and(task_spawner_filter.clone())
|
|
.and(network_globals.clone())
|
|
.then(
|
|
|query_res: Result<api_types::PeersQuery, warp::Rejection>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let query = query_res?;
|
|
let mut peers: Vec<api_types::PeerData> = Vec::new();
|
|
network_globals
|
|
.peers
|
|
.read()
|
|
.peers()
|
|
.for_each(|(peer_id, peer_info)| {
|
|
let address =
|
|
if let Some(multiaddr) = peer_info.seen_multiaddrs().next() {
|
|
multiaddr.to_string()
|
|
} else if let Some(addr) = peer_info.listening_addresses().first() {
|
|
addr.to_string()
|
|
} else {
|
|
String::new()
|
|
};
|
|
|
|
// the eth2 API spec implies only peers we have been connected to at some point should be included.
|
|
if let Some(&dir) = peer_info.connection_direction() {
|
|
let direction = dir.into();
|
|
let state = peer_info.connection_status().clone().into();
|
|
|
|
let state_matches = query
|
|
.state
|
|
.as_ref()
|
|
.is_none_or(|states| states.contains(&state));
|
|
let direction_matches = query
|
|
.direction
|
|
.as_ref()
|
|
.is_none_or(|directions| directions.contains(&direction));
|
|
|
|
if state_matches && direction_matches {
|
|
peers.push(api_types::PeerData {
|
|
peer_id: peer_id.to_string(),
|
|
enr: peer_info.enr().map(|enr| enr.to_base64()),
|
|
last_seen_p2p_address: address,
|
|
direction,
|
|
state,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
Ok(api_types::PeersData {
|
|
meta: api_types::PeersMetaData {
|
|
count: peers.len() as u64,
|
|
},
|
|
data: peers,
|
|
})
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET node/peer_count
|
|
let get_node_peer_count = eth_v1
|
|
.clone()
|
|
.and(warp::path("node"))
|
|
.and(warp::path("peer_count"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(network_globals.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>,
|
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let mut connected: u64 = 0;
|
|
let mut connecting: u64 = 0;
|
|
let mut disconnected: u64 = 0;
|
|
let mut disconnecting: u64 = 0;
|
|
|
|
network_globals
|
|
.peers
|
|
.read()
|
|
.peers()
|
|
.for_each(|(_, peer_info)| {
|
|
let state =
|
|
api_types::PeerState::from(peer_info.connection_status().clone());
|
|
match state {
|
|
api_types::PeerState::Connected => connected += 1,
|
|
api_types::PeerState::Connecting => connecting += 1,
|
|
api_types::PeerState::Disconnected => disconnected += 1,
|
|
api_types::PeerState::Disconnecting => disconnecting += 1,
|
|
}
|
|
});
|
|
|
|
Ok(api_types::GenericResponse::from(api_types::PeerCount {
|
|
connected,
|
|
connecting,
|
|
disconnected,
|
|
disconnecting,
|
|
}))
|
|
})
|
|
},
|
|
);
|
|
/*
|
|
* validator
|
|
*/
|
|
|
|
// GET validator/duties/proposer/{epoch}
|
|
let get_validator_duties_proposer = get_validator_duties_proposer(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
not_while_syncing_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// GET validator/blocks/{slot}
|
|
let get_validator_blocks = get_validator_blocks(
|
|
any_version.clone().clone(),
|
|
chain_filter.clone(),
|
|
not_while_syncing_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// GET validator/blinded_blocks/{slot}
|
|
let get_validator_blinded_blocks = get_validator_blinded_blocks(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
not_while_syncing_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// GET validator/attestation_data?slot,committee_index
|
|
let get_validator_attestation_data = get_validator_attestation_data(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
not_while_syncing_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// GET validator/aggregate_attestation?attestation_data_root,slot
|
|
let get_validator_aggregate_attestation = get_validator_aggregate_attestation(
|
|
any_version.clone().clone(),
|
|
chain_filter.clone(),
|
|
not_while_syncing_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// POST validator/duties/attester/{epoch}
|
|
let post_validator_duties_attester = post_validator_duties_attester(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
not_while_syncing_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// POST validator/duties/sync/{epoch}
|
|
let post_validator_duties_sync = post_validator_duties_sync(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
not_while_syncing_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// GET validator/sync_committee_contribution
|
|
let get_validator_sync_committee_contribution = get_validator_sync_committee_contribution(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
not_while_syncing_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// POST validator/aggregate_and_proofs
|
|
let post_validator_aggregate_and_proofs = post_validator_aggregate_and_proofs(
|
|
any_version.clone().clone(),
|
|
chain_filter.clone(),
|
|
network_tx_filter.clone(),
|
|
not_while_syncing_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
let post_validator_contribution_and_proofs = post_validator_contribution_and_proofs(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
network_tx_filter.clone(),
|
|
not_while_syncing_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// POST validator/beacon_committee_subscriptions
|
|
let post_validator_beacon_committee_subscriptions =
|
|
post_validator_beacon_committee_subscriptions(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
validator_subscription_tx_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// POST validator/prepare_beacon_proposer
|
|
let post_validator_prepare_beacon_proposer = post_validator_prepare_beacon_proposer(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
network_tx_filter.clone(),
|
|
not_while_syncing_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// POST validator/register_validator
|
|
let post_validator_register_validator = post_validator_register_validator(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
// POST validator/sync_committee_subscriptions
|
|
let post_validator_sync_committee_subscriptions = post_validator_sync_committee_subscriptions(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
validator_subscription_tx_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// POST validator/liveness/{epoch}
|
|
let post_validator_liveness_epoch = post_validator_liveness_epoch(
|
|
eth_v1.clone().clone(),
|
|
chain_filter.clone(),
|
|
task_spawner_filter.clone(),
|
|
);
|
|
|
|
// POST lighthouse/finalize
|
|
let post_lighthouse_finalize = warp::path("lighthouse")
|
|
.and(warp::path("finalize"))
|
|
.and(warp::path::end())
|
|
.and(warp_utils::json::json())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|request_data: api_types::ManualFinalizationRequestData,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
|
let checkpoint = Checkpoint {
|
|
epoch: request_data.epoch,
|
|
root: request_data.block_root,
|
|
};
|
|
|
|
chain
|
|
.manually_finalize_state(request_data.state_root, checkpoint)
|
|
.map(|_| api_types::GenericResponse::from(request_data))
|
|
.map_err(|e| {
|
|
warp_utils::reject::custom_bad_request(format!(
|
|
"Failed to finalize state due to error: {e:?}"
|
|
))
|
|
})
|
|
})
|
|
},
|
|
);
|
|
|
|
// POST lighthouse/compaction
|
|
let post_lighthouse_compaction = warp::path("lighthouse")
|
|
.and(warp::path("compaction"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
|
chain.manually_compact_database();
|
|
Ok(api_types::GenericResponse::from(String::from(
|
|
"Triggered manual compaction",
|
|
)))
|
|
})
|
|
},
|
|
);
|
|
|
|
// POST lighthouse/add_peer
|
|
let post_lighthouse_add_peer = warp::path("lighthouse")
|
|
.and(warp::path("add_peer"))
|
|
.and(warp::path::end())
|
|
.and(warp_utils::json::json())
|
|
.and(task_spawner_filter.clone())
|
|
.and(network_globals.clone())
|
|
.and(network_tx_filter.clone())
|
|
.then(
|
|
|request_data: api_types::AdminPeer,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>,
|
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
|
let enr = Enr::from_str(&request_data.enr).map_err(|e| {
|
|
warp_utils::reject::custom_bad_request(format!("invalid enr error {}", e))
|
|
})?;
|
|
info!(
|
|
peer_id = %enr.peer_id(),
|
|
multiaddr = ?enr.multiaddr(),
|
|
"Adding trusted peer"
|
|
);
|
|
network_globals.add_trusted_peer(enr.clone());
|
|
|
|
utils::publish_network_message(
|
|
&network_tx,
|
|
NetworkMessage::ConnectTrustedPeer(enr),
|
|
)?;
|
|
|
|
Ok(())
|
|
})
|
|
},
|
|
);
|
|
|
|
// POST lighthouse/remove_peer
|
|
let post_lighthouse_remove_peer = warp::path("lighthouse")
|
|
.and(warp::path("remove_peer"))
|
|
.and(warp::path::end())
|
|
.and(warp_utils::json::json())
|
|
.and(task_spawner_filter.clone())
|
|
.and(network_globals.clone())
|
|
.and(network_tx_filter.clone())
|
|
.then(
|
|
|request_data: api_types::AdminPeer,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>,
|
|
network_tx: UnboundedSender<NetworkMessage<T::EthSpec>>| {
|
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
|
let enr = Enr::from_str(&request_data.enr).map_err(|e| {
|
|
warp_utils::reject::custom_bad_request(format!("invalid enr error {}", e))
|
|
})?;
|
|
info!(
|
|
peer_id = %enr.peer_id(),
|
|
multiaddr = ?enr.multiaddr(),
|
|
"Removing trusted peer"
|
|
);
|
|
network_globals.remove_trusted_peer(enr.clone());
|
|
|
|
utils::publish_network_message(
|
|
&network_tx,
|
|
NetworkMessage::DisconnectTrustedPeer(enr),
|
|
)?;
|
|
|
|
Ok(())
|
|
})
|
|
},
|
|
);
|
|
|
|
// POST lighthouse/liveness
|
|
let post_lighthouse_liveness = warp::path("lighthouse")
|
|
.and(warp::path("liveness"))
|
|
.and(warp::path::end())
|
|
.and(warp_utils::json::json())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|request_data: api_types::LivenessRequestData,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
|
// Ensure the request is for either the current, previous or next epoch.
|
|
let current_epoch =
|
|
chain.epoch().map_err(warp_utils::reject::unhandled_error)?;
|
|
let prev_epoch = current_epoch.saturating_sub(Epoch::new(1));
|
|
let next_epoch = current_epoch.saturating_add(Epoch::new(1));
|
|
|
|
if request_data.epoch < prev_epoch || request_data.epoch > next_epoch {
|
|
return Err(warp_utils::reject::custom_bad_request(format!(
|
|
"request epoch {} is more than one epoch from the current epoch {}",
|
|
request_data.epoch, current_epoch
|
|
)));
|
|
}
|
|
|
|
let liveness: Vec<api_types::LivenessResponseData> = request_data
|
|
.indices
|
|
.iter()
|
|
.cloned()
|
|
.map(|index| {
|
|
let is_live =
|
|
chain.validator_seen_at_epoch(index as usize, request_data.epoch);
|
|
api_types::LivenessResponseData {
|
|
index,
|
|
epoch: request_data.epoch,
|
|
is_live,
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
Ok(api_types::GenericResponse::from(liveness))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/health
|
|
let get_lighthouse_health = warp::path("lighthouse")
|
|
.and(warp::path("health"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.then(|task_spawner: TaskSpawner<T::EthSpec>| {
|
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
|
eth2::lighthouse::Health::observe()
|
|
.map(api_types::GenericResponse::from)
|
|
.map_err(warp_utils::reject::custom_bad_request)
|
|
})
|
|
});
|
|
|
|
// GET lighthouse/ui/health
|
|
let get_lighthouse_ui_health = warp::path("lighthouse")
|
|
.and(warp::path("ui"))
|
|
.and(warp::path("health"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(system_info_filter)
|
|
.and(app_start_filter)
|
|
.and(data_dir_filter)
|
|
.and(network_globals.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>,
|
|
sysinfo,
|
|
app_start: std::time::Instant,
|
|
data_dir,
|
|
network_globals| {
|
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
|
let app_uptime = app_start.elapsed().as_secs();
|
|
Ok(api_types::GenericResponse::from(observe_system_health_bn(
|
|
sysinfo,
|
|
data_dir,
|
|
app_uptime,
|
|
network_globals,
|
|
)))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/ui/validator_count
|
|
let get_lighthouse_ui_validator_count = warp::path("lighthouse")
|
|
.and(warp::path("ui"))
|
|
.and(warp::path("validator_count"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
ui::get_validator_count(chain).map(api_types::GenericResponse::from)
|
|
})
|
|
},
|
|
);
|
|
|
|
// POST lighthouse/ui/validator_metrics
|
|
let post_lighthouse_ui_validator_metrics = warp::path("lighthouse")
|
|
.and(warp::path("ui"))
|
|
.and(warp::path("validator_metrics"))
|
|
.and(warp::path::end())
|
|
.and(warp_utils::json::json())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|request_data: ui::ValidatorMetricsRequestData,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
ui::post_validator_monitor_metrics(request_data, chain)
|
|
.map(api_types::GenericResponse::from)
|
|
})
|
|
},
|
|
);
|
|
|
|
// POST lighthouse/ui/validator_info
|
|
let post_lighthouse_ui_validator_info = warp::path("lighthouse")
|
|
.and(warp::path("ui"))
|
|
.and(warp::path("validator_info"))
|
|
.and(warp::path::end())
|
|
.and(warp_utils::json::json())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|request_data: ui::ValidatorInfoRequestData,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
ui::get_validator_info(request_data, chain)
|
|
.map(api_types::GenericResponse::from)
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/syncing
|
|
let get_lighthouse_syncing = warp::path("lighthouse")
|
|
.and(warp::path("syncing"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(network_globals.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>,
|
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>| {
|
|
task_spawner.blocking_json_task(Priority::P0, move || {
|
|
Ok(api_types::GenericResponse::from(
|
|
network_globals.sync_state(),
|
|
))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/nat
|
|
let get_lighthouse_nat = warp::path("lighthouse")
|
|
.and(warp::path("nat"))
|
|
.and(task_spawner_filter.clone())
|
|
.and(warp::path::end())
|
|
.then(|task_spawner: TaskSpawner<T::EthSpec>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
Ok(api_types::GenericResponse::from(observe_nat()))
|
|
})
|
|
});
|
|
|
|
// GET lighthouse/peers
|
|
let get_lighthouse_peers = warp::path("lighthouse")
|
|
.and(warp::path("peers"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(network_globals.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>,
|
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
Ok(network_globals
|
|
.peers
|
|
.read()
|
|
.peers()
|
|
.map(|(peer_id, peer_info)| peer::Peer {
|
|
peer_id: peer_id.to_string(),
|
|
peer_info: peer_info.clone(),
|
|
})
|
|
.collect::<Vec<_>>())
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/peers/connected
|
|
let get_lighthouse_peers_connected = warp::path("lighthouse")
|
|
.and(warp::path("peers"))
|
|
.and(warp::path("connected"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(network_globals)
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>,
|
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
let mut peers = vec![];
|
|
for (peer_id, peer_info) in network_globals.peers.read().connected_peers() {
|
|
peers.push(peer::Peer {
|
|
peer_id: peer_id.to_string(),
|
|
peer_info: peer_info.clone(),
|
|
});
|
|
}
|
|
Ok(peers)
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/proto_array
|
|
let get_lighthouse_proto_array = warp::path("lighthouse")
|
|
.and(warp::path("proto_array"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_response_task(Priority::P1, move || {
|
|
Ok::<_, warp::Rejection>(warp::reply::json(
|
|
&api_types::GenericResponseRef::from(
|
|
chain
|
|
.canonical_head
|
|
.fork_choice_read_lock()
|
|
.proto_array()
|
|
.core_proto_array(),
|
|
),
|
|
))
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/validator_inclusion/{epoch}/{validator_id}
|
|
let get_lighthouse_validator_inclusion_global = warp::path("lighthouse")
|
|
.and(warp::path("validator_inclusion"))
|
|
.and(warp::path::param::<Epoch>())
|
|
.and(warp::path::param::<ValidatorId>())
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|epoch: Epoch,
|
|
validator_id: ValidatorId,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
validator_inclusion::validator_inclusion_data(epoch, &validator_id, &chain)
|
|
.map(api_types::GenericResponse::from)
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/validator_inclusion/{epoch}/global
|
|
let get_lighthouse_validator_inclusion = warp::path("lighthouse")
|
|
.and(warp::path("validator_inclusion"))
|
|
.and(warp::path::param::<Epoch>())
|
|
.and(warp::path("global"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|epoch: Epoch, task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
validator_inclusion::global_validator_inclusion_data(epoch, &chain)
|
|
.map(api_types::GenericResponse::from)
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/staking
|
|
let get_lighthouse_staking = warp::path("lighthouse")
|
|
.and(warp::path("staking"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.then(|task_spawner: TaskSpawner<T::EthSpec>| {
|
|
// This API is fairly useless since we abolished the distinction between staking and
|
|
// non-staking nodes. We keep it for backwards-compatibility with LH v7.0.0, and in case
|
|
// we want to reintroduce the distinction in future.
|
|
task_spawner.blocking_json_task(Priority::P1, move || Ok(()))
|
|
});
|
|
|
|
let database_path = warp::path("lighthouse").and(warp::path("database"));
|
|
|
|
// GET lighthouse/database/info
|
|
let get_lighthouse_database_info = database_path
|
|
.and(warp::path("info"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || database::info(chain))
|
|
},
|
|
);
|
|
|
|
// POST lighthouse/database/reconstruct
|
|
let post_lighthouse_database_reconstruct = database_path
|
|
.and(warp::path("reconstruct"))
|
|
.and(warp::path::end())
|
|
.and(not_while_syncing_filter)
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|not_synced_filter: Result<(), Rejection>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
not_synced_filter?;
|
|
chain.store_migrator.process_reconstruction();
|
|
Ok("success")
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/custody/info
|
|
let get_lighthouse_custody_info = warp::path("lighthouse")
|
|
.and(warp::path("custody"))
|
|
.and(warp::path("info"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || custody::info(chain))
|
|
},
|
|
);
|
|
|
|
// POST lighthouse/custody/backfill
|
|
let post_lighthouse_custody_backfill = warp::path("lighthouse")
|
|
.and(warp::path("custody"))
|
|
.and(warp::path("backfill"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
// Calling this endpoint will trigger custody backfill once `effective_epoch``
|
|
// is finalized.
|
|
let effective_epoch = chain
|
|
.canonical_head
|
|
.cached_head()
|
|
.head_slot()
|
|
.epoch(T::EthSpec::slots_per_epoch())
|
|
+ 1;
|
|
let custody_context = chain.data_availability_checker.custody_context();
|
|
// Reset validator custody requirements to `effective_epoch` with the latest
|
|
// cgc requiremnets.
|
|
custody_context.reset_validator_custody_requirements(effective_epoch);
|
|
// Update `DataColumnCustodyInfo` to reflect the custody change.
|
|
chain.update_data_column_custody_info(Some(
|
|
effective_epoch.start_slot(T::EthSpec::slots_per_epoch()),
|
|
));
|
|
Ok(())
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/analysis/block_rewards
|
|
let get_lighthouse_block_rewards = warp::path("lighthouse")
|
|
.and(warp::path("analysis"))
|
|
.and(warp::path("block_rewards"))
|
|
.and(warp::query::<eth2::lighthouse::BlockRewardsQuery>())
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(|query, task_spawner: TaskSpawner<T::EthSpec>, chain| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
block_rewards::get_block_rewards(query, chain)
|
|
})
|
|
});
|
|
|
|
// POST lighthouse/analysis/block_rewards
|
|
let post_lighthouse_block_rewards = warp::path("lighthouse")
|
|
.and(warp::path("analysis"))
|
|
.and(warp::path("block_rewards"))
|
|
.and(warp_utils::json::json())
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(|blocks, task_spawner: TaskSpawner<T::EthSpec>, chain| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
block_rewards::compute_block_rewards(blocks, chain)
|
|
})
|
|
});
|
|
|
|
// GET lighthouse/analysis/attestation_performance/{index}
|
|
let get_lighthouse_attestation_performance = warp::path("lighthouse")
|
|
.and(warp::path("analysis"))
|
|
.and(warp::path("attestation_performance"))
|
|
.and(warp::path::param::<String>())
|
|
.and(warp::query::<eth2::lighthouse::AttestationPerformanceQuery>())
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|target, query, task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
attestation_performance::get_attestation_performance(target, query, chain)
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/analysis/block_packing_efficiency
|
|
let get_lighthouse_block_packing_efficiency = warp::path("lighthouse")
|
|
.and(warp::path("analysis"))
|
|
.and(warp::path("block_packing_efficiency"))
|
|
.and(warp::query::<eth2::lighthouse::BlockPackingEfficiencyQuery>())
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|query, task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_json_task(Priority::P1, move || {
|
|
block_packing_efficiency::get_block_packing_efficiency(query, chain)
|
|
})
|
|
},
|
|
);
|
|
|
|
// GET lighthouse/merge_readiness
|
|
let get_lighthouse_merge_readiness = warp::path("lighthouse")
|
|
.and(warp::path("merge_readiness"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter.clone())
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.spawn_async_with_rejection(Priority::P1, async move {
|
|
let current_slot = chain.slot_clock.now_or_genesis().unwrap_or(Slot::new(0));
|
|
let merge_readiness = chain.check_bellatrix_readiness(current_slot).await;
|
|
Ok::<_, warp::reject::Rejection>(
|
|
warp::reply::json(&api_types::GenericResponse::from(merge_readiness))
|
|
.into_response(),
|
|
)
|
|
})
|
|
},
|
|
);
|
|
|
|
let get_events = eth_v1
|
|
.clone()
|
|
.and(warp::path("events"))
|
|
.and(warp::path::end())
|
|
.and(multi_key_query::<api_types::EventQuery>())
|
|
.and(task_spawner_filter.clone())
|
|
.and(chain_filter)
|
|
.then(
|
|
|topics_res: Result<api_types::EventQuery, warp::Rejection>,
|
|
task_spawner: TaskSpawner<T::EthSpec>,
|
|
chain: Arc<BeaconChain<T>>| {
|
|
task_spawner.blocking_response_task(Priority::P0, move || {
|
|
let topics = topics_res?;
|
|
// for each topic subscribed spawn a new subscription
|
|
let mut receivers = Vec::with_capacity(topics.topics.len());
|
|
|
|
if let Some(event_handler) = chain.event_handler.as_ref() {
|
|
for topic in topics.topics {
|
|
let receiver = match topic {
|
|
api_types::EventTopic::Head => event_handler.subscribe_head(),
|
|
api_types::EventTopic::Block => event_handler.subscribe_block(),
|
|
api_types::EventTopic::BlobSidecar => {
|
|
event_handler.subscribe_blob_sidecar()
|
|
}
|
|
api_types::EventTopic::DataColumnSidecar => {
|
|
event_handler.subscribe_data_column_sidecar()
|
|
}
|
|
api_types::EventTopic::Attestation => {
|
|
event_handler.subscribe_attestation()
|
|
}
|
|
api_types::EventTopic::SingleAttestation => {
|
|
event_handler.subscribe_single_attestation()
|
|
}
|
|
api_types::EventTopic::VoluntaryExit => {
|
|
event_handler.subscribe_exit()
|
|
}
|
|
api_types::EventTopic::FinalizedCheckpoint => {
|
|
event_handler.subscribe_finalized()
|
|
}
|
|
api_types::EventTopic::ChainReorg => {
|
|
event_handler.subscribe_reorgs()
|
|
}
|
|
api_types::EventTopic::ContributionAndProof => {
|
|
event_handler.subscribe_contributions()
|
|
}
|
|
api_types::EventTopic::PayloadAttributes => {
|
|
event_handler.subscribe_payload_attributes()
|
|
}
|
|
api_types::EventTopic::LateHead => {
|
|
event_handler.subscribe_late_head()
|
|
}
|
|
api_types::EventTopic::LightClientFinalityUpdate => {
|
|
event_handler.subscribe_light_client_finality_update()
|
|
}
|
|
api_types::EventTopic::LightClientOptimisticUpdate => {
|
|
event_handler.subscribe_light_client_optimistic_update()
|
|
}
|
|
api_types::EventTopic::BlockReward => {
|
|
event_handler.subscribe_block_reward()
|
|
}
|
|
api_types::EventTopic::AttesterSlashing => {
|
|
event_handler.subscribe_attester_slashing()
|
|
}
|
|
api_types::EventTopic::ProposerSlashing => {
|
|
event_handler.subscribe_proposer_slashing()
|
|
}
|
|
api_types::EventTopic::BlsToExecutionChange => {
|
|
event_handler.subscribe_bls_to_execution_change()
|
|
}
|
|
api_types::EventTopic::BlockGossip => {
|
|
event_handler.subscribe_block_gossip()
|
|
}
|
|
};
|
|
|
|
receivers.push(
|
|
BroadcastStream::new(receiver)
|
|
.map(|msg| {
|
|
match msg {
|
|
Ok(data) => Event::default()
|
|
.event(data.topic_name())
|
|
.json_data(data)
|
|
.unwrap_or_else(|e| {
|
|
Event::default()
|
|
.comment(format!("error - bad json: {e:?}"))
|
|
}),
|
|
// Do not terminate the stream if the channel fills
|
|
// up. Just drop some messages and send a comment to
|
|
// the client.
|
|
Err(BroadcastStreamRecvError::Lagged(n)) => {
|
|
Event::default().comment(format!(
|
|
"error - dropped {n} messages"
|
|
))
|
|
}
|
|
}
|
|
})
|
|
.map(Ok::<_, std::convert::Infallible>),
|
|
);
|
|
}
|
|
} else {
|
|
return Err(warp_utils::reject::custom_server_error(
|
|
"event handler was not initialized".to_string(),
|
|
));
|
|
}
|
|
|
|
let s = futures::stream::select_all(receivers);
|
|
|
|
Ok(warp::sse::reply(warp::sse::keep_alive().stream(s)))
|
|
})
|
|
},
|
|
);
|
|
|
|
// Subscribe to logs via Server Side Events
|
|
// /lighthouse/logs
|
|
let lighthouse_log_events = warp::path("lighthouse")
|
|
.and(warp::path("logs"))
|
|
.and(warp::path::end())
|
|
.and(task_spawner_filter)
|
|
.and(sse_component_filter)
|
|
.then(
|
|
|task_spawner: TaskSpawner<T::EthSpec>, sse_component: Option<SSELoggingComponents>| {
|
|
task_spawner.blocking_response_task(Priority::P1, 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 serde_json::to_string(&data)
|
|
.map_err(|e| format!("{:?}", e))
|
|
{
|
|
// Send the json as a Server Side Event
|
|
Ok(json) => Ok(Event::default().data(json)),
|
|
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(),
|
|
))
|
|
}
|
|
})
|
|
},
|
|
);
|
|
|
|
// Define the ultimate set of routes that will be provided to the server.
|
|
// Use `uor` rather than `or` in order to simplify types (see `UnifyingOrFilter`).
|
|
let routes = warp::get()
|
|
.and(
|
|
get_beacon_genesis
|
|
.uor(get_beacon_state_root)
|
|
.uor(get_beacon_state_fork)
|
|
.uor(get_beacon_state_finality_checkpoints)
|
|
.uor(get_beacon_state_validator_balances)
|
|
.uor(get_beacon_state_validators_id)
|
|
.uor(get_beacon_state_validators)
|
|
.uor(get_beacon_state_committees)
|
|
.uor(get_beacon_state_sync_committees)
|
|
.uor(get_beacon_state_randao)
|
|
.uor(get_beacon_state_pending_deposits)
|
|
.uor(get_beacon_state_pending_partial_withdrawals)
|
|
.uor(get_beacon_state_pending_consolidations)
|
|
.uor(get_beacon_headers)
|
|
.uor(get_beacon_headers_block_id)
|
|
.uor(get_beacon_block)
|
|
.uor(get_beacon_block_attestations)
|
|
.uor(get_beacon_blinded_block)
|
|
.uor(get_beacon_block_root)
|
|
.uor(get_blob_sidecars)
|
|
.uor(get_blobs)
|
|
.uor(get_beacon_pool_attestations)
|
|
.uor(get_beacon_pool_attester_slashings)
|
|
.uor(get_beacon_pool_proposer_slashings)
|
|
.uor(get_beacon_pool_voluntary_exits)
|
|
.uor(get_beacon_pool_bls_to_execution_changes)
|
|
.uor(get_beacon_rewards_blocks)
|
|
.uor(get_config_fork_schedule)
|
|
.uor(get_config_spec)
|
|
.uor(get_config_deposit_contract)
|
|
.uor(get_debug_beacon_states)
|
|
.uor(get_debug_data_column_sidecars)
|
|
.uor(get_debug_beacon_heads)
|
|
.uor(get_debug_fork_choice)
|
|
.uor(get_node_identity)
|
|
.uor(get_node_version)
|
|
.uor(get_node_syncing)
|
|
.uor(get_node_health)
|
|
.uor(get_node_peers_by_id)
|
|
.uor(get_node_peers)
|
|
.uor(get_node_peer_count)
|
|
.uor(get_validator_duties_proposer)
|
|
.uor(get_validator_blocks)
|
|
.uor(get_validator_blinded_blocks)
|
|
.uor(get_validator_attestation_data)
|
|
.uor(get_validator_aggregate_attestation)
|
|
.uor(get_validator_sync_committee_contribution)
|
|
.uor(get_lighthouse_health)
|
|
.uor(get_lighthouse_ui_health)
|
|
.uor(get_lighthouse_ui_validator_count)
|
|
.uor(get_lighthouse_syncing)
|
|
.uor(get_lighthouse_nat)
|
|
.uor(get_lighthouse_peers)
|
|
.uor(get_lighthouse_peers_connected)
|
|
.uor(get_lighthouse_proto_array)
|
|
.uor(get_lighthouse_validator_inclusion_global)
|
|
.uor(get_lighthouse_validator_inclusion)
|
|
.uor(get_lighthouse_staking)
|
|
.uor(get_lighthouse_database_info)
|
|
.uor(get_lighthouse_custody_info)
|
|
.uor(get_lighthouse_block_rewards)
|
|
.uor(get_lighthouse_attestation_performance)
|
|
.uor(get_beacon_light_client_optimistic_update)
|
|
.uor(get_beacon_light_client_finality_update)
|
|
.uor(get_beacon_light_client_bootstrap)
|
|
.uor(get_beacon_light_client_updates)
|
|
.uor(get_lighthouse_block_packing_efficiency)
|
|
.uor(get_lighthouse_merge_readiness)
|
|
.uor(get_events)
|
|
.uor(get_expected_withdrawals)
|
|
.uor(lighthouse_log_events.boxed())
|
|
.recover(warp_utils::reject::handle_rejection),
|
|
)
|
|
.boxed()
|
|
.uor(
|
|
warp::post().and(
|
|
warp::header::exact(CONTENT_TYPE_HEADER, SSZ_CONTENT_TYPE_HEADER)
|
|
// Routes which expect `application/octet-stream` go within this `and`.
|
|
.and(
|
|
post_beacon_blocks_ssz
|
|
.uor(post_beacon_blocks_v2_ssz)
|
|
.uor(post_beacon_blinded_blocks_ssz)
|
|
.uor(post_beacon_blinded_blocks_v2_ssz),
|
|
)
|
|
.uor(post_beacon_blocks)
|
|
.uor(post_beacon_blinded_blocks)
|
|
.uor(post_beacon_blocks_v2)
|
|
.uor(post_beacon_blinded_blocks_v2)
|
|
.uor(post_beacon_pool_attestations_v2)
|
|
.uor(post_beacon_pool_attester_slashings)
|
|
.uor(post_beacon_pool_proposer_slashings)
|
|
.uor(post_beacon_pool_voluntary_exits)
|
|
.uor(post_beacon_pool_sync_committees)
|
|
.uor(post_beacon_pool_bls_to_execution_changes)
|
|
.uor(post_beacon_state_validators)
|
|
.uor(post_beacon_state_validator_balances)
|
|
.uor(post_beacon_state_validator_identities)
|
|
.uor(post_beacon_rewards_attestations)
|
|
.uor(post_beacon_rewards_sync_committee)
|
|
.uor(post_validator_duties_attester)
|
|
.uor(post_validator_duties_sync)
|
|
.uor(post_validator_aggregate_and_proofs)
|
|
.uor(post_validator_contribution_and_proofs)
|
|
.uor(post_validator_beacon_committee_subscriptions)
|
|
.uor(post_validator_sync_committee_subscriptions)
|
|
.uor(post_validator_prepare_beacon_proposer)
|
|
.uor(post_validator_register_validator)
|
|
.uor(post_validator_liveness_epoch)
|
|
.uor(post_lighthouse_liveness)
|
|
.uor(post_lighthouse_database_reconstruct)
|
|
.uor(post_lighthouse_block_rewards)
|
|
.uor(post_lighthouse_ui_validator_metrics)
|
|
.uor(post_lighthouse_ui_validator_info)
|
|
.uor(post_lighthouse_finalize)
|
|
.uor(post_lighthouse_compaction)
|
|
.uor(post_lighthouse_add_peer)
|
|
.uor(post_lighthouse_remove_peer)
|
|
.uor(post_lighthouse_custody_backfill)
|
|
.recover(warp_utils::reject::handle_rejection),
|
|
),
|
|
)
|
|
.recover(warp_utils::reject::handle_rejection)
|
|
.with(tracing_logging())
|
|
.with(prometheus_metrics())
|
|
// Add a `Server` header.
|
|
.map(|reply| warp::reply::with_header(reply, "Server", &version_with_platform()))
|
|
.with(cors_builder.build())
|
|
.boxed();
|
|
|
|
let http_socket: SocketAddr = SocketAddr::new(config.listen_addr, config.listen_port);
|
|
let http_server: HttpServer = match config.tls_config {
|
|
Some(tls_config) => {
|
|
let (socket, server) = warp::serve(routes)
|
|
.tls()
|
|
.cert_path(tls_config.cert)
|
|
.key_path(tls_config.key)
|
|
.try_bind_with_graceful_shutdown(http_socket, async {
|
|
shutdown.await;
|
|
})?;
|
|
|
|
info!("HTTP API is being served over TLS");
|
|
|
|
(socket, Box::pin(server))
|
|
}
|
|
None => {
|
|
let (socket, server) =
|
|
warp::serve(routes).try_bind_with_graceful_shutdown(http_socket, async {
|
|
shutdown.await;
|
|
})?;
|
|
(socket, Box::pin(server))
|
|
}
|
|
};
|
|
|
|
info!(
|
|
listen_address = %http_server.0,
|
|
"HTTP API started"
|
|
);
|
|
|
|
Ok(http_server)
|
|
}
|