//! This crate provides two major things: //! //! 1. The types served by the `http_api` crate. //! 2. A wrapper around `reqwest` that forms a HTTP client, able of consuming the endpoints served //! by the `http_api` crate. //! //! Eventually it would be ideal to publish this crate on crates.io, however we have some local //! dependencies preventing this presently. pub mod beacon_response; pub mod error; #[cfg(feature = "lighthouse")] pub mod lighthouse; #[cfg(feature = "lighthouse")] pub mod lighthouse_vc; pub mod mixin; pub mod types; pub use beacon_response::{ BeaconResponse, EmptyMetadata, ExecutionOptimisticFinalizedBeaconResponse, ExecutionOptimisticFinalizedMetadata, ForkVersionedResponse, UnversionedResponse, }; pub use self::error::{Error, ok_or_error, success_or_error}; pub use reqwest; pub use reqwest::{StatusCode, Url}; pub use sensitive_url::SensitiveUrl; use self::mixin::{RequestAccept, ResponseOptional}; use self::types::*; use bls::SignatureBytes; use context_deserialize::ContextDeserialize; use educe::Educe; #[cfg(feature = "events")] use futures::Stream; #[cfg(feature = "events")] use futures_util::StreamExt; use reqwest::{ Body, IntoUrl, RequestBuilder, Response, header::{HeaderMap, HeaderValue}, }; #[cfg(feature = "events")] use reqwest_eventsource::{Event, RequestBuilderExt}; use serde::{Serialize, de::DeserializeOwned}; use ssz::Encode; use std::fmt; use std::future::Future; use std::time::Duration; pub const V1: EndpointVersion = EndpointVersion(1); pub const V2: EndpointVersion = EndpointVersion(2); pub const V3: EndpointVersion = EndpointVersion(3); pub const CONSENSUS_VERSION_HEADER: &str = "Eth-Consensus-Version"; pub const EXECUTION_PAYLOAD_BLINDED_HEADER: &str = "Eth-Execution-Payload-Blinded"; pub const EXECUTION_PAYLOAD_VALUE_HEADER: &str = "Eth-Execution-Payload-Value"; pub const CONSENSUS_BLOCK_VALUE_HEADER: &str = "Eth-Consensus-Block-Value"; pub const CONTENT_TYPE_HEADER: &str = "Content-Type"; pub const SSZ_CONTENT_TYPE_HEADER: &str = "application/octet-stream"; pub const JSON_CONTENT_TYPE_HEADER: &str = "application/json"; /// Specific optimized timeout constants for HTTP requests involved in different validator duties. /// This can help ensure that proper endpoint fallback occurs. const HTTP_ATTESTATION_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_ATTESTATION_SUBSCRIPTIONS_TIMEOUT_QUOTIENT: u32 = 24; const HTTP_ATTESTATION_AGGREGATOR_TIMEOUT_QUOTIENT: u32 = 24; // For DVT involving middleware only const HTTP_LIVENESS_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_PROPOSAL_TIMEOUT_QUOTIENT: u32 = 2; const HTTP_PROPOSER_DUTIES_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_SYNC_AGGREGATOR_TIMEOUT_QUOTIENT: u32 = 24; // For DVT involving middleware only const HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT: u32 = 4; const HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT: u32 = 4; const HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT: u32 = 4; // Generally the timeout for events should be longer than a slot. const HTTP_GET_EVENTS_TIMEOUT_MULTIPLIER: u32 = 50; const HTTP_DEFAULT_TIMEOUT_QUOTIENT: u32 = 4; /// A struct to define a variety of different timeouts for different validator tasks to ensure /// proper fallback behaviour. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Timeouts { pub attestation: Duration, pub attester_duties: Duration, pub attestation_subscriptions: Duration, pub attestation_aggregators: Duration, pub liveness: Duration, pub proposal: Duration, pub proposer_duties: Duration, pub sync_committee_contribution: Duration, pub sync_duties: Duration, pub sync_aggregators: Duration, pub get_beacon_blocks_ssz: Duration, pub get_debug_beacon_states: Duration, pub get_deposit_snapshot: Duration, pub get_validator_block: Duration, pub events: Duration, pub default: Duration, } impl Timeouts { pub fn set_all(timeout: Duration) -> Self { Timeouts { attestation: timeout, attester_duties: timeout, attestation_subscriptions: timeout, attestation_aggregators: timeout, liveness: timeout, proposal: timeout, proposer_duties: timeout, sync_committee_contribution: timeout, sync_duties: timeout, sync_aggregators: timeout, get_beacon_blocks_ssz: timeout, get_debug_beacon_states: timeout, get_deposit_snapshot: timeout, get_validator_block: timeout, events: HTTP_GET_EVENTS_TIMEOUT_MULTIPLIER * timeout, default: timeout, } } pub fn use_optimized_timeouts(base_timeout: Duration) -> Self { Timeouts { attestation: base_timeout / HTTP_ATTESTATION_TIMEOUT_QUOTIENT, attester_duties: base_timeout / HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT, attestation_subscriptions: base_timeout / HTTP_ATTESTATION_SUBSCRIPTIONS_TIMEOUT_QUOTIENT, attestation_aggregators: base_timeout / HTTP_ATTESTATION_AGGREGATOR_TIMEOUT_QUOTIENT, liveness: base_timeout / HTTP_LIVENESS_TIMEOUT_QUOTIENT, proposal: base_timeout / HTTP_PROPOSAL_TIMEOUT_QUOTIENT, proposer_duties: base_timeout / HTTP_PROPOSER_DUTIES_TIMEOUT_QUOTIENT, sync_committee_contribution: base_timeout / HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT, sync_duties: base_timeout / HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT, sync_aggregators: base_timeout / HTTP_SYNC_AGGREGATOR_TIMEOUT_QUOTIENT, get_beacon_blocks_ssz: base_timeout / HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT, get_debug_beacon_states: base_timeout / HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT, get_deposit_snapshot: base_timeout / HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT, get_validator_block: base_timeout / HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT, events: HTTP_GET_EVENTS_TIMEOUT_MULTIPLIER * base_timeout, default: base_timeout / HTTP_DEFAULT_TIMEOUT_QUOTIENT, } } } /// A wrapper around `reqwest::Client` which provides convenience methods for interfacing with a /// Lighthouse Beacon Node HTTP server (`http_api`). #[derive(Clone, Debug, Educe)] #[educe(PartialEq)] pub struct BeaconNodeHttpClient { #[educe(PartialEq(ignore))] client: reqwest::Client, server: SensitiveUrl, timeouts: Timeouts, } impl Eq for BeaconNodeHttpClient {} impl fmt::Display for BeaconNodeHttpClient { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.server.fmt(f) } } impl BeaconNodeHttpClient { pub fn new(server: SensitiveUrl, timeouts: Timeouts) -> Self { Self { client: reqwest::Client::new(), server, timeouts, } } pub fn from_components( server: SensitiveUrl, client: reqwest::Client, timeouts: Timeouts, ) -> Self { Self { client, server, timeouts, } } // Returns a reference to the `SensitiveUrl` of the server. pub fn server(&self) -> &SensitiveUrl { &self.server } /// Return the path with the standard `/eth/vX` prefix applied. fn eth_path(&self, version: EndpointVersion) -> Result { let mut path = self.server.expose_full().clone(); path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("eth") .push(&version.to_string()); Ok(path) } /// Perform a HTTP GET request. async fn get(&self, url: U) -> Result { let response = self.get_response(url, |b| b).await?; Ok(response.json().await?) } /// Perform an HTTP GET request, returning the `Response` for processing. pub async fn get_response( &self, url: U, builder: impl FnOnce(RequestBuilder) -> RequestBuilder, ) -> Result { let response = builder(self.client.get(url).timeout(self.timeouts.default)) .send() .await?; ok_or_error(response).await } /// Perform a HTTP GET request with a custom timeout. async fn get_with_timeout( &self, url: U, timeout: Duration, ) -> Result { let response = self .get_response(url, |builder| builder.timeout(timeout)) .await?; Ok(response.json().await?) } /// Perform a HTTP GET request, returning `None` on a 404 error. async fn get_opt(&self, url: U) -> Result, Error> { match self .get_response(url, |b| b.accept(Accept::Json)) .await .optional()? { Some(response) => Ok(Some(response.json().await?)), None => Ok(None), } } /// Perform a HTTP GET request with a custom timeout, returning `None` on a 404 error. async fn get_opt_with_timeout( &self, url: U, timeout: Duration, ) -> Result, Error> { let opt_response = self .get_response(url, |b| b.timeout(timeout).accept(Accept::Json)) .await .optional()?; match opt_response { Some(response) => Ok(Some(response.json().await?)), None => Ok(None), } } pub async fn get_fork_contextual( &self, url: U, ctx_constructor: impl Fn(ForkName) -> Ctx, ) -> Result>, Error> where U: IntoUrl, T: ContextDeserialize<'static, Ctx>, Meta: DeserializeOwned, Ctx: Clone, { let response = self .get_response(url, |b| b.accept(Accept::Json)) .await .optional()?; let Some(resp) = response else { return Ok(None); }; let bytes = resp.bytes().await?; #[derive(serde::Deserialize)] struct Helper { // TODO: remove this default once checkpointz follows the spec #[serde(default = "ForkName::latest_stable")] version: ForkName, #[serde(flatten)] metadata: serde_json::Value, data: serde_json::Value, } let helper: Helper = serde_json::from_slice(&bytes).map_err(Error::InvalidJson)?; let metadata: Meta = serde_json::from_value(helper.metadata).map_err(Error::InvalidJson)?; let ctx = ctx_constructor(helper.version); let data: T = ContextDeserialize::context_deserialize(helper.data, ctx) .map_err(Error::InvalidJson)?; Ok(Some(ForkVersionedResponse { version: helper.version, metadata, data, })) } /// Perform a HTTP GET request using an 'accept' header, returning `None` on a 404 error. pub async fn get_bytes_opt_accept_header( &self, url: U, accept_header: Accept, timeout: Duration, ) -> Result>, Error> { let opt_response = self .get_response(url, |b| b.accept(accept_header).timeout(timeout)) .await .optional()?; match opt_response { Some(resp) => Ok(Some(resp.bytes().await?.into_iter().collect::>())), None => Ok(None), } } /// Perform a HTTP GET request using an 'accept' header, returning `None` on a 404 error. pub async fn get_response_with_response_headers( &self, url: U, accept_header: Accept, timeout: Duration, parser: impl FnOnce(Response, HeaderMap) -> F, ) -> Result, Error> where F: Future>, { let opt_response = self .get_response(url, |b| b.accept(accept_header).timeout(timeout)) .await .optional()?; match opt_response { Some(resp) => { let response_headers = resp.headers().clone(); let parsed_response = parser(resp, response_headers).await?; Ok(Some(parsed_response)) } None => Ok(None), } } /// Perform a HTTP POST request. async fn post(&self, url: U, body: &T) -> Result<(), Error> { self.post_generic(url, body, None).await?; Ok(()) } /// Perform a HTTP POST request, returning a JSON response. async fn post_with_response( &self, url: U, body: &T, ) -> Result { self.post_generic(url, body, None) .await? .json() .await .map_err(Into::into) } async fn post_with_opt_response( &self, url: U, body: &T, ) -> Result, Error> { if let Some(response) = self.post_generic(url, body, None).await.optional()? { response.json().await.map_err(Into::into) } else { Ok(None) } } /// Perform a HTTP POST request with a custom timeout. async fn post_with_timeout( &self, url: U, body: &T, timeout: Duration, ) -> Result<(), Error> { self.post_generic(url, body, Some(timeout)).await?; Ok(()) } /// Perform a HTTP POST request with a custom timeout and consensus header. async fn post_with_timeout_and_consensus_header( &self, url: U, body: &T, timeout: Duration, fork_name: ForkName, ) -> Result<(), Error> { self.post_generic_with_consensus_version(url, body, Some(timeout), fork_name) .await?; Ok(()) } /// Perform a HTTP POST request with a custom timeout, returning a JSON response. async fn post_with_timeout_and_response( &self, url: U, body: &V, timeout: Duration, ) -> Result { self.post_generic(url, body, Some(timeout)) .await? .json() .await .map_err(Error::from) } /// Generic POST function supporting arbitrary responses and timeouts. async fn post_generic( &self, url: U, body: &T, timeout: Option, ) -> Result { let builder = self .client .post(url) .timeout(timeout.unwrap_or(self.timeouts.default)); let response = builder.json(body).send().await?; success_or_error(response).await } /// Generic POST function supporting arbitrary responses and timeouts. async fn post_generic_with_consensus_version( &self, url: U, body: &T, timeout: Option, fork: ForkName, ) -> Result { let builder = self .client .post(url) .timeout(timeout.unwrap_or(self.timeouts.default)); let response = builder .header(CONSENSUS_VERSION_HEADER, fork.to_string()) .json(body) .send() .await?; success_or_error(response).await } /// Generic POST function that includes octet-stream content type header. async fn post_generic_with_ssz_header( &self, url: U, body: &T, ) -> Result { let builder = self.client.post(url).timeout(self.timeouts.default); let mut headers = HeaderMap::new(); headers.insert( "Content-Type", HeaderValue::from_static("application/octet-stream"), ); let response = builder.headers(headers).json(body).send().await?; success_or_error(response).await } /// Generic POST function supporting arbitrary responses and timeouts. async fn post_generic_with_consensus_version_and_ssz_body, U: IntoUrl>( &self, url: U, body: T, timeout: Option, fork: ForkName, ) -> Result { let builder = self .client .post(url) .timeout(timeout.unwrap_or(self.timeouts.default)); let mut headers = HeaderMap::new(); headers.insert( CONSENSUS_VERSION_HEADER, HeaderValue::from_str(&fork.to_string()).expect("Failed to create header value"), ); headers.insert( "Content-Type", HeaderValue::from_static("application/octet-stream"), ); let response = builder.headers(headers).body(body).send().await?; success_or_error(response).await } /// `GET beacon/genesis` /// /// ## Errors /// /// May return a `404` if beacon chain genesis has not yet occurred. pub async fn get_beacon_genesis(&self) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("genesis"); self.get(path).await } /// `GET beacon/states/{state_id}/root` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_states_root( &self, state_id: StateId, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("root"); self.get_opt(path).await } /// `GET beacon/states/{state_id}/fork` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_states_fork( &self, state_id: StateId, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("fork"); self.get_opt(path).await } /// `GET beacon/states/{state_id}/finality_checkpoints` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_states_finality_checkpoints( &self, state_id: StateId, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("finality_checkpoints"); self.get_opt(path).await } /// `GET beacon/states/{state_id}/validator_balances?id` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_states_validator_balances( &self, state_id: StateId, ids: Option<&[ValidatorId]>, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("validator_balances"); if let Some(ids) = ids { let id_string = ids .iter() .map(|i| i.to_string()) .collect::>() .join(","); path.query_pairs_mut().append_pair("id", &id_string); } self.get_opt(path).await } /// TESTING ONLY: This request should fail with a 415 response code. pub async fn post_beacon_states_validator_balances_with_ssz_header( &self, state_id: StateId, ids: Vec, ) -> Result { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("validator_balances"); let request = ValidatorBalancesRequestBody { ids }; self.post_generic_with_ssz_header(path, &request).await } /// `POST beacon/states/{state_id}/validator_balances` /// /// Returns `Ok(None)` on a 404 error. pub async fn post_beacon_states_validator_balances( &self, state_id: StateId, ids: Vec, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("validator_balances"); let request = ValidatorBalancesRequestBody { ids }; self.post_with_opt_response(path, &request).await } /// `POST beacon/states/{state_id}/validator_identities` /// /// Returns `Ok(None)` on a 404 error. pub async fn post_beacon_states_validator_identities( &self, state_id: StateId, ids: Vec, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("validator_identities"); let request = ValidatorIdentitiesRequestBody { ids }; self.post_with_opt_response(path, &request).await } /// `GET beacon/states/{state_id}/validators?id,status` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_states_validators( &self, state_id: StateId, ids: Option<&[ValidatorId]>, statuses: Option<&[ValidatorStatus]>, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("validators"); if let Some(ids) = ids { let id_string = ids .iter() .map(|i| i.to_string()) .collect::>() .join(","); path.query_pairs_mut().append_pair("id", &id_string); } if let Some(statuses) = statuses { let status_string = statuses .iter() .map(|i| i.to_string()) .collect::>() .join(","); path.query_pairs_mut().append_pair("status", &status_string); } self.get_opt(path).await } /// `POST beacon/states/{state_id}/validators` /// /// Returns `Ok(None)` on a 404 error. pub async fn post_beacon_states_validators( &self, state_id: StateId, ids: Option>, statuses: Option>, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("validators"); let request = ValidatorsRequestBody { ids, statuses }; self.post_with_opt_response(path, &request).await } /// `GET beacon/states/{state_id}/committees?slot,index,epoch` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_states_committees( &self, state_id: StateId, slot: Option, index: Option, epoch: Option, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("committees"); if let Some(slot) = slot { path.query_pairs_mut() .append_pair("slot", &slot.to_string()); } if let Some(index) = index { path.query_pairs_mut() .append_pair("index", &index.to_string()); } if let Some(epoch) = epoch { path.query_pairs_mut() .append_pair("epoch", &epoch.to_string()); } self.get_opt(path).await } /// `GET beacon/states/{state_id}/sync_committees?epoch` pub async fn get_beacon_states_sync_committees( &self, state_id: StateId, epoch: Option, ) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("sync_committees"); if let Some(epoch) = epoch { path.query_pairs_mut() .append_pair("epoch", &epoch.to_string()); } self.get(path).await } /// `GET beacon/states/{state_id}/randao?epoch` pub async fn get_beacon_states_randao( &self, state_id: StateId, epoch: Option, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("randao"); if let Some(epoch) = epoch { path.query_pairs_mut() .append_pair("epoch", &epoch.to_string()); } self.get_opt(path).await } /// `GET beacon/states/{state_id}/validators/{validator_id}` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_states_validator_id( &self, state_id: StateId, validator_id: &ValidatorId, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("validators") .push(&validator_id.to_string()); self.get_opt(path).await } /// `GET beacon/states/{state_id}/pending_deposits` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_states_pending_deposits( &self, state_id: StateId, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("pending_deposits"); self.get_fork_contextual(path, |fork| fork) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `GET beacon/states/{state_id}/pending_partial_withdrawals` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_states_pending_partial_withdrawals( &self, state_id: StateId, ) -> Result< Option>>, Error, > { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("pending_partial_withdrawals"); self.get_fork_contextual(path, |fork| fork) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `GET beacon/states/{state_id}/pending_consolidations` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_states_pending_consolidations( &self, state_id: StateId, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("states") .push(&state_id.to_string()) .push("pending_consolidations"); self.get_fork_contextual(path, |fork| fork) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `GET beacon/light_client/updates` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_light_client_updates( &self, start_period: u64, count: u64, ) -> Result>>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("light_client") .push("updates"); path.query_pairs_mut() .append_pair("start_period", &start_period.to_string()); path.query_pairs_mut() .append_pair("count", &count.to_string()); self.get_opt(path).await.map(|opt| { opt.map(|updates: Vec<_>| { updates .into_iter() .map(BeaconResponse::ForkVersioned) .collect() }) }) } /// `GET beacon/light_client/updates` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_light_client_updates_ssz( &self, start_period: u64, count: u64, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("light_client") .push("updates"); path.query_pairs_mut() .append_pair("start_period", &start_period.to_string()); path.query_pairs_mut() .append_pair("count", &count.to_string()); self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.default) .await } /// `GET beacon/light_client/bootstrap` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_light_client_bootstrap( &self, block_root: Hash256, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("light_client") .push("bootstrap") .push(&format!("{:?}", block_root)); self.get_opt(path) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `GET beacon/light_client/optimistic_update` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_light_client_optimistic_update( &self, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("light_client") .push("optimistic_update"); self.get_opt(path) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `GET beacon/light_client/finality_update` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_light_client_finality_update( &self, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("light_client") .push("finality_update"); self.get_opt(path) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `GET beacon/headers?slot,parent_root` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_headers( &self, slot: Option, parent_root: Option, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("headers"); if let Some(slot) = slot { path.query_pairs_mut() .append_pair("slot", &slot.to_string()); } if let Some(root) = parent_root { path.query_pairs_mut() .append_pair("parent_root", &format!("{:?}", root)); } self.get_opt(path).await } /// `GET beacon/headers/{block_id}` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_headers_block_id( &self, block_id: BlockId, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("headers") .push(&block_id.to_string()); self.get_opt(path).await } /// `POST beacon/blocks` /// /// Returns `Ok(None)` on a 404 error. pub async fn post_beacon_blocks( &self, block_contents: &PublishBlockRequest, ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blocks"); let fork_name = block_contents.signed_block().fork_name_unchecked(); self.post_generic_with_consensus_version( path, block_contents, Some(self.timeouts.proposal), fork_name, ) .await?; Ok(()) } /// `POST beacon/blocks` /// /// Returns `Ok(None)` on a 404 error. pub async fn post_beacon_blocks_ssz( &self, block_contents: &PublishBlockRequest, ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blocks"); self.post_generic_with_consensus_version_and_ssz_body( path, block_contents.as_ssz_bytes(), Some(self.timeouts.proposal), block_contents.signed_block().fork_name_unchecked(), ) .await?; Ok(()) } /// `POST beacon/blinded_blocks` /// /// Returns `Ok(None)` on a 404 error. pub async fn post_beacon_blinded_blocks( &self, block: &SignedBlindedBeaconBlock, ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blinded_blocks"); self.post_with_timeout(path, block, self.timeouts.proposal) .await?; Ok(()) } /// `POST beacon/blinded_blocks` /// /// Returns `Ok(None)` on a 404 error. pub async fn post_beacon_blinded_blocks_ssz( &self, block: &SignedBlindedBeaconBlock, ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blinded_blocks"); self.post_generic_with_consensus_version_and_ssz_body( path, block.as_ssz_bytes(), Some(self.timeouts.proposal), block.fork_name_unchecked(), ) .await?; Ok(()) } pub fn post_beacon_blocks_v2_path( &self, validation_level: Option, ) -> Result { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|_| Error::InvalidUrl(self.server.clone()))? .extend(&["beacon", "blocks"]); path.set_query( validation_level .map(|v| format!("broadcast_validation={}", v)) .as_deref(), ); Ok(path) } pub fn post_beacon_blinded_blocks_v2_path( &self, validation_level: Option, ) -> Result { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|_| Error::InvalidUrl(self.server.clone()))? .extend(&["beacon", "blinded_blocks"]); path.set_query( validation_level .map(|v| format!("broadcast_validation={}", v)) .as_deref(), ); Ok(path) } /// `POST v2/beacon/blocks` pub async fn post_beacon_blocks_v2( &self, block_contents: &PublishBlockRequest, validation_level: Option, ) -> Result { let response = self .post_generic_with_consensus_version( self.post_beacon_blocks_v2_path(validation_level)?, block_contents, Some(self.timeouts.proposal), block_contents.signed_block().message().body().fork_name(), ) .await?; Ok(response) } /// `POST v2/beacon/blocks` pub async fn post_beacon_blocks_v2_ssz( &self, block_contents: &PublishBlockRequest, validation_level: Option, ) -> Result { let response = self .post_generic_with_consensus_version_and_ssz_body( self.post_beacon_blocks_v2_path(validation_level)?, block_contents.as_ssz_bytes(), Some(self.timeouts.proposal), block_contents.signed_block().message().body().fork_name(), ) .await?; Ok(response) } /// `POST v2/beacon/blinded_blocks` pub async fn post_beacon_blinded_blocks_v2( &self, signed_block: &SignedBlindedBeaconBlock, validation_level: Option, ) -> Result { let response = self .post_generic_with_consensus_version( self.post_beacon_blinded_blocks_v2_path(validation_level)?, signed_block, Some(self.timeouts.proposal), signed_block.message().body().fork_name(), ) .await?; Ok(response) } /// `POST v2/beacon/blinded_blocks` pub async fn post_beacon_blinded_blocks_v2_ssz( &self, signed_block: &SignedBlindedBeaconBlock, validation_level: Option, ) -> Result { let response = self .post_generic_with_consensus_version_and_ssz_body( self.post_beacon_blinded_blocks_v2_path(validation_level)?, signed_block.as_ssz_bytes(), Some(self.timeouts.proposal), signed_block.message().body().fork_name(), ) .await?; Ok(response) } /// Path for `v2/beacon/blocks` pub fn get_beacon_blocks_path(&self, block_id: BlockId) -> Result { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blocks") .push(&block_id.to_string()); Ok(path) } /// Path for `v1/beacon/blob_sidecars/{block_id}` pub fn get_blob_sidecars_path(&self, block_id: BlockId) -> Result { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blob_sidecars") .push(&block_id.to_string()); Ok(path) } /// Path for `v1/beacon/blobs/{blob_id}` pub fn get_blobs_path(&self, block_id: BlockId) -> Result { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blobs") .push(&block_id.to_string()); Ok(path) } /// Path for `v1/beacon/blinded_blocks/{block_id}` pub fn get_beacon_blinded_blocks_path(&self, block_id: BlockId) -> Result { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blinded_blocks") .push(&block_id.to_string()); Ok(path) } /// `GET v2/beacon/blocks` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_blocks( &self, block_id: BlockId, ) -> Result>>, Error> { let path = self.get_beacon_blocks_path(block_id)?; self.get_opt(path) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `GET v1/beacon/blob_sidecars/{block_id}` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_blob_sidecars( &self, block_id: BlockId, indices: Option<&[u64]>, spec: &ChainSpec, ) -> Result>>, Error> { let mut path = self.get_blob_sidecars_path(block_id)?; if let Some(indices) = indices { let indices_string = indices .iter() .map(|i| i.to_string()) .collect::>() .join(","); path.query_pairs_mut() .append_pair("indices", &indices_string); } self.get_fork_contextual(path, |fork| { // TODO(EIP-7892): this will overestimate the max number of blobs // It would be better if we could get an epoch passed into this function (fork, spec.max_blobs_per_block_within_fork(fork) as usize) }) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `GET v1/beacon/blobs/{block_id}` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_blobs( &self, block_id: BlockId, versioned_hashes: Option<&[Hash256]>, ) -> Result>>>, Error> { let mut path = self.get_blobs_path(block_id)?; if let Some(hashes) = versioned_hashes { let hashes_string = hashes .iter() .map(|hash| hash.to_string()) .collect::>() .join(","); path.query_pairs_mut() .append_pair("versioned_hashes", &hashes_string); } self.get_opt(path) .await .map(|opt| opt.map(BeaconResponse::Unversioned)) } /// `GET v1/beacon/blinded_blocks/{block_id}` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_blinded_blocks( &self, block_id: BlockId, ) -> Result< Option>>, Error, > { let path = self.get_beacon_blinded_blocks_path(block_id)?; self.get_opt(path) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `GET v1/beacon/blocks` (LEGACY) /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_blocks_v1( &self, block_id: BlockId, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blocks") .push(&block_id.to_string()); self.get_opt(path) .await .map(|opt| opt.map(BeaconResponse::Unversioned)) } /// `GET beacon/blocks` as SSZ /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_blocks_ssz( &self, block_id: BlockId, spec: &ChainSpec, ) -> Result>, Error> { let path = self.get_beacon_blocks_path(block_id)?; self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_beacon_blocks_ssz) .await? .map(|bytes| SignedBeaconBlock::from_ssz_bytes(&bytes, spec).map_err(Error::InvalidSsz)) .transpose() } /// `GET beacon/blinded_blocks/{block_id}` as SSZ /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_blinded_blocks_ssz( &self, block_id: BlockId, spec: &ChainSpec, ) -> Result>, Error> { let path = self.get_beacon_blinded_blocks_path(block_id)?; self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_beacon_blocks_ssz) .await? .map(|bytes| { SignedBlindedBeaconBlock::from_ssz_bytes(&bytes, spec).map_err(Error::InvalidSsz) }) .transpose() } /// `GET beacon/blocks/{block_id}/root` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_blocks_root( &self, block_id: BlockId, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blocks") .push(&block_id.to_string()) .push("root"); self.get_opt(path).await } /// `GET v1/beacon/blocks/{block_id}/attestations` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_blocks_attestations_v1( &self, block_id: BlockId, ) -> Result>>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blocks") .push(&block_id.to_string()) .push("attestations"); self.get_opt(path).await } /// `GET v2/beacon/blocks/{block_id}/attestations` /// /// Returns `Ok(None)` on a 404 error. pub async fn get_beacon_blocks_attestations_v2( &self, block_id: BlockId, ) -> Result>>>, Error> { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("blocks") .push(&block_id.to_string()) .push("attestations"); self.get_opt(path) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `POST v2/beacon/pool/attestations` pub async fn post_beacon_pool_attestations_v2( &self, attestations: Vec, fork_name: ForkName, ) -> Result<(), Error> { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("attestations"); self.post_with_timeout_and_consensus_header( path, &attestations, self.timeouts.attestation, fork_name, ) .await?; Ok(()) } /// `GET v1/beacon/pool/attestations?slot,committee_index` pub async fn get_beacon_pool_attestations_v1( &self, slot: Option, committee_index: Option, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("attestations"); if let Some(slot) = slot { path.query_pairs_mut() .append_pair("slot", &slot.to_string()); } if let Some(index) = committee_index { path.query_pairs_mut() .append_pair("committee_index", &index.to_string()); } self.get(path).await } /// `GET v2/beacon/pool/attestations?slot,committee_index` pub async fn get_beacon_pool_attestations_v2( &self, slot: Option, committee_index: Option, ) -> Result>>, Error> { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("attestations"); if let Some(slot) = slot { path.query_pairs_mut() .append_pair("slot", &slot.to_string()); } if let Some(index) = committee_index { path.query_pairs_mut() .append_pair("committee_index", &index.to_string()); } self.get(path).await.map(BeaconResponse::ForkVersioned) } /// `POST v1/beacon/pool/attester_slashings` pub async fn post_beacon_pool_attester_slashings_v1( &self, slashing: &AttesterSlashing, ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("attester_slashings"); self.post_generic(path, slashing, None).await?; Ok(()) } /// `POST v2/beacon/pool/attester_slashings` pub async fn post_beacon_pool_attester_slashings_v2( &self, slashing: &AttesterSlashing, fork_name: ForkName, ) -> Result<(), Error> { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("attester_slashings"); self.post_generic_with_consensus_version(path, slashing, None, fork_name) .await?; Ok(()) } /// `GET v1/beacon/pool/attester_slashings` pub async fn get_beacon_pool_attester_slashings_v1( &self, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("attester_slashings"); self.get(path).await } /// `GET v2/beacon/pool/attester_slashings` pub async fn get_beacon_pool_attester_slashings_v2( &self, ) -> Result>>, Error> { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("attester_slashings"); self.get(path).await.map(BeaconResponse::ForkVersioned) } /// `POST beacon/pool/proposer_slashings` pub async fn post_beacon_pool_proposer_slashings( &self, slashing: &ProposerSlashing, ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("proposer_slashings"); self.post(path, slashing).await?; Ok(()) } /// `GET beacon/pool/proposer_slashings` pub async fn get_beacon_pool_proposer_slashings( &self, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("proposer_slashings"); self.get(path).await } /// `POST beacon/pool/voluntary_exits` pub async fn post_beacon_pool_voluntary_exits( &self, exit: &SignedVoluntaryExit, ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("voluntary_exits"); self.post(path, exit).await?; Ok(()) } /// `GET beacon/pool/voluntary_exits` pub async fn get_beacon_pool_voluntary_exits( &self, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("voluntary_exits"); self.get(path).await } /// `POST beacon/pool/sync_committees` pub async fn post_beacon_pool_sync_committee_signatures( &self, signatures: &[SyncCommitteeMessage], ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("sync_committees"); self.post(path, &signatures).await?; Ok(()) } /// `POST beacon/pool/bls_to_execution_changes` pub async fn post_beacon_pool_bls_to_execution_changes( &self, address_changes: &[SignedBlsToExecutionChange], ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("pool") .push("bls_to_execution_changes"); self.post(path, &address_changes).await?; Ok(()) } /// `POST beacon/rewards/sync_committee` pub async fn post_beacon_rewards_sync_committee( &self, block_id: BlockId, validators: &[ValidatorId], ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("rewards") .push("sync_committee") .push(&block_id.to_string()); self.post_with_response(path, &validators).await } /// `GET beacon/rewards/blocks` pub async fn get_beacon_rewards_blocks( &self, block_id: BlockId, ) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("rewards") .push("blocks") .push(&block_id.to_string()); self.get(path).await } /// `POST beacon/rewards/attestations` pub async fn post_beacon_rewards_attestations( &self, epoch: Epoch, validators: &[ValidatorId], ) -> Result { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("beacon") .push("rewards") .push("attestations") .push(&epoch.to_string()); self.post_with_response(path, &validators).await } // GET builder/states/{state_id}/expected_withdrawals pub async fn get_expected_withdrawals( &self, state_id: &StateId, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("builder") .push("states") .push(&state_id.to_string()) .push("expected_withdrawals"); self.get(path).await } /// `POST validator/contribution_and_proofs` pub async fn post_validator_contribution_and_proofs( &self, signed_contributions: &[SignedContributionAndProof], ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("contribution_and_proofs"); self.post_with_timeout( path, &signed_contributions, self.timeouts.sync_committee_contribution, ) .await?; Ok(()) } /// `POST validator/prepare_beacon_proposer` pub async fn post_validator_prepare_beacon_proposer( &self, preparation_data: &[ProposerPreparationData], ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("prepare_beacon_proposer"); self.post(path, &preparation_data).await?; Ok(()) } /// `POST validator/register_validator` pub async fn post_validator_register_validator( &self, registration_data: &[SignedValidatorRegistrationData], ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("register_validator"); self.post(path, ®istration_data).await?; Ok(()) } /// `GET config/fork_schedule` pub async fn get_config_fork_schedule(&self) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("config") .push("fork_schedule"); self.get(path).await } /// `GET config/spec` pub async fn get_config_spec( &self, ) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("config") .push("spec"); self.get(path).await } /// `GET config/deposit_contract` pub async fn get_config_deposit_contract( &self, ) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("config") .push("deposit_contract"); self.get(path).await } /// `GET node/version` pub async fn get_node_version(&self) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("node") .push("version"); self.get(path).await } /// `GET node/identity` pub async fn get_node_identity(&self) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("node") .push("identity"); self.get(path).await } /// `GET node/syncing` pub async fn get_node_syncing(&self) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("node") .push("syncing"); self.get(path).await } /// `GET node/health` pub async fn get_node_health(&self) -> Result { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("node") .push("health"); let status = self .client .get(path) .timeout(self.timeouts.default) .send() .await? .status(); if status == StatusCode::OK || status == StatusCode::PARTIAL_CONTENT { Ok(status) } else { Err(Error::StatusCode(status)) } } /// `GET node/peers/{peer_id}` pub async fn get_node_peers_by_id( &self, peer_id: &str, ) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("node") .push("peers") .push(peer_id); self.get(path).await } /// `GET node/peers` pub async fn get_node_peers( &self, states: Option<&[PeerState]>, directions: Option<&[PeerDirection]>, ) -> Result { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("node") .push("peers"); if let Some(states) = states { let state_string = states .iter() .map(|i| i.to_string()) .collect::>() .join(","); path.query_pairs_mut().append_pair("state", &state_string); } if let Some(directions) = directions { let dir_string = directions .iter() .map(|i| i.to_string()) .collect::>() .join(","); path.query_pairs_mut().append_pair("direction", &dir_string); } self.get(path).await } /// `GET node/peer_count` pub async fn get_node_peer_count(&self) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("node") .push("peer_count"); self.get(path).await } /// URL path for `v2/debug/beacon/states/{state_id}`. pub fn get_debug_beacon_states_path(&self, state_id: StateId) -> Result { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("debug") .push("beacon") .push("states") .push(&state_id.to_string()); Ok(path) } /// `GET v2/debug/beacon/states/{state_id}` pub async fn get_debug_beacon_states( &self, state_id: StateId, ) -> Result>>, Error> { let path = self.get_debug_beacon_states_path(state_id)?; self.get_opt(path) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `GET debug/beacon/states/{state_id}` /// `-H "accept: application/octet-stream"` pub async fn get_debug_beacon_states_ssz( &self, state_id: StateId, spec: &ChainSpec, ) -> Result>, Error> { let path = self.get_debug_beacon_states_path(state_id)?; self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_debug_beacon_states) .await? .map(|bytes| BeaconState::from_ssz_bytes(&bytes, spec).map_err(Error::InvalidSsz)) .transpose() } /// `GET v2/debug/beacon/heads` pub async fn get_debug_beacon_heads( &self, ) -> Result>, Error> { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("debug") .push("beacon") .push("heads"); self.get(path).await } /// `GET v1/debug/beacon/heads` (LEGACY) pub async fn get_debug_beacon_heads_v1( &self, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("debug") .push("beacon") .push("heads"); self.get(path).await } /// `GET v1/debug/fork_choice` pub async fn get_debug_fork_choice(&self) -> Result { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("debug") .push("fork_choice"); self.get(path).await } /// `GET validator/duties/proposer/{epoch}` pub async fn get_validator_duties_proposer( &self, epoch: Epoch, ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("duties") .push("proposer") .push(&epoch.to_string()); self.get_with_timeout(path, self.timeouts.proposer_duties) .await } /// `GET v2/validator/blocks/{slot}` pub async fn get_validator_blocks( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, ) -> Result>, Error> { self.get_validator_blocks_modular(slot, randao_reveal, graffiti, SkipRandaoVerification::No) .await } /// `GET v2/validator/blocks/{slot}` pub async fn get_validator_blocks_modular( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, ) -> Result>, Error> { let path = self .get_validator_blocks_path::(slot, randao_reveal, graffiti, skip_randao_verification) .await?; self.get(path).await.map(BeaconResponse::ForkVersioned) } /// returns `GET v2/validator/blocks/{slot}` URL path pub async fn get_validator_blocks_path( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, ) -> Result { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("blocks") .push(&slot.to_string()); path.query_pairs_mut() .append_pair("randao_reveal", &randao_reveal.to_string()); if let Some(graffiti) = graffiti { path.query_pairs_mut() .append_pair("graffiti", &graffiti.to_string()); } if skip_randao_verification == SkipRandaoVerification::Yes { path.query_pairs_mut() .append_pair("skip_randao_verification", ""); } Ok(path) } /// returns `GET v3/validator/blocks/{slot}` URL path pub async fn get_validator_blocks_v3_path( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, builder_booster_factor: Option, graffiti_policy: Option, ) -> Result { let mut path = self.eth_path(V3)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("blocks") .push(&slot.to_string()); path.query_pairs_mut() .append_pair("randao_reveal", &randao_reveal.to_string()); if let Some(graffiti) = graffiti { path.query_pairs_mut() .append_pair("graffiti", &graffiti.to_string()); } if skip_randao_verification == SkipRandaoVerification::Yes { path.query_pairs_mut() .append_pair("skip_randao_verification", ""); } if let Some(builder_booster_factor) = builder_booster_factor { path.query_pairs_mut() .append_pair("builder_boost_factor", &builder_booster_factor.to_string()); } // Only append the HTTP URL request if the graffiti_policy is to AppendClientVersions // If PreserveUserGraffiti (default), then the HTTP URL request does not contain graffiti_policy // so that the default case is compliant to the spec if let Some(GraffitiPolicy::AppendClientVersions) = graffiti_policy { path.query_pairs_mut() .append_pair("graffiti_policy", "AppendClientVersions"); } Ok(path) } /// `GET v3/validator/blocks/{slot}` pub async fn get_validator_blocks_v3( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, builder_booster_factor: Option, graffiti_policy: Option, ) -> Result<(JsonProduceBlockV3Response, ProduceBlockV3Metadata), Error> { self.get_validator_blocks_v3_modular( slot, randao_reveal, graffiti, SkipRandaoVerification::No, builder_booster_factor, graffiti_policy, ) .await } /// `GET v3/validator/blocks/{slot}` pub async fn get_validator_blocks_v3_modular( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, builder_booster_factor: Option, graffiti_policy: Option, ) -> Result<(JsonProduceBlockV3Response, ProduceBlockV3Metadata), Error> { let path = self .get_validator_blocks_v3_path( slot, randao_reveal, graffiti, skip_randao_verification, builder_booster_factor, graffiti_policy, ) .await?; let opt_result = self .get_response_with_response_headers( path, Accept::Json, self.timeouts.get_validator_block, |response, headers| async move { let header_metadata = ProduceBlockV3Metadata::try_from(&headers) .map_err(Error::InvalidHeaders)?; if header_metadata.execution_payload_blinded { let blinded_response = response .json::, ProduceBlockV3Metadata>>() .await? .map_data(ProduceBlockV3Response::Blinded); Ok((blinded_response, header_metadata)) } else { let full_block_response= response .json::, ProduceBlockV3Metadata>>() .await? .map_data(ProduceBlockV3Response::Full); Ok((full_block_response, header_metadata)) } }, ) .await?; // Generic handler is optional but this route should never 404 unless unimplemented, so // treat that as an error. opt_result.ok_or(Error::StatusCode(StatusCode::NOT_FOUND)) } /// `GET v3/validator/blocks/{slot}` in ssz format pub async fn get_validator_blocks_v3_ssz( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, builder_booster_factor: Option, graffiti_policy: Option, ) -> Result<(ProduceBlockV3Response, ProduceBlockV3Metadata), Error> { self.get_validator_blocks_v3_modular_ssz::( slot, randao_reveal, graffiti, SkipRandaoVerification::No, builder_booster_factor, graffiti_policy, ) .await } /// `GET v3/validator/blocks/{slot}` in ssz format pub async fn get_validator_blocks_v3_modular_ssz( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, builder_booster_factor: Option, graffiti_policy: Option, ) -> Result<(ProduceBlockV3Response, ProduceBlockV3Metadata), Error> { let path = self .get_validator_blocks_v3_path( slot, randao_reveal, graffiti, skip_randao_verification, builder_booster_factor, graffiti_policy, ) .await?; let opt_response = self .get_response_with_response_headers( path, Accept::Ssz, self.timeouts.get_validator_block, |response, headers| async move { let metadata = ProduceBlockV3Metadata::try_from(&headers) .map_err(Error::InvalidHeaders)?; let response_bytes = response.bytes().await?; // Parse bytes based on metadata. let response = if metadata.execution_payload_blinded { ProduceBlockV3Response::Blinded( BlindedBeaconBlock::from_ssz_bytes_for_fork( &response_bytes, metadata.consensus_version, ) .map_err(Error::InvalidSsz)?, ) } else { ProduceBlockV3Response::Full( FullBlockContents::from_ssz_bytes_for_fork( &response_bytes, metadata.consensus_version, ) .map_err(Error::InvalidSsz)?, ) }; Ok((response, metadata)) }, ) .await?; // Generic handler is optional but this route should never 404 unless unimplemented, so // treat that as an error. opt_response.ok_or(Error::StatusCode(StatusCode::NOT_FOUND)) } /// `GET v2/validator/blocks/{slot}` in ssz format pub async fn get_validator_blocks_ssz( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, ) -> Result>, Error> { self.get_validator_blocks_modular_ssz::( slot, randao_reveal, graffiti, SkipRandaoVerification::No, ) .await } /// `GET v2/validator/blocks/{slot}` in ssz format pub async fn get_validator_blocks_modular_ssz( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, ) -> Result>, Error> { let path = self .get_validator_blocks_path::(slot, randao_reveal, graffiti, skip_randao_verification) .await?; self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_validator_block) .await } /// `GET v2/validator/blinded_blocks/{slot}` pub async fn get_validator_blinded_blocks( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, ) -> Result>, Error> { self.get_validator_blinded_blocks_modular( slot, randao_reveal, graffiti, SkipRandaoVerification::No, ) .await } /// returns `GET v1/validator/blinded_blocks/{slot}` URL path pub async fn get_validator_blinded_blocks_path( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, ) -> Result { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("blinded_blocks") .push(&slot.to_string()); path.query_pairs_mut() .append_pair("randao_reveal", &randao_reveal.to_string()); if let Some(graffiti) = graffiti { path.query_pairs_mut() .append_pair("graffiti", &graffiti.to_string()); } if skip_randao_verification == SkipRandaoVerification::Yes { path.query_pairs_mut() .append_key_only("skip_randao_verification"); } Ok(path) } /// `GET v1/validator/blinded_blocks/{slot}` pub async fn get_validator_blinded_blocks_modular( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, ) -> Result>, Error> { let path = self .get_validator_blinded_blocks_path::( slot, randao_reveal, graffiti, skip_randao_verification, ) .await?; self.get(path).await.map(BeaconResponse::ForkVersioned) } /// `GET v2/validator/blinded_blocks/{slot}` in ssz format pub async fn get_validator_blinded_blocks_ssz( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, ) -> Result>, Error> { self.get_validator_blinded_blocks_modular_ssz::( slot, randao_reveal, graffiti, SkipRandaoVerification::No, ) .await } pub async fn get_validator_blinded_blocks_modular_ssz( &self, slot: Slot, randao_reveal: &SignatureBytes, graffiti: Option<&Graffiti>, skip_randao_verification: SkipRandaoVerification, ) -> Result>, Error> { let path = self .get_validator_blinded_blocks_path::( slot, randao_reveal, graffiti, skip_randao_verification, ) .await?; self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_validator_block) .await } /// `GET validator/attestation_data?slot,committee_index` pub async fn get_validator_attestation_data( &self, slot: Slot, committee_index: CommitteeIndex, ) -> Result, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("attestation_data"); path.query_pairs_mut() .append_pair("slot", &slot.to_string()) .append_pair("committee_index", &committee_index.to_string()); self.get_with_timeout(path, self.timeouts.attestation).await } /// `GET v1/validator/aggregate_attestation?slot,attestation_data_root` pub async fn get_validator_aggregate_attestation_v1( &self, slot: Slot, attestation_data_root: Hash256, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("aggregate_attestation"); path.query_pairs_mut() .append_pair("slot", &slot.to_string()) .append_pair( "attestation_data_root", &format!("{:?}", attestation_data_root), ); self.get_opt_with_timeout(path, self.timeouts.attestation) .await } /// `GET v2/validator/aggregate_attestation?slot,attestation_data_root,committee_index` pub async fn get_validator_aggregate_attestation_v2( &self, slot: Slot, attestation_data_root: Hash256, committee_index: CommitteeIndex, ) -> Result>>, Error> { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("aggregate_attestation"); path.query_pairs_mut() .append_pair("slot", &slot.to_string()) .append_pair( "attestation_data_root", &format!("{:?}", attestation_data_root), ) .append_pair("committee_index", &committee_index.to_string()); self.get_opt_with_timeout(path, self.timeouts.attestation) .await .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } /// `GET validator/sync_committee_contribution` pub async fn get_validator_sync_committee_contribution( &self, sync_committee_data: &SyncContributionData, ) -> Result>>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("sync_committee_contribution"); path.query_pairs_mut() .append_pair("slot", &sync_committee_data.slot.to_string()) .append_pair( "beacon_block_root", &format!("{:?}", sync_committee_data.beacon_block_root), ) .append_pair( "subcommittee_index", &sync_committee_data.subcommittee_index.to_string(), ); self.get_opt(path).await } /// `POST lighthouse/liveness` pub async fn post_lighthouse_liveness( &self, ids: &[u64], epoch: Epoch, ) -> Result>, Error> { let mut path = self.server.expose_full().clone(); path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("lighthouse") .push("liveness"); self.post_with_timeout_and_response( path, &LivenessRequestData { indices: ids.to_vec(), epoch, }, self.timeouts.liveness, ) .await } /// `POST validator/liveness/{epoch}` pub async fn post_validator_liveness_epoch( &self, epoch: Epoch, indices: &[u64], ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("liveness") .push(&epoch.to_string()); self.post_with_timeout_and_response( path, &ValidatorIndexDataRef(indices), self.timeouts.liveness, ) .await } /// `POST validator/duties/attester/{epoch}` pub async fn post_validator_duties_attester( &self, epoch: Epoch, indices: &[u64], ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("duties") .push("attester") .push(&epoch.to_string()); self.post_with_timeout_and_response( path, &ValidatorIndexDataRef(indices), self.timeouts.attester_duties, ) .await } /// `POST v1/validator/aggregate_and_proofs` pub async fn post_validator_aggregate_and_proof_v1( &self, aggregates: &[SignedAggregateAndProof], ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("aggregate_and_proofs"); self.post_with_timeout(path, &aggregates, self.timeouts.attestation) .await?; Ok(()) } /// `POST v2/validator/aggregate_and_proofs` pub async fn post_validator_aggregate_and_proof_v2( &self, aggregates: &[SignedAggregateAndProof], fork_name: ForkName, ) -> Result<(), Error> { let mut path = self.eth_path(V2)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("aggregate_and_proofs"); self.post_with_timeout_and_consensus_header( path, &aggregates, self.timeouts.attestation, fork_name, ) .await?; Ok(()) } /// `POST validator/beacon_committee_subscriptions` pub async fn post_validator_beacon_committee_subscriptions( &self, subscriptions: &[BeaconCommitteeSubscription], ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("beacon_committee_subscriptions"); self.post_with_timeout( path, &subscriptions, self.timeouts.attestation_subscriptions, ) .await?; Ok(()) } /// `POST validator/sync_committee_subscriptions` pub async fn post_validator_sync_committee_subscriptions( &self, subscriptions: &[SyncCommitteeSubscription], ) -> Result<(), Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("sync_committee_subscriptions"); self.post(path, &subscriptions).await?; Ok(()) } /// `GET events?topics` #[cfg(feature = "events")] pub async fn get_events( &self, topic: &[EventTopic], ) -> Result, Error>> + use, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("events"); let topic_string = topic .iter() .map(|i| i.to_string()) .collect::>() .join(","); path.query_pairs_mut().append_pair("topics", &topic_string); let mut es = self .client .get(path) .timeout(self.timeouts.events) .eventsource() .map_err(Error::SseEventSource)?; // If we don't await `Event::Open` here, then the consumer // will not get any Message events until they start awaiting the stream. // This is a way to register the stream with the sse server before // message events start getting emitted. while let Some(event) = es.next().await { match event { Ok(Event::Open) => break, Err(err) => return Err(Error::SseClient(err.into())), // This should never happen as we are guaranteed to get the // Open event before any message starts coming through. Ok(Event::Message(_)) => continue, } } Ok(Box::pin(es.filter_map(|event| async move { match event { Ok(Event::Open) => None, Ok(Event::Message(message)) => { Some(EventKind::from_sse_bytes(&message.event, &message.data)) } Err(err) => Some(Err(Error::SseClient(err.into()))), } }))) } /// `POST validator/duties/sync/{epoch}` pub async fn post_validator_duties_sync( &self, epoch: Epoch, indices: &[u64], ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("duties") .push("sync") .push(&epoch.to_string()); self.post_with_timeout_and_response( path, &ValidatorIndexDataRef(indices), self.timeouts.sync_duties, ) .await } /// `POST validator/beacon_committee_selections` pub async fn post_validator_beacon_committee_selections( &self, selections: &[BeaconCommitteeSelection], ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("beacon_committee_selections"); self.post_with_timeout_and_response( path, &selections, self.timeouts.attestation_aggregators, ) .await } /// `POST validator/sync_committee_selections` pub async fn post_validator_sync_committee_selections( &self, selections: &[SyncCommitteeSelection], ) -> Result>, Error> { let mut path = self.eth_path(V1)?; path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? .push("validator") .push("sync_committee_selections"); self.post_with_timeout_and_response(path, &selections, self.timeouts.sync_aggregators) .await } }