Files
lighthouse/common/eth2/src/lib.rs
chonghe a93cafee08 Implement selections Beacon API endpoints to support DVT middleware (#7016)
* #6610


  - [x] Add `beacon_committee_selections` endpoint
- [x] Test beacon committee aggregator and confirmed working
- [x] Add `sync_committee_selections` endpoint
- [x] Test sync committee aggregator and confirmed working
2025-09-03 03:50:41 +00:00

2906 lines
93 KiB
Rust

//! 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.
#[cfg(feature = "lighthouse")]
pub mod lighthouse;
#[cfg(feature = "lighthouse")]
pub mod lighthouse_vc;
pub mod mixin;
pub mod types;
use self::mixin::{RequestAccept, ResponseOptional};
use self::types::{Error as ResponseError, *};
use ::types::beacon_response::ExecutionOptimisticFinalizedBeaconResponse;
use derivative::Derivative;
use futures::Stream;
use futures_util::StreamExt;
use libp2p_identity::PeerId;
use pretty_reqwest_error::PrettyReqwestError;
pub use reqwest;
use reqwest::{
Body, IntoUrl, RequestBuilder, Response,
header::{HeaderMap, HeaderValue},
};
pub use reqwest::{StatusCode, Url};
use reqwest_eventsource::{Event, EventSource};
pub use sensitive_url::{SensitiveError, SensitiveUrl};
use serde::{Serialize, de::DeserializeOwned};
use ssz::Encode;
use std::fmt;
use std::future::Future;
use std::path::PathBuf;
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;
const HTTP_DEFAULT_TIMEOUT_QUOTIENT: u32 = 4;
#[derive(Debug)]
pub enum Error {
/// The `reqwest` client raised an error.
HttpClient(PrettyReqwestError),
/// The `reqwest_eventsource` client raised an error.
SseClient(Box<reqwest_eventsource::Error>),
/// The server returned an error message where the body was able to be parsed.
ServerMessage(ErrorMessage),
/// The server returned an error message with an array of errors.
ServerIndexedMessage(IndexedErrorMessage),
/// The server returned an error message where the body was unable to be parsed.
StatusCode(StatusCode),
/// The supplied URL is badly formatted. It should look something like `http://127.0.0.1:5052`.
InvalidUrl(SensitiveUrl),
/// The supplied validator client secret is invalid.
InvalidSecret(String),
/// The server returned a response with an invalid signature. It may be an impostor.
InvalidSignatureHeader,
/// The server returned a response without a signature header. It may be an impostor.
MissingSignatureHeader,
/// The server returned an invalid JSON response.
InvalidJson(serde_json::Error),
/// The server returned an invalid server-sent event.
InvalidServerSentEvent(String),
/// The server sent invalid response headers.
InvalidHeaders(String),
/// The server returned an invalid SSZ response.
InvalidSsz(ssz::DecodeError),
/// An I/O error occurred while loading an API token from disk.
TokenReadError(PathBuf, std::io::Error),
/// The client has been configured without a server pubkey, but requires one for this request.
NoServerPubkey,
/// The client has been configured without an API token, but requires one for this request.
NoToken,
}
impl From<reqwest::Error> for Error {
fn from(error: reqwest::Error) -> Self {
Error::HttpClient(error.into())
}
}
impl Error {
/// If the error has a HTTP status code, return it.
pub fn status(&self) -> Option<StatusCode> {
match self {
Error::HttpClient(error) => error.inner().status(),
Error::SseClient(error) => {
if let reqwest_eventsource::Error::InvalidStatusCode(status, _) = error.as_ref() {
Some(*status)
} else {
None
}
}
Error::ServerMessage(msg) => StatusCode::try_from(msg.code).ok(),
Error::ServerIndexedMessage(msg) => StatusCode::try_from(msg.code).ok(),
Error::StatusCode(status) => Some(*status),
Error::InvalidUrl(_) => None,
Error::InvalidSecret(_) => None,
Error::InvalidSignatureHeader => None,
Error::MissingSignatureHeader => None,
Error::InvalidJson(_) => None,
Error::InvalidSsz(_) => None,
Error::InvalidServerSentEvent(_) => None,
Error::InvalidHeaders(_) => None,
Error::TokenReadError(..) => None,
Error::NoServerPubkey | Error::NoToken => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// 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 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,
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,
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, Derivative)]
#[derivative(PartialEq)]
pub struct BeaconNodeHttpClient {
#[derivative(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 AsRef<str> for BeaconNodeHttpClient {
fn as_ref(&self) -> &str {
self.server.as_ref()
}
}
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,
}
}
/// Return the path with the standard `/eth/vX` prefix applied.
fn eth_path(&self, version: EndpointVersion) -> Result<Url, Error> {
let mut path = self.server.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<T: DeserializeOwned, U: IntoUrl>(&self, url: U) -> Result<T, Error> {
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<U: IntoUrl>(
&self,
url: U,
builder: impl FnOnce(RequestBuilder) -> RequestBuilder,
) -> Result<Response, Error> {
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<T: DeserializeOwned, U: IntoUrl>(
&self,
url: U,
timeout: Duration,
) -> Result<T, Error> {
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<T: DeserializeOwned, U: IntoUrl>(&self, url: U) -> Result<Option<T>, 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<T: DeserializeOwned, U: IntoUrl>(
&self,
url: U,
timeout: Duration,
) -> Result<Option<T>, 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<T, U, Ctx, Meta>(
&self,
url: U,
ctx_constructor: impl Fn(ForkName) -> Ctx,
) -> Result<Option<ForkVersionedResponse<T, Meta>>, 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<U: IntoUrl>(
&self,
url: U,
accept_header: Accept,
timeout: Duration,
) -> Result<Option<Vec<u8>>, 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::<Vec<_>>())),
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<U: IntoUrl, F, T>(
&self,
url: U,
accept_header: Accept,
timeout: Duration,
parser: impl FnOnce(Response, HeaderMap) -> F,
) -> Result<Option<T>, Error>
where
F: Future<Output = Result<T, Error>>,
{
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<T: Serialize, U: IntoUrl>(&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<T: Serialize, U: IntoUrl, R: DeserializeOwned>(
&self,
url: U,
body: &T,
) -> Result<R, Error> {
self.post_generic(url, body, None)
.await?
.json()
.await
.map_err(Into::into)
}
async fn post_with_opt_response<T: Serialize, U: IntoUrl, R: DeserializeOwned>(
&self,
url: U,
body: &T,
) -> Result<Option<R>, 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<T: Serialize, U: IntoUrl>(
&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<T: Serialize, U: IntoUrl>(
&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<T: DeserializeOwned, U: IntoUrl, V: Serialize>(
&self,
url: U,
body: &V,
timeout: Duration,
) -> Result<T, Error> {
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<T: Serialize, U: IntoUrl>(
&self,
url: U,
body: &T,
timeout: Option<Duration>,
) -> Result<Response, Error> {
let builder = self
.client
.post(url)
.timeout(timeout.unwrap_or(self.timeouts.default));
let response = builder.json(body).send().await?;
ok_or_error(response).await
}
/// Generic POST function supporting arbitrary responses and timeouts.
async fn post_generic_with_consensus_version<T: Serialize, U: IntoUrl>(
&self,
url: U,
body: &T,
timeout: Option<Duration>,
fork: ForkName,
) -> Result<Response, Error> {
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?;
ok_or_error(response).await
}
/// Generic POST function that includes octet-stream content type header.
async fn post_generic_with_ssz_header<T: Serialize, U: IntoUrl>(
&self,
url: U,
body: &T,
) -> Result<Response, Error> {
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?;
ok_or_error(response).await
}
/// Generic POST function supporting arbitrary responses and timeouts.
async fn post_generic_with_consensus_version_and_ssz_body<T: Into<Body>, U: IntoUrl>(
&self,
url: U,
body: T,
timeout: Option<Duration>,
fork: ForkName,
) -> Result<Response, Error> {
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?;
ok_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<GenericResponse<GenesisData>, 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<Option<ExecutionOptimisticFinalizedResponse<RootData>>, 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<Option<ExecutionOptimisticFinalizedResponse<Fork>>, 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<Option<ExecutionOptimisticFinalizedResponse<FinalityCheckpointsData>>, 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<Option<ExecutionOptimisticFinalizedResponse<Vec<ValidatorBalanceData>>>, 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::<Vec<_>>()
.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<ValidatorId>,
) -> Result<Response, 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_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<ValidatorId>,
) -> Result<Option<ExecutionOptimisticFinalizedResponse<Vec<ValidatorBalanceData>>>, 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<ValidatorId>,
) -> Result<Option<ExecutionOptimisticFinalizedResponse<Vec<ValidatorIdentityData>>>, 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<Option<ExecutionOptimisticFinalizedResponse<Vec<ValidatorData>>>, 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::<Vec<_>>()
.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::<Vec<_>>()
.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<Vec<ValidatorId>>,
statuses: Option<Vec<ValidatorStatus>>,
) -> Result<Option<ExecutionOptimisticFinalizedResponse<Vec<ValidatorData>>>, 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<Slot>,
index: Option<u64>,
epoch: Option<Epoch>,
) -> Result<Option<ExecutionOptimisticFinalizedResponse<Vec<CommitteeData>>>, 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<Epoch>,
) -> Result<ExecutionOptimisticFinalizedResponse<SyncCommitteeByValidatorIndices>, 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<Epoch>,
) -> Result<Option<ExecutionOptimisticFinalizedResponse<RandaoMix>>, 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<Option<ExecutionOptimisticFinalizedResponse<ValidatorData>>, 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<Option<ExecutionOptimisticFinalizedResponse<Vec<PendingDeposit>>>, 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_opt(path).await
}
/// `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<ExecutionOptimisticFinalizedResponse<Vec<PendingPartialWithdrawal>>>, 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_opt(path).await
}
/// `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<Option<ExecutionOptimisticFinalizedResponse<Vec<PendingConsolidation>>>, 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_opt(path).await
}
/// `GET beacon/light_client/updates`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_light_client_updates<E: EthSpec>(
&self,
start_period: u64,
count: u64,
) -> Result<Option<Vec<BeaconResponse<LightClientUpdate<E>>>>, 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<E: EthSpec>(
&self,
start_period: u64,
count: u64,
) -> Result<Option<Vec<u8>>, 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<E: EthSpec>(
&self,
block_root: Hash256,
) -> Result<Option<BeaconResponse<LightClientBootstrap<E>>>, 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<E: EthSpec>(
&self,
) -> Result<Option<BeaconResponse<LightClientOptimisticUpdate<E>>>, 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<E: EthSpec>(
&self,
) -> Result<Option<BeaconResponse<LightClientFinalityUpdate<E>>>, 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<Slot>,
parent_root: Option<Hash256>,
) -> Result<Option<ExecutionOptimisticFinalizedResponse<Vec<BlockHeaderData>>>, 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<Option<ExecutionOptimisticFinalizedResponse<BlockHeaderData>>, 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<E: EthSpec>(
&self,
block_contents: &PublishBlockRequest<E>,
) -> 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<E: EthSpec>(
&self,
block_contents: &PublishBlockRequest<E>,
) -> 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<E: EthSpec>(
&self,
block: &SignedBlindedBeaconBlock<E>,
) -> 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<E: EthSpec>(
&self,
block: &SignedBlindedBeaconBlock<E>,
) -> 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<BroadcastValidation>,
) -> Result<Url, Error> {
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<BroadcastValidation>,
) -> Result<Url, Error> {
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<E: EthSpec>(
&self,
block_contents: &PublishBlockRequest<E>,
validation_level: Option<BroadcastValidation>,
) -> Result<(), Error> {
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(())
}
/// `POST v2/beacon/blocks`
pub async fn post_beacon_blocks_v2_ssz<E: EthSpec>(
&self,
block_contents: &PublishBlockRequest<E>,
validation_level: Option<BroadcastValidation>,
) -> Result<(), Error> {
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(())
}
/// `POST v2/beacon/blinded_blocks`
pub async fn post_beacon_blinded_blocks_v2<E: EthSpec>(
&self,
signed_block: &SignedBlindedBeaconBlock<E>,
validation_level: Option<BroadcastValidation>,
) -> Result<(), Error> {
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(())
}
/// `POST v2/beacon/blinded_blocks`
pub async fn post_beacon_blinded_blocks_v2_ssz<E: EthSpec>(
&self,
signed_block: &SignedBlindedBeaconBlock<E>,
validation_level: Option<BroadcastValidation>,
) -> Result<(), Error> {
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(())
}
/// Path for `v2/beacon/blocks`
pub fn get_beacon_blocks_path(&self, block_id: BlockId) -> Result<Url, 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());
Ok(path)
}
/// Path for `v1/beacon/blob_sidecars/{block_id}`
pub fn get_blobs_path(&self, block_id: BlockId) -> Result<Url, Error> {
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/blinded_blocks/{block_id}`
pub fn get_beacon_blinded_blocks_path(&self, block_id: BlockId) -> Result<Url, Error> {
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<E: EthSpec>(
&self,
block_id: BlockId,
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<SignedBeaconBlock<E>>>, 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_blobs<E: EthSpec>(
&self,
block_id: BlockId,
indices: Option<&[u64]>,
spec: &ChainSpec,
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<BlobSidecarList<E>>>, Error> {
let mut path = self.get_blobs_path(block_id)?;
if let Some(indices) = indices {
let indices_string = indices
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.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/blinded_blocks/{block_id}`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_blinded_blocks<E: EthSpec>(
&self,
block_id: BlockId,
) -> Result<
Option<ExecutionOptimisticFinalizedBeaconResponse<SignedBlindedBeaconBlock<E>>>,
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<E: EthSpec>(
&self,
block_id: BlockId,
) -> Result<Option<BeaconResponse<SignedBeaconBlock<E>>>, 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<E: EthSpec>(
&self,
block_id: BlockId,
spec: &ChainSpec,
) -> Result<Option<SignedBeaconBlock<E>>, 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<E: EthSpec>(
&self,
block_id: BlockId,
spec: &ChainSpec,
) -> Result<Option<SignedBlindedBeaconBlock<E>>, 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<Option<ExecutionOptimisticFinalizedResponse<RootData>>, 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<E: EthSpec>(
&self,
block_id: BlockId,
) -> Result<Option<ExecutionOptimisticFinalizedResponse<Vec<Attestation<E>>>>, 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<E: EthSpec>(
&self,
block_id: BlockId,
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<Vec<Attestation<E>>>>, 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<E: EthSpec>(
&self,
attestations: Vec<SingleAttestation>,
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<E: EthSpec>(
&self,
slot: Option<Slot>,
committee_index: Option<u64>,
) -> Result<GenericResponse<Vec<Attestation<E>>>, 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<E: EthSpec>(
&self,
slot: Option<Slot>,
committee_index: Option<u64>,
) -> Result<BeaconResponse<Vec<Attestation<E>>>, 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<E: EthSpec>(
&self,
slashing: &AttesterSlashing<E>,
) -> 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<E: EthSpec>(
&self,
slashing: &AttesterSlashing<E>,
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<E: EthSpec>(
&self,
) -> Result<GenericResponse<Vec<AttesterSlashing<E>>>, 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<E: EthSpec>(
&self,
) -> Result<BeaconResponse<Vec<AttesterSlashing<E>>>, 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<GenericResponse<Vec<ProposerSlashing>>, 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<GenericResponse<Vec<SignedVoluntaryExit>>, 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<GenericResponse<Vec<SyncCommitteeReward>>, 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<GenericResponse<StandardBlockReward>, 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<StandardAttestationRewards, Error> {
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<ExecutionOptimisticFinalizedResponse<Vec<Withdrawal>>, 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<E: EthSpec>(
&self,
signed_contributions: &[SignedContributionAndProof<E>],
) -> 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, &registration_data).await?;
Ok(())
}
/// `GET config/fork_schedule`
pub async fn get_config_fork_schedule(&self) -> Result<GenericResponse<Vec<Fork>>, 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<T: Serialize + DeserializeOwned>(
&self,
) -> Result<GenericResponse<T>, 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<GenericResponse<DepositContractData>, 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<GenericResponse<VersionData>, 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<GenericResponse<IdentityData>, 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<GenericResponse<SyncingData>, 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<StatusCode, Error> {
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: PeerId,
) -> Result<GenericResponse<PeerData>, 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.to_string());
self.get(path).await
}
/// `GET node/peers`
pub async fn get_node_peers(
&self,
states: Option<&[PeerState]>,
directions: Option<&[PeerDirection]>,
) -> Result<PeersData, Error> {
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::<Vec<_>>()
.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::<Vec<_>>()
.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<GenericResponse<PeerCount>, 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<Url, Error> {
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<E: EthSpec>(
&self,
state_id: StateId,
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<BeaconState<E>>>, 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<E: EthSpec>(
&self,
state_id: StateId,
spec: &ChainSpec,
) -> Result<Option<BeaconState<E>>, 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<GenericResponse<Vec<ChainHeadData>>, 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<GenericResponse<Vec<ChainHeadData>>, 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<ForkChoice, Error> {
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<DutiesResponse<Vec<ProposerData>>, 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<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
) -> Result<BeaconResponse<FullBlockContents<E>>, 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<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<BeaconResponse<FullBlockContents<E>>, Error> {
let path = self
.get_validator_blocks_path::<E>(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<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<Url, Error> {
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<u64>,
) -> Result<Url, Error> {
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());
}
Ok(path)
}
/// `GET v3/validator/blocks/{slot}`
pub async fn get_validator_blocks_v3<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
builder_booster_factor: Option<u64>,
) -> Result<(JsonProduceBlockV3Response<E>, ProduceBlockV3Metadata), Error> {
self.get_validator_blocks_v3_modular(
slot,
randao_reveal,
graffiti,
SkipRandaoVerification::No,
builder_booster_factor,
)
.await
}
/// `GET v3/validator/blocks/{slot}`
pub async fn get_validator_blocks_v3_modular<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
builder_booster_factor: Option<u64>,
) -> Result<(JsonProduceBlockV3Response<E>, ProduceBlockV3Metadata), Error> {
let path = self
.get_validator_blocks_v3_path(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
builder_booster_factor,
)
.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::<ForkVersionedResponse<BlindedBeaconBlock<E>,
ProduceBlockV3Metadata>>()
.await?
.map_data(ProduceBlockV3Response::Blinded);
Ok((blinded_response, header_metadata))
} else {
let full_block_response= response
.json::<ForkVersionedResponse<FullBlockContents<E>,
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<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
builder_booster_factor: Option<u64>,
) -> Result<(ProduceBlockV3Response<E>, ProduceBlockV3Metadata), Error> {
self.get_validator_blocks_v3_modular_ssz::<E>(
slot,
randao_reveal,
graffiti,
SkipRandaoVerification::No,
builder_booster_factor,
)
.await
}
/// `GET v3/validator/blocks/{slot}` in ssz format
pub async fn get_validator_blocks_v3_modular_ssz<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
builder_booster_factor: Option<u64>,
) -> Result<(ProduceBlockV3Response<E>, ProduceBlockV3Metadata), Error> {
let path = self
.get_validator_blocks_v3_path(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
builder_booster_factor,
)
.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<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
) -> Result<Option<Vec<u8>>, Error> {
self.get_validator_blocks_modular_ssz::<E>(
slot,
randao_reveal,
graffiti,
SkipRandaoVerification::No,
)
.await
}
/// `GET v2/validator/blocks/{slot}` in ssz format
pub async fn get_validator_blocks_modular_ssz<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<Option<Vec<u8>>, Error> {
let path = self
.get_validator_blocks_path::<E>(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<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
) -> Result<BeaconResponse<BlindedBeaconBlock<E>>, 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<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<Url, Error> {
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<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<BeaconResponse<BlindedBeaconBlock<E>>, Error> {
let path = self
.get_validator_blinded_blocks_path::<E>(
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<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
) -> Result<Option<Vec<u8>>, Error> {
self.get_validator_blinded_blocks_modular_ssz::<E>(
slot,
randao_reveal,
graffiti,
SkipRandaoVerification::No,
)
.await
}
pub async fn get_validator_blinded_blocks_modular_ssz<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
) -> Result<Option<Vec<u8>>, Error> {
let path = self
.get_validator_blinded_blocks_path::<E>(
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<GenericResponse<AttestationData>, 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<E: EthSpec>(
&self,
slot: Slot,
attestation_data_root: Hash256,
) -> Result<Option<GenericResponse<Attestation<E>>>, 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<E: EthSpec>(
&self,
slot: Slot,
attestation_data_root: Hash256,
committee_index: CommitteeIndex,
) -> Result<Option<BeaconResponse<Attestation<E>>>, 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<E: EthSpec>(
&self,
sync_committee_data: &SyncContributionData,
) -> Result<Option<GenericResponse<SyncCommitteeContribution<E>>>, 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<GenericResponse<Vec<LivenessResponseData>>, Error> {
let mut path = self.server.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<GenericResponse<Vec<StandardLivenessResponseData>>, 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<DutiesResponse<Vec<AttesterData>>, 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<E: EthSpec>(
&self,
aggregates: &[SignedAggregateAndProof<E>],
) -> 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<E: EthSpec>(
&self,
aggregates: &[SignedAggregateAndProof<E>],
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`
pub async fn get_events<E: EthSpec>(
&self,
topic: &[EventTopic],
) -> Result<impl Stream<Item = Result<EventKind<E>, Error>> + use<E>, 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::<Vec<_>>()
.join(",");
path.query_pairs_mut().append_pair("topics", &topic_string);
let mut es = EventSource::get(path);
// 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<ExecutionOptimisticFinalizedResponse<Vec<SyncDuty>>, 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<GenericResponse<Vec<BeaconCommitteeSelection>>, 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<GenericResponse<Vec<SyncCommitteeSelection>>, 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
}
}
/// Returns `Ok(response)` if the response is a `200 OK` response. Otherwise, creates an
/// appropriate error message.
pub async fn ok_or_error(response: Response) -> Result<Response, Error> {
let status = response.status();
if status == StatusCode::OK {
Ok(response)
} else if let Ok(message) = response.json().await {
match message {
ResponseError::Message(message) => Err(Error::ServerMessage(message)),
ResponseError::Indexed(indexed) => Err(Error::ServerIndexedMessage(indexed)),
}
} else {
Err(Error::StatusCode(status))
}
}