Resolve merge conflicts

This commit is contained in:
Eitan Seri-Levi
2026-01-02 08:52:14 -06:00
918 changed files with 49304 additions and 37273 deletions

View File

@@ -4,36 +4,36 @@ version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = { workspace = true }
[features]
default = []
lighthouse = ["proto_array", "eth2_keystore", "eip_3076", "zeroize"]
events = ["reqwest-eventsource", "futures", "futures-util"]
[dependencies]
derivative = { workspace = true }
either = { workspace = true }
enr = { version = "0.13.0", features = ["ed25519"] }
eth2_keystore = { workspace = true }
bls = { workspace = true }
context_deserialize = { workspace = true }
educe = { workspace = true }
eip_3076 = { workspace = true, optional = true }
eth2_keystore = { workspace = true, optional = true }
ethereum_serde_utils = { workspace = true }
ethereum_ssz = { workspace = true }
ethereum_ssz_derive = { workspace = true }
futures = { workspace = true }
futures-util = "0.3.8"
libp2p-identity = { version = "0.2", features = ["peerid"] }
futures = { workspace = true, optional = true }
futures-util = { version = "0.3.8", optional = true }
mediatype = "0.19.13"
multiaddr = "0.18.2"
pretty_reqwest_error = { workspace = true }
proto_array = { workspace = true }
rand = { workspace = true }
proto_array = { workspace = true, optional = true }
reqwest = { workspace = true }
reqwest-eventsource = "0.5.0"
reqwest-eventsource = { version = "0.6.0", optional = true }
sensitive_url = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
slashing_protection = { workspace = true }
ssz_types = { workspace = true }
test_random_derive = { path = "../../common/test_random_derive" }
superstruct = { workspace = true }
types = { workspace = true }
zeroize = { workspace = true }
zeroize = { workspace = true, optional = true }
[dev-dependencies]
rand = { workspace = true }
test_random_derive = { path = "../../common/test_random_derive" }
tokio = { workspace = true }
[features]
default = ["lighthouse"]
lighthouse = []

View File

@@ -0,0 +1,257 @@
use context_deserialize::ContextDeserialize;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::value::Value;
use types::ForkName;
/// The metadata of type M should be set to `EmptyMetadata` if you don't care about adding fields other than
/// version. If you *do* care about adding other fields you can mix in any type that implements
/// `Deserialize`.
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct ForkVersionedResponse<T, M = EmptyMetadata> {
pub version: ForkName,
#[serde(flatten)]
pub metadata: M,
pub data: T,
}
// Used for responses to V1 endpoints that don't have a version field.
/// The metadata of type M should be set to `EmptyMetadata` if you don't care about adding fields other than
/// version. If you *do* care about adding other fields you can mix in any type that implements
/// `Deserialize`.
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct UnversionedResponse<T, M = EmptyMetadata> {
#[serde(flatten)]
pub metadata: M,
pub data: T,
}
#[derive(Debug, PartialEq, Clone, Serialize)]
#[serde(untagged)]
pub enum BeaconResponse<T, M = EmptyMetadata> {
ForkVersioned(ForkVersionedResponse<T, M>),
Unversioned(UnversionedResponse<T, M>),
}
impl<T, M> BeaconResponse<T, M> {
pub fn version(&self) -> Option<ForkName> {
match self {
BeaconResponse::ForkVersioned(response) => Some(response.version),
BeaconResponse::Unversioned(_) => None,
}
}
pub fn data(&self) -> &T {
match self {
BeaconResponse::ForkVersioned(response) => &response.data,
BeaconResponse::Unversioned(response) => &response.data,
}
}
pub fn metadata(&self) -> &M {
match self {
BeaconResponse::ForkVersioned(response) => &response.metadata,
BeaconResponse::Unversioned(response) => &response.metadata,
}
}
}
/// Metadata type similar to unit (i.e. `()`) but deserializes from a map (`serde_json::Value`).
///
/// Unfortunately the braces are semantically significant, i.e. `struct EmptyMetadata;` does not
/// work.
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
pub struct EmptyMetadata {}
/// Fork versioned response with extra information about finalization & optimistic execution.
pub type ExecutionOptimisticFinalizedBeaconResponse<T> =
BeaconResponse<T, ExecutionOptimisticFinalizedMetadata>;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct ExecutionOptimisticFinalizedMetadata {
pub execution_optimistic: Option<bool>,
pub finalized: Option<bool>,
}
impl<'de, T, M> Deserialize<'de> for ForkVersionedResponse<T, M>
where
T: ContextDeserialize<'de, ForkName>,
M: DeserializeOwned,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
version: ForkName,
#[serde(flatten)]
metadata: Value,
data: Value,
}
let helper = Helper::deserialize(deserializer)?;
// Deserialize metadata
let metadata = serde_json::from_value(helper.metadata).map_err(serde::de::Error::custom)?;
// Deserialize `data` using ContextDeserialize
let data = T::context_deserialize(helper.data, helper.version)
.map_err(serde::de::Error::custom)?;
Ok(ForkVersionedResponse {
version: helper.version,
metadata,
data,
})
}
}
impl<'de, T, M> Deserialize<'de> for UnversionedResponse<T, M>
where
T: DeserializeOwned,
M: DeserializeOwned,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper<T, M> {
#[serde(flatten)]
metadata: M,
data: T,
}
let helper = Helper::deserialize(deserializer)?;
Ok(UnversionedResponse {
metadata: helper.metadata,
data: helper.data,
})
}
}
impl<T, M> BeaconResponse<T, M> {
pub fn map_data<U>(self, f: impl FnOnce(T) -> U) -> BeaconResponse<U, M> {
match self {
BeaconResponse::ForkVersioned(response) => {
BeaconResponse::ForkVersioned(response.map_data(f))
}
BeaconResponse::Unversioned(response) => {
BeaconResponse::Unversioned(response.map_data(f))
}
}
}
pub fn into_data(self) -> T {
match self {
BeaconResponse::ForkVersioned(response) => response.data,
BeaconResponse::Unversioned(response) => response.data,
}
}
}
impl<T, M> UnversionedResponse<T, M> {
pub fn map_data<U>(self, f: impl FnOnce(T) -> U) -> UnversionedResponse<U, M> {
let UnversionedResponse { metadata, data } = self;
UnversionedResponse {
metadata,
data: f(data),
}
}
}
impl<T, M> ForkVersionedResponse<T, M> {
/// Apply a function to the inner `data`, potentially changing its type.
pub fn map_data<U>(self, f: impl FnOnce(T) -> U) -> ForkVersionedResponse<U, M> {
let ForkVersionedResponse {
version,
metadata,
data,
} = self;
ForkVersionedResponse {
version,
metadata,
data: f(data),
}
}
}
impl<T, M> From<ForkVersionedResponse<T, M>> for BeaconResponse<T, M> {
fn from(response: ForkVersionedResponse<T, M>) -> Self {
BeaconResponse::ForkVersioned(response)
}
}
impl<T, M> From<UnversionedResponse<T, M>> for BeaconResponse<T, M> {
fn from(response: UnversionedResponse<T, M>) -> Self {
BeaconResponse::Unversioned(response)
}
}
#[cfg(test)]
mod fork_version_response_tests {
use crate::beacon_response::ExecutionOptimisticFinalizedMetadata;
use crate::{
ExecutionPayload, ExecutionPayloadBellatrix, ForkName, ForkVersionedResponse,
MainnetEthSpec, UnversionedResponse,
};
use serde_json::json;
#[test]
fn fork_versioned_response_deserialize_correct_fork() {
type E = MainnetEthSpec;
let response_json =
serde_json::to_string(&json!(ForkVersionedResponse::<ExecutionPayload<E>> {
version: ForkName::Bellatrix,
metadata: Default::default(),
data: ExecutionPayload::Bellatrix(ExecutionPayloadBellatrix::default()),
}))
.unwrap();
let result: Result<ForkVersionedResponse<ExecutionPayload<E>>, _> =
serde_json::from_str(&response_json);
assert!(result.is_ok());
}
#[test]
fn fork_versioned_response_deserialize_incorrect_fork() {
type E = MainnetEthSpec;
let response_json =
serde_json::to_string(&json!(ForkVersionedResponse::<ExecutionPayload<E>> {
version: ForkName::Capella,
metadata: Default::default(),
data: ExecutionPayload::Bellatrix(ExecutionPayloadBellatrix::default()),
}))
.unwrap();
let result: Result<ForkVersionedResponse<ExecutionPayload<E>>, _> =
serde_json::from_str(&response_json);
assert!(result.is_err());
}
// The following test should only pass by having the attribute #[serde(flatten)] on the metadata
#[test]
fn unversioned_response_serialize_dezerialize_round_trip_test() {
// Create an UnversionedResponse with some data
let data = UnversionedResponse {
metadata: ExecutionOptimisticFinalizedMetadata {
execution_optimistic: Some(false),
finalized: Some(false),
},
data: "some_test_data".to_string(),
};
let serialized = serde_json::to_string(&data);
let deserialized =
serde_json::from_str(&serialized.unwrap()).expect("Failed to deserialize");
assert_eq!(data, deserialized);
}
}

167
common/eth2/src/error.rs Normal file
View File

@@ -0,0 +1,167 @@
//! Centralized error handling for eth2 API clients
//!
//! This module consolidates all error types, response processing,
//! and recovery logic for both beacon node and validator client APIs.
use pretty_reqwest_error::PrettyReqwestError;
use reqwest::{Response, StatusCode};
use sensitive_url::SensitiveUrl;
use serde::{Deserialize, Serialize};
use std::{fmt, path::PathBuf};
/// Main error type for eth2 API clients
#[derive(Debug)]
pub enum Error {
/// The `reqwest` client raised an error.
HttpClient(PrettyReqwestError),
#[cfg(feature = "events")]
/// 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,
}
/// An API error serializable to JSON.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ErrorMessage {
pub code: u16,
pub message: String,
#[serde(default)]
pub stacktraces: Vec<String>,
}
/// An indexed API error serializable to JSON.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IndexedErrorMessage {
pub code: u16,
pub message: String,
pub failures: Vec<Failure>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Failure {
pub index: u64,
pub message: String,
}
impl Failure {
pub fn new(index: usize, message: String) -> Self {
Self {
index: index as u64,
message,
}
}
}
/// Server error response variants
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ResponseError {
Indexed(IndexedErrorMessage),
Message(ErrorMessage),
}
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(),
#[cfg(feature = "events")]
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 From<reqwest::Error> for Error {
fn from(error: reqwest::Error) -> Self {
Error::HttpClient(error.into())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// Returns `Ok(response)` if the response is a `200 OK`, `202 ACCEPTED`, or `204 NO_CONTENT`
/// 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
|| status == StatusCode::ACCEPTED
|| status == StatusCode::NO_CONTENT
{
Ok(response)
} else if let Ok(message) = response.json::<ResponseError>().await {
match message {
ResponseError::Message(message) => Err(Error::ServerMessage(message)),
ResponseError::Indexed(indexed) => Err(Error::ServerIndexedMessage(indexed)),
}
} else {
Err(Error::StatusCode(status))
}
}
/// Returns `Ok(response)` if the response is a success (2xx) response. Otherwise, creates an
/// appropriate error message.
pub async fn success_or_error(response: Response) -> Result<Response, Error> {
let status = response.status();
if status.is_success() {
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))
}
}

View File

@@ -7,6 +7,8 @@
//! 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")]
@@ -14,28 +16,35 @@ 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 either::Either;
use futures::Stream;
use futures_util::StreamExt;
use libp2p_identity::PeerId;
use pretty_reqwest_error::PrettyReqwestError;
pub use reqwest;
use reqwest::{
header::{HeaderMap, HeaderValue},
Body, IntoUrl, RequestBuilder, Response,
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, EventSource};
pub use sensitive_url::{SensitiveError, SensitiveUrl};
use serde::{de::DeserializeOwned, Serialize};
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);
@@ -51,82 +60,23 @@ 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";
#[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)
}
}
/// 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;
/// A struct to define a variety of different timeouts for different validator tasks to ensure
/// proper fallback behaviour.
@@ -135,11 +85,13 @@ 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 inclusion_list: Duration,
pub inclusion_list_duties: Duration,
pub get_beacon_blocks_ssz: Duration,
@@ -155,11 +107,13 @@ impl 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,
inclusion_list: timeout,
inclusion_list_duties: timeout,
get_beacon_blocks_ssz: timeout,
@@ -169,14 +123,39 @@ impl Timeouts {
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,
// TODO(EIP7805) check timeouts
inclusion_list_duties: base_timeout,
inclusion_list: base_timeout,
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)]
#[derive(Clone, Debug, Educe)]
#[educe(PartialEq)]
pub struct BeaconNodeHttpClient {
#[derivative(PartialEq = "ignore")]
#[educe(PartialEq(ignore))]
client: reqwest::Client,
server: SensitiveUrl,
timeouts: Timeouts,
@@ -190,12 +169,6 @@ impl fmt::Display for BeaconNodeHttpClient {
}
}
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 {
@@ -216,10 +189,14 @@ impl BeaconNodeHttpClient {
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<Url, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -459,7 +436,7 @@ impl BeaconNodeHttpClient {
.post(url)
.timeout(timeout.unwrap_or(self.timeouts.default));
let response = builder.json(body).send().await?;
ok_or_error(response).await
success_or_error(response).await
}
/// Generic POST function supporting arbitrary responses and timeouts.
@@ -479,7 +456,7 @@ impl BeaconNodeHttpClient {
.json(body)
.send()
.await?;
ok_or_error(response).await
success_or_error(response).await
}
/// Generic POST function that includes octet-stream content type header.
@@ -496,7 +473,7 @@ impl BeaconNodeHttpClient {
HeaderValue::from_static("application/octet-stream"),
);
let response = builder.headers(headers).json(body).send().await?;
ok_or_error(response).await
success_or_error(response).await
}
/// Generic POST function supporting arbitrary responses and timeouts.
@@ -521,7 +498,7 @@ impl BeaconNodeHttpClient {
HeaderValue::from_static("application/octet-stream"),
);
let response = builder.headers(headers).body(body).send().await?;
ok_or_error(response).await
success_or_error(response).await
}
/// `GET beacon/genesis`
@@ -670,6 +647,29 @@ impl BeaconNodeHttpClient {
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.
@@ -842,7 +842,8 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_states_pending_deposits(
&self,
state_id: StateId,
) -> Result<Option<ExecutionOptimisticFinalizedResponse<Vec<PendingDeposit>>>, Error> {
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<Vec<PendingDeposit>>>, Error>
{
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -852,7 +853,9 @@ impl BeaconNodeHttpClient {
.push(&state_id.to_string())
.push("pending_deposits");
self.get_opt(path).await
self.get_fork_contextual(path, |fork| fork)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET beacon/states/{state_id}/pending_partial_withdrawals`
@@ -861,8 +864,10 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_states_pending_partial_withdrawals(
&self,
state_id: StateId,
) -> Result<Option<ExecutionOptimisticFinalizedResponse<Vec<PendingPartialWithdrawal>>>, Error>
{
) -> Result<
Option<ExecutionOptimisticFinalizedBeaconResponse<Vec<PendingPartialWithdrawal>>>,
Error,
> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -872,7 +877,9 @@ impl BeaconNodeHttpClient {
.push(&state_id.to_string())
.push("pending_partial_withdrawals");
self.get_opt(path).await
self.get_fork_contextual(path, |fork| fork)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET beacon/states/{state_id}/pending_consolidations`
@@ -881,7 +888,7 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_states_pending_consolidations(
&self,
state_id: StateId,
) -> Result<Option<ExecutionOptimisticFinalizedResponse<Vec<PendingConsolidation>>>, Error>
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<Vec<PendingConsolidation>>>, Error>
{
let mut path = self.eth_path(V1)?;
@@ -892,7 +899,9 @@ impl BeaconNodeHttpClient {
.push(&state_id.to_string())
.push("pending_consolidations");
self.get_opt(path).await
self.get_fork_contextual(path, |fork| fork)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET beacon/light_client/updates`
@@ -927,6 +936,32 @@ impl BeaconNodeHttpClient {
})
}
/// `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.
@@ -1169,16 +1204,17 @@ impl BeaconNodeHttpClient {
&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?;
) -> Result<Response, Error> {
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(())
Ok(response)
}
/// `POST v2/beacon/blocks`
@@ -1186,16 +1222,17 @@ impl BeaconNodeHttpClient {
&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?;
) -> Result<Response, Error> {
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(())
Ok(response)
}
/// `POST v2/beacon/blinded_blocks`
@@ -1203,16 +1240,17 @@ impl BeaconNodeHttpClient {
&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?;
) -> Result<Response, Error> {
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(())
Ok(response)
}
/// `POST v2/beacon/blinded_blocks`
@@ -1220,16 +1258,17 @@ impl BeaconNodeHttpClient {
&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?;
) -> Result<Response, Error> {
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(())
Ok(response)
}
/// Path for `v2/beacon/blocks`
@@ -1244,7 +1283,7 @@ impl BeaconNodeHttpClient {
}
/// Path for `v1/beacon/blob_sidecars/{block_id}`
pub fn get_blobs_path(&self, block_id: BlockId) -> Result<Url, Error> {
pub fn get_blob_sidecars_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()))?
@@ -1254,6 +1293,17 @@ impl BeaconNodeHttpClient {
Ok(path)
}
/// Path for `v1/beacon/blobs/{blob_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("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<Url, Error> {
let mut path = self.eth_path(V1)?;
@@ -1282,13 +1332,13 @@ impl BeaconNodeHttpClient {
/// `GET v1/beacon/blob_sidecars/{block_id}`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_blobs<E: EthSpec>(
pub async fn get_blob_sidecars<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)?;
let mut path = self.get_blob_sidecars_path(block_id)?;
if let Some(indices) = indices {
let indices_string = indices
.iter()
@@ -1300,12 +1350,39 @@ impl BeaconNodeHttpClient {
}
self.get_fork_contextual(path, |fork| {
(fork, spec.max_blobs_per_block_by_fork(fork) as usize)
// 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<E: EthSpec>(
&self,
block_id: BlockId,
versioned_hashes: Option<&[Hash256]>,
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<Vec<BlobWrapper<E>>>>, 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::<Vec<_>>()
.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.
@@ -1436,29 +1513,10 @@ impl BeaconNodeHttpClient {
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `POST v1/beacon/pool/attestations`
pub async fn post_beacon_pool_attestations_v1<E: EthSpec>(
&self,
attestations: &[Attestation<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("attestations");
self.post_with_timeout(path, &attestations, self.timeouts.attestation)
.await?;
Ok(())
}
/// `POST v2/beacon/pool/attestations`
pub async fn post_beacon_pool_attestations_v2<E: EthSpec>(
&self,
attestations: Either<Vec<Attestation<E>>, Vec<SingleAttestation>>,
attestations: Vec<SingleAttestation>,
fork_name: ForkName,
) -> Result<(), Error> {
let mut path = self.eth_path(V2)?;
@@ -1469,26 +1527,13 @@ impl BeaconNodeHttpClient {
.push("pool")
.push("attestations");
match attestations {
Either::Right(attestations) => {
self.post_with_timeout_and_consensus_header(
path,
&attestations,
self.timeouts.attestation,
fork_name,
)
.await?;
}
Either::Left(attestations) => {
self.post_with_timeout_and_consensus_header(
path,
&attestations,
self.timeouts.attestation,
fork_name,
)
.await?;
}
};
self.post_with_timeout_and_consensus_header(
path,
&attestations,
self.timeouts.attestation,
fork_name,
)
.await?;
Ok(())
}
@@ -1736,18 +1781,6 @@ impl BeaconNodeHttpClient {
Ok(())
}
/// `GET beacon/deposit_snapshot`
pub async fn get_deposit_snapshot(&self) -> Result<Option<types::DepositTreeSnapshot>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("beacon")
.push("deposit_snapshot");
self.get_opt_with_timeout::<GenericResponse<_>, _>(path, self.timeouts.get_deposit_snapshot)
.await
.map(|opt| opt.map(|r| r.data))
}
/// `POST beacon/rewards/sync_committee`
pub async fn post_beacon_rewards_sync_committee(
&self,
@@ -1976,7 +2009,7 @@ impl BeaconNodeHttpClient {
/// `GET node/peers/{peer_id}`
pub async fn get_node_peers_by_id(
&self,
peer_id: PeerId,
peer_id: &str,
) -> Result<GenericResponse<PeerData>, Error> {
let mut path = self.eth_path(V1)?;
@@ -1984,7 +2017,7 @@ impl BeaconNodeHttpClient {
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("node")
.push("peers")
.push(&peer_id.to_string());
.push(peer_id);
self.get(path).await
}
@@ -2200,6 +2233,7 @@ impl BeaconNodeHttpClient {
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<Url, Error> {
let mut path = self.eth_path(V3)?;
@@ -2227,6 +2261,14 @@ impl BeaconNodeHttpClient {
.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)
}
@@ -2237,6 +2279,7 @@ impl BeaconNodeHttpClient {
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(JsonProduceBlockV3Response<E>, ProduceBlockV3Metadata), Error> {
self.get_validator_blocks_v3_modular(
slot,
@@ -2244,6 +2287,7 @@ impl BeaconNodeHttpClient {
graffiti,
SkipRandaoVerification::No,
builder_booster_factor,
graffiti_policy,
)
.await
}
@@ -2256,6 +2300,7 @@ impl BeaconNodeHttpClient {
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(JsonProduceBlockV3Response<E>, ProduceBlockV3Metadata), Error> {
let path = self
.get_validator_blocks_v3_path(
@@ -2264,6 +2309,7 @@ impl BeaconNodeHttpClient {
graffiti,
skip_randao_verification,
builder_booster_factor,
graffiti_policy,
)
.await?;
@@ -2306,6 +2352,7 @@ impl BeaconNodeHttpClient {
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(ProduceBlockV3Response<E>, ProduceBlockV3Metadata), Error> {
self.get_validator_blocks_v3_modular_ssz::<E>(
slot,
@@ -2313,6 +2360,7 @@ impl BeaconNodeHttpClient {
graffiti,
SkipRandaoVerification::No,
builder_booster_factor,
graffiti_policy,
)
.await
}
@@ -2325,6 +2373,7 @@ impl BeaconNodeHttpClient {
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(ProduceBlockV3Response<E>, ProduceBlockV3Metadata), Error> {
let path = self
.get_validator_blocks_v3_path(
@@ -2333,6 +2382,7 @@ impl BeaconNodeHttpClient {
graffiti,
skip_randao_verification,
builder_booster_factor,
graffiti_policy,
)
.await?;
@@ -2633,7 +2683,7 @@ impl BeaconNodeHttpClient {
ids: &[u64],
epoch: Epoch,
) -> Result<GenericResponse<Vec<LivenessResponseData>>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -2801,10 +2851,11 @@ impl BeaconNodeHttpClient {
}
/// `GET events?topics`
#[cfg(feature = "events")]
pub async fn get_events<E: EthSpec>(
&self,
topic: &[EventTopic],
) -> Result<impl Stream<Item = Result<EventKind<E>, Error>>, Error> {
) -> 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()))?
@@ -2864,21 +2915,40 @@ impl BeaconNodeHttpClient {
)
.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();
/// `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)?;
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))
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
}
}

View File

@@ -3,15 +3,13 @@
mod attestation_performance;
mod block_packing_efficiency;
mod block_rewards;
mod custody;
pub mod sync_state;
use crate::{
BeaconNodeHttpClient, DepositData, Error, Hash256, Slot,
lighthouse::sync_state::SyncState,
types::{
AdminPeer, DepositTreeSnapshot, Epoch, FinalizedExecutionBlock, GenericResponse,
ValidatorId,
},
BeaconNodeHttpClient, DepositData, Error, Eth1Data, Hash256, Slot,
types::{AdminPeer, Epoch, GenericResponse, ValidatorId},
};
use proto_array::core::ProtoArray;
use serde::{Deserialize, Serialize};
@@ -25,6 +23,7 @@ pub use block_packing_efficiency::{
BlockPackingEfficiency, BlockPackingEfficiencyQuery, ProposerInfo, UniqueAttestation,
};
pub use block_rewards::{AttestationRewards, BlockReward, BlockRewardMeta, BlockRewardsQuery};
pub use custody::CustodyInfo;
// Define "legacy" implementations of `Option<T>` which use four bytes for encoding the union
// selector.
@@ -159,18 +158,6 @@ pub struct ProcessHealth {
pub pid_process_seconds_total: u64,
}
/// Indicates how up-to-date the Eth1 caches are.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Eth1SyncStatusData {
pub head_block_number: Option<u64>,
pub head_block_timestamp: Option<u64>,
pub latest_cached_block_number: Option<u64>,
pub latest_cached_block_timestamp: Option<u64>,
pub voting_target_timestamp: u64,
pub eth1_node_sync_status_percentage: f64,
pub lighthouse_is_cached_and_ready: bool,
}
/// A fully parsed eth1 deposit contract log.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode)]
pub struct DepositLog {
@@ -183,45 +170,10 @@ pub struct DepositLog {
pub signature_is_valid: bool,
}
/// A block of the eth1 chain.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode)]
pub struct Eth1Block {
pub hash: Hash256,
pub timestamp: u64,
pub number: u64,
#[ssz(with = "four_byte_option_hash256")]
pub deposit_root: Option<Hash256>,
#[ssz(with = "four_byte_option_u64")]
pub deposit_count: Option<u64>,
}
impl Eth1Block {
pub fn eth1_data(self) -> Option<Eth1Data> {
Some(Eth1Data {
deposit_root: self.deposit_root?,
deposit_count: self.deposit_count?,
block_hash: self.hash,
})
}
}
impl From<Eth1Block> for FinalizedExecutionBlock {
fn from(eth1_block: Eth1Block) -> Self {
Self {
deposit_count: eth1_block.deposit_count.unwrap_or(0),
deposit_root: eth1_block
.deposit_root
.unwrap_or_else(|| DepositTreeSnapshot::default().deposit_root),
block_hash: eth1_block.hash,
block_height: eth1_block.number,
}
}
}
impl BeaconNodeHttpClient {
/// `GET lighthouse/health`
pub async fn get_lighthouse_health(&self) -> Result<GenericResponse<Health>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -233,7 +185,7 @@ impl BeaconNodeHttpClient {
/// `GET lighthouse/syncing`
pub async fn get_lighthouse_syncing(&self) -> Result<GenericResponse<SyncState>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -243,6 +195,32 @@ impl BeaconNodeHttpClient {
self.get(path).await
}
/// `GET lighthouse/custody/info`
pub async fn get_lighthouse_custody_info(&self) -> Result<CustodyInfo, Error> {
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("lighthouse")
.push("custody")
.push("info");
self.get(path).await
}
/// `POST lighthouse/custody/backfill`
pub async fn post_lighthouse_custody_backfill(&self) -> Result<(), Error> {
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("lighthouse")
.push("custody")
.push("backfill");
self.post(path, &()).await
}
/*
* Note:
*
@@ -253,7 +231,7 @@ impl BeaconNodeHttpClient {
/// `GET lighthouse/proto_array`
pub async fn get_lighthouse_proto_array(&self) -> Result<GenericResponse<ProtoArray>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -268,7 +246,7 @@ impl BeaconNodeHttpClient {
&self,
epoch: Epoch,
) -> Result<GenericResponse<GlobalValidatorInclusionData>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -286,7 +264,7 @@ impl BeaconNodeHttpClient {
epoch: Epoch,
validator_id: ValidatorId,
) -> Result<GenericResponse<Option<ValidatorInclusionData>>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -298,66 +276,9 @@ impl BeaconNodeHttpClient {
self.get(path).await
}
/// `GET lighthouse/eth1/syncing`
pub async fn get_lighthouse_eth1_syncing(
&self,
) -> Result<GenericResponse<Eth1SyncStatusData>, Error> {
let mut path = self.server.full.clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("lighthouse")
.push("eth1")
.push("syncing");
self.get(path).await
}
/// `GET lighthouse/eth1/block_cache`
pub async fn get_lighthouse_eth1_block_cache(
&self,
) -> Result<GenericResponse<Vec<Eth1Block>>, Error> {
let mut path = self.server.full.clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("lighthouse")
.push("eth1")
.push("block_cache");
self.get(path).await
}
/// `GET lighthouse/eth1/deposit_cache`
pub async fn get_lighthouse_eth1_deposit_cache(
&self,
) -> Result<GenericResponse<Vec<DepositLog>>, Error> {
let mut path = self.server.full.clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("lighthouse")
.push("eth1")
.push("deposit_cache");
self.get(path).await
}
/// `GET lighthouse/staking`
pub async fn get_lighthouse_staking(&self) -> Result<bool, Error> {
let mut path = self.server.full.clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("lighthouse")
.push("staking");
self.get_opt::<(), _>(path).await.map(|opt| opt.is_some())
}
/// `POST lighthouse/database/reconstruct`
pub async fn post_lighthouse_database_reconstruct(&self) -> Result<String, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -370,7 +291,7 @@ impl BeaconNodeHttpClient {
/// `POST lighthouse/add_peer`
pub async fn post_lighthouse_add_peer(&self, req: AdminPeer) -> Result<(), Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -382,7 +303,7 @@ impl BeaconNodeHttpClient {
/// `POST lighthouse/remove_peer`
pub async fn post_lighthouse_remove_peer(&self, req: AdminPeer) -> Result<(), Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -402,7 +323,7 @@ impl BeaconNodeHttpClient {
start_slot: Slot,
end_slot: Slot,
) -> Result<Vec<BlockReward>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -423,7 +344,7 @@ impl BeaconNodeHttpClient {
start_epoch: Epoch,
end_epoch: Epoch,
) -> Result<Vec<BlockPackingEfficiency>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -445,7 +366,7 @@ impl BeaconNodeHttpClient {
end_epoch: Epoch,
target: String,
) -> Result<Vec<AttestationPerformance>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?

View File

@@ -0,0 +1,11 @@
use serde::{Deserialize, Serialize};
use types::Slot;
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct CustodyInfo {
pub earliest_custodied_data_column_slot: Slot,
#[serde(with = "serde_utils::quoted_u64")]
pub custody_group_count: u64,
#[serde(with = "serde_utils::quoted_u64_vec")]
pub custody_columns: Vec<u64>,
}

View File

@@ -15,6 +15,10 @@ pub enum SyncState {
/// specified by its peers. Once completed, the node enters this sync state and attempts to
/// download all required historical blocks.
BackFillSyncing { completed: usize, remaining: usize },
/// The node is undertaking a custody backfill sync. This occurs for a node that has completed forward and
/// backfill sync and has undergone a custody count change. During custody backfill sync the node attempts
/// to backfill its new column custody requirements up to the data availability window.
CustodyBackFillSyncing { completed: usize, remaining: usize },
/// The node has completed syncing a finalized chain and is in the process of re-evaluating
/// which sync state to progress to.
SyncTransition,
@@ -39,6 +43,17 @@ pub enum BackFillState {
Failed,
}
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
/// The state of the custody backfill sync.
pub enum CustodyBackFillState {
/// We are currently backfilling custody columns.
Syncing,
/// A custody backfill sync has completed.
Completed,
/// A custody sync should is set to Pending for various reasons.
Pending(String),
}
impl PartialEq for SyncState {
fn eq(&self, other: &Self) -> bool {
matches!(
@@ -54,6 +69,10 @@ impl PartialEq for SyncState {
SyncState::BackFillSyncing { .. },
SyncState::BackFillSyncing { .. }
)
| (
SyncState::CustodyBackFillSyncing { .. },
SyncState::CustodyBackFillSyncing { .. }
)
)
}
}
@@ -65,8 +84,8 @@ impl SyncState {
SyncState::SyncingFinalized { .. } => true,
SyncState::SyncingHead { .. } => true,
SyncState::SyncTransition => true,
// Backfill doesn't effect any logic, we consider this state, not syncing.
SyncState::BackFillSyncing { .. } => false,
// Both backfill and custody backfill don't effect any logic, we consider this state, not syncing.
SyncState::BackFillSyncing { .. } | SyncState::CustodyBackFillSyncing { .. } => false,
SyncState::Synced => false,
SyncState::Stalled => false,
}
@@ -77,7 +96,7 @@ impl SyncState {
SyncState::SyncingFinalized { .. } => true,
SyncState::SyncingHead { .. } => false,
SyncState::SyncTransition => false,
SyncState::BackFillSyncing { .. } => false,
SyncState::BackFillSyncing { .. } | SyncState::CustodyBackFillSyncing { .. } => false,
SyncState::Synced => false,
SyncState::Stalled => false,
}
@@ -87,7 +106,12 @@ impl SyncState {
///
/// NOTE: We consider the node synced if it is fetching old historical blocks.
pub fn is_synced(&self) -> bool {
matches!(self, SyncState::Synced | SyncState::BackFillSyncing { .. })
matches!(
self,
SyncState::Synced
| SyncState::BackFillSyncing { .. }
| SyncState::CustodyBackFillSyncing { .. }
)
}
/// Returns true if the node is *stalled*, i.e. has no synced peers.
@@ -108,6 +132,9 @@ impl std::fmt::Display for SyncState {
SyncState::Stalled => write!(f, "Stalled"),
SyncState::SyncTransition => write!(f, "Evaluating known peers"),
SyncState::BackFillSyncing { .. } => write!(f, "Syncing Historical Blocks"),
SyncState::CustodyBackFillSyncing { .. } => {
write!(f, "Syncing Historical Data Columns")
}
}
}
}

View File

@@ -1,11 +1,12 @@
use super::types::*;
use crate::Error;
use crate::{Error, success_or_error};
use bls::PublicKeyBytes;
use reqwest::{
header::{HeaderMap, HeaderValue},
IntoUrl,
header::{HeaderMap, HeaderValue},
};
use sensitive_url::SensitiveUrl;
use serde::{de::DeserializeOwned, Serialize};
use serde::{Serialize, de::DeserializeOwned};
use std::fmt::{self, Display};
use std::fs;
use std::path::Path;
@@ -145,7 +146,7 @@ impl ValidatorClientHttpClient {
.send()
.await
.map_err(Error::from)?;
ok_or_error(response).await
success_or_error(response).await
}
/// Perform a HTTP DELETE request, returning the `Response` for further processing.
@@ -157,7 +158,7 @@ impl ValidatorClientHttpClient {
.send()
.await
.map_err(Error::from)?;
ok_or_error(response).await
success_or_error(response).await
}
async fn get<T: DeserializeOwned, U: IntoUrl>(&self, url: U) -> Result<T, Error> {
@@ -218,7 +219,7 @@ impl ValidatorClientHttpClient {
.send()
.await
.map_err(Error::from)?;
ok_or_error(response).await
success_or_error(response).await
}
async fn post<T: Serialize, U: IntoUrl, V: DeserializeOwned>(
@@ -250,7 +251,7 @@ impl ValidatorClientHttpClient {
.send()
.await
.map_err(Error::from)?;
ok_or_error(response).await?;
success_or_error(response).await?;
Ok(())
}
@@ -268,7 +269,7 @@ impl ValidatorClientHttpClient {
.send()
.await
.map_err(Error::from)?;
ok_or_error(response).await
success_or_error(response).await
}
/// Perform a HTTP DELETE request.
@@ -283,7 +284,7 @@ impl ValidatorClientHttpClient {
/// `GET lighthouse/version`
pub async fn get_lighthouse_version(&self) -> Result<GenericResponse<VersionData>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -295,7 +296,7 @@ impl ValidatorClientHttpClient {
/// `GET lighthouse/health`
pub async fn get_lighthouse_health(&self) -> Result<GenericResponse<Health>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -309,7 +310,7 @@ impl ValidatorClientHttpClient {
pub async fn get_lighthouse_spec<T: Serialize + DeserializeOwned>(
&self,
) -> Result<GenericResponse<T>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -323,7 +324,7 @@ impl ValidatorClientHttpClient {
pub async fn get_lighthouse_validators(
&self,
) -> Result<GenericResponse<Vec<ValidatorData>>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -338,7 +339,7 @@ impl ValidatorClientHttpClient {
&self,
validator_pubkey: &PublicKeyBytes,
) -> Result<Option<GenericResponse<ValidatorData>>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -354,7 +355,7 @@ impl ValidatorClientHttpClient {
&self,
validators: Vec<ValidatorRequest>,
) -> Result<GenericResponse<PostValidatorsResponseData>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -369,7 +370,7 @@ impl ValidatorClientHttpClient {
&self,
request: &CreateValidatorsMnemonicRequest,
) -> Result<GenericResponse<Vec<CreatedValidator>>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -385,7 +386,7 @@ impl ValidatorClientHttpClient {
&self,
request: &KeystoreValidatorsPostRequest,
) -> Result<GenericResponse<ValidatorData>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -401,7 +402,7 @@ impl ValidatorClientHttpClient {
&self,
request: &[Web3SignerValidatorRequest],
) -> Result<(), Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -424,7 +425,7 @@ impl ValidatorClientHttpClient {
prefer_builder_proposals: Option<bool>,
graffiti: Option<GraffitiString>,
) -> Result<(), Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -451,7 +452,7 @@ impl ValidatorClientHttpClient {
&self,
req: &DeleteKeystoresRequest,
) -> Result<ExportKeystoresResponse, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -462,7 +463,7 @@ impl ValidatorClientHttpClient {
}
fn make_keystores_url(&self) -> Result<Url, Error> {
let mut url = self.server.full.clone();
let mut url = self.server.expose_full().clone();
url.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("eth")
@@ -472,7 +473,7 @@ impl ValidatorClientHttpClient {
}
fn make_remotekeys_url(&self) -> Result<Url, Error> {
let mut url = self.server.full.clone();
let mut url = self.server.expose_full().clone();
url.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("eth")
@@ -482,7 +483,7 @@ impl ValidatorClientHttpClient {
}
fn make_fee_recipient_url(&self, pubkey: &PublicKeyBytes) -> Result<Url, Error> {
let mut url = self.server.full.clone();
let mut url = self.server.expose_full().clone();
url.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("eth")
@@ -494,7 +495,7 @@ impl ValidatorClientHttpClient {
}
fn make_graffiti_url(&self, pubkey: &PublicKeyBytes) -> Result<Url, Error> {
let mut url = self.server.full.clone();
let mut url = self.server.expose_full().clone();
url.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("eth")
@@ -506,7 +507,7 @@ impl ValidatorClientHttpClient {
}
fn make_gas_limit_url(&self, pubkey: &PublicKeyBytes) -> Result<Url, Error> {
let mut url = self.server.full.clone();
let mut url = self.server.expose_full().clone();
url.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("eth")
@@ -519,7 +520,7 @@ impl ValidatorClientHttpClient {
/// `GET lighthouse/auth`
pub async fn get_auth(&self) -> Result<AuthResponse, Error> {
let mut url = self.server.full.clone();
let mut url = self.server.expose_full().clone();
url.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("lighthouse")
@@ -635,7 +636,7 @@ impl ValidatorClientHttpClient {
pubkey: &PublicKeyBytes,
epoch: Option<Epoch>,
) -> Result<GenericResponse<SignedVoluntaryExit>, Error> {
let mut path = self.server.full.clone();
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
@@ -681,20 +682,3 @@ impl ValidatorClientHttpClient {
self.delete(url).await
}
}
/// Returns `Ok(response)` if the response is a `200 OK` response or a
/// `202 Accepted` response. Otherwise, creates an appropriate error message.
async fn ok_or_error(response: Response) -> Result<Response, Error> {
let status = response.status();
if status == StatusCode::OK
|| status == StatusCode::ACCEPTED
|| status == StatusCode::NO_CONTENT
{
Ok(response)
} else if let Ok(message) = response.json().await {
Err(Error::ServerMessage(message))
} else {
Err(Error::StatusCode(status))
}
}

View File

@@ -1,9 +1,10 @@
use bls::PublicKeyBytes;
use eth2_keystore::Keystore;
use serde::{Deserialize, Serialize};
use types::{Address, Graffiti, PublicKeyBytes};
use types::{Address, Graffiti};
use zeroize::Zeroizing;
pub use slashing_protection::interchange::Interchange;
pub use eip_3076::Interchange;
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct GetFeeRecipientResponse {

View File

@@ -1,8 +1,8 @@
pub use crate::lighthouse::Health;
pub use crate::lighthouse_vc::std_types::*;
pub use crate::types::{GenericResponse, VersionData};
use bls::{PublicKey, PublicKeyBytes};
use eth2_keystore::Keystore;
use graffiti::GraffitiString;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub use types::*;
@@ -197,3 +197,13 @@ pub struct SingleExportKeystoresResponse {
pub struct SetGraffitiRequest {
pub graffiti: GraffitiString,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct UpdateCandidatesRequest {
pub beacon_nodes: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct UpdateCandidatesResponse {
pub new_beacon_nodes_list: Vec<String>,
}

View File

@@ -1,5 +1,5 @@
use crate::{types::Accept, Error, CONSENSUS_VERSION_HEADER};
use reqwest::{header::ACCEPT, RequestBuilder, Response, StatusCode};
use crate::{CONSENSUS_VERSION_HEADER, Error, types::Accept};
use reqwest::{RequestBuilder, Response, StatusCode, header::ACCEPT};
use std::str::FromStr;
use types::ForkName;

View File

@@ -1,70 +1,43 @@
//! This module exposes a superset of the `types` crate. It adds additional types that are only
//! required for the HTTP API.
pub use types::*;
use crate::{
Error as ServerError, CONSENSUS_BLOCK_VALUE_HEADER, CONSENSUS_VERSION_HEADER,
EXECUTION_PAYLOAD_BLINDED_HEADER, EXECUTION_PAYLOAD_VALUE_HEADER,
CONSENSUS_BLOCK_VALUE_HEADER, CONSENSUS_VERSION_HEADER, EXECUTION_PAYLOAD_BLINDED_HEADER,
EXECUTION_PAYLOAD_VALUE_HEADER, Error as ServerError,
};
use enr::{CombinedKey, Enr};
use mediatype::{names, MediaType, MediaTypeList};
use multiaddr::Multiaddr;
use bls::{PublicKeyBytes, SecretKey, Signature, SignatureBytes};
use context_deserialize::ContextDeserialize;
use mediatype::{MediaType, MediaTypeList, names};
use reqwest::header::HeaderMap;
use serde::{Deserialize, Deserializer, Serialize};
use serde_utils::quoted_u64::Quoted;
use ssz::Encode;
use ssz::{Decode, DecodeError};
use ssz_derive::{Decode, Encode};
use std::fmt::{self, Display};
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use superstruct::superstruct;
#[cfg(test)]
use test_random_derive::TestRandom;
use types::beacon_block_body::KzgCommitments;
#[cfg(test)]
use types::test_utils::TestRandom;
pub use types::*;
// TODO(mac): Temporary module and re-export hack to expose old `consensus/types` via `eth2/types`.
pub use crate::beacon_response::*;
pub mod beacon_response {
pub use crate::beacon_response::*;
}
#[cfg(feature = "lighthouse")]
use crate::lighthouse::BlockReward;
/// An API error serializable to JSON.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Error {
Indexed(IndexedErrorMessage),
Message(ErrorMessage),
}
/// An API error serializable to JSON.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ErrorMessage {
pub code: u16,
pub message: String,
#[serde(default)]
pub stacktraces: Vec<String>,
}
/// An indexed API error serializable to JSON.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IndexedErrorMessage {
pub code: u16,
pub message: String,
pub failures: Vec<Failure>,
}
/// A single failure in an index of API errors, serializable to JSON.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Failure {
pub index: u64,
pub message: String,
}
impl Failure {
pub fn new(index: usize, message: String) -> Self {
Self {
index: index as u64,
message,
}
}
}
// Re-export error types from the unified error module
pub use crate::error::{ErrorMessage, Failure, IndexedErrorMessage, ResponseError as Error};
/// The version of a single API endpoint, e.g. the `v1` in `/eth/v1/beacon/blocks`.
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -349,6 +322,14 @@ pub struct ValidatorBalanceData {
pub balance: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ValidatorIdentityData {
#[serde(with = "serde_utils::quoted_u64")]
pub index: u64,
pub pubkey: PublicKeyBytes,
pub activation_epoch: Epoch,
}
// Implemented according to what is described here:
//
// https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ
@@ -357,7 +338,7 @@ pub struct ValidatorBalanceData {
// this proposal:
//
// https://hackmd.io/bQxMDRt1RbS1TLno8K4NPg?view
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ValidatorStatus {
PendingInitialized,
@@ -581,9 +562,9 @@ pub struct ChainHeadData {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IdentityData {
pub peer_id: String,
pub enr: Enr<CombinedKey>,
pub p2p_addresses: Vec<Multiaddr>,
pub discovery_addresses: Vec<Multiaddr>,
pub enr: String,
pub p2p_addresses: Vec<String>,
pub discovery_addresses: Vec<String>,
pub metadata: MetaData,
}
@@ -694,6 +675,12 @@ pub struct ValidatorBalancesRequestBody {
pub ids: Vec<ValidatorId>,
}
#[derive(Clone, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ValidatorIdentitiesRequestBody {
pub ids: Vec<ValidatorId>,
}
#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BlobIndicesQuery {
@@ -701,6 +688,20 @@ pub struct BlobIndicesQuery {
pub indices: Option<Vec<u64>>,
}
#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BlobsVersionedHashesQuery {
#[serde(default, deserialize_with = "option_query_vec")]
pub versioned_hashes: Option<Vec<Hash256>>,
}
#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DataColumnIndicesQuery {
#[serde(default, deserialize_with = "option_query_vec")]
pub indices: Option<Vec<u64>>,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ValidatorIndexData(#[serde(with = "serde_utils::quoted_u64_vec")] pub Vec<u64>);
@@ -751,12 +752,20 @@ pub struct ProposerData {
pub slot: Slot,
}
#[derive(Clone, Copy, Serialize, Deserialize, Default, Debug)]
pub enum GraffitiPolicy {
#[default]
PreserveUserGraffiti,
AppendClientVersions,
}
#[derive(Clone, Deserialize)]
pub struct ValidatorBlocksQuery {
pub randao_reveal: SignatureBytes,
pub graffiti: Option<Graffiti>,
pub skip_randao_verification: SkipRandaoVerification,
pub builder_boost_factor: Option<u64>,
pub graffiti_policy: Option<GraffitiPolicy>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
@@ -807,16 +816,32 @@ pub struct LightClientUpdatesQuery {
pub count: u64,
}
#[derive(Encode, Decode)]
pub struct LightClientUpdateResponseChunk {
pub struct LightClientUpdateResponseChunk<E: EthSpec> {
pub response_chunk_len: u64,
pub response_chunk: LightClientUpdateResponseChunkInner,
pub response_chunk: LightClientUpdateResponseChunkInner<E>,
}
#[derive(Encode, Decode)]
pub struct LightClientUpdateResponseChunkInner {
impl<E: EthSpec> Encode for LightClientUpdateResponseChunk<E> {
fn is_ssz_fixed_len() -> bool {
false
}
fn ssz_bytes_len(&self) -> usize {
0_u64.ssz_bytes_len()
+ self.response_chunk.context.len()
+ self.response_chunk.payload.ssz_bytes_len()
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.response_chunk_len.to_le_bytes());
buf.extend_from_slice(&self.response_chunk.context);
self.response_chunk.payload.ssz_append(buf);
}
}
pub struct LightClientUpdateResponseChunkInner<E: EthSpec> {
pub context: [u8; 4],
pub payload: Vec<u8>,
pub payload: LightClientUpdate<E>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
@@ -934,6 +959,23 @@ pub struct PeerCount {
pub disconnecting: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BeaconCommitteeSelection {
#[serde(with = "serde_utils::quoted_u64")]
pub validator_index: u64,
pub slot: Slot,
pub selection_proof: Signature,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SyncCommitteeSelection {
#[serde(with = "serde_utils::quoted_u64")]
pub validator_index: u64,
pub slot: Slot,
#[serde(with = "serde_utils::quoted_u64")]
pub subcommittee_index: u64,
pub selection_proof: Signature,
}
// --------- Server Sent Event Types -----------
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
@@ -965,6 +1007,35 @@ impl SseBlobSidecar {
}
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct SseDataColumnSidecar {
pub block_root: Hash256,
#[serde(with = "serde_utils::quoted_u64")]
pub index: u64,
pub slot: Slot,
pub kzg_commitments: Vec<KzgCommitment>,
pub versioned_hashes: Vec<VersionedHash>,
}
impl SseDataColumnSidecar {
pub fn from_data_column_sidecar<E: EthSpec>(
data_column_sidecar: &DataColumnSidecar<E>,
) -> SseDataColumnSidecar {
let kzg_commitments = data_column_sidecar.kzg_commitments.to_vec();
let versioned_hashes = kzg_commitments
.iter()
.map(|c| c.calculate_versioned_hash())
.collect();
SseDataColumnSidecar {
block_root: data_column_sidecar.block_root(),
index: data_column_sidecar.index,
slot: data_column_sidecar.slot(),
kzg_commitments,
versioned_hashes,
}
}
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct SseFinalizedCheckpoint {
pub block: Hash256,
@@ -1077,7 +1148,7 @@ impl<'de> ContextDeserialize<'de, ForkName> for SsePayloadAttributes {
return Err(serde::de::Error::custom(format!(
"SsePayloadAttributes failed to deserialize: unsupported fork '{}'",
context
)))
)));
}
ForkName::Bellatrix => {
Self::V1(Deserialize::deserialize(deserializer).map_err(convert_err)?)
@@ -1085,7 +1156,11 @@ impl<'de> ContextDeserialize<'de, ForkName> for SsePayloadAttributes {
ForkName::Capella => {
Self::V2(Deserialize::deserialize(deserializer).map_err(convert_err)?)
}
ForkName::Deneb | ForkName::Electra | ForkName::Eip7805 | ForkName::Fulu => {
ForkName::Deneb
| ForkName::Electra
| ForkName::Fulu
| ForkName::Eip7805
| ForkName::Gloas => {
Self::V3(Deserialize::deserialize(deserializer).map_err(convert_err)?)
}
})
@@ -1122,6 +1197,7 @@ pub enum EventKind<E: EthSpec> {
SingleAttestation(Box<SingleAttestation>),
Block(SseBlock),
BlobSidecar(SseBlobSidecar),
DataColumnSidecar(SseDataColumnSidecar),
FinalizedCheckpoint(SseFinalizedCheckpoint),
Head(SseHead),
VoluntaryExit(SignedVoluntaryExit),
@@ -1146,6 +1222,7 @@ impl<E: EthSpec> EventKind<E> {
EventKind::Head(_) => "head",
EventKind::Block(_) => "block",
EventKind::BlobSidecar(_) => "blob_sidecar",
EventKind::DataColumnSidecar(_) => "data_column_sidecar",
EventKind::Attestation(_) => "attestation",
EventKind::SingleAttestation(_) => "single_attestation",
EventKind::VoluntaryExit(_) => "voluntary_exit",
@@ -1182,6 +1259,11 @@ impl<E: EthSpec> EventKind<E> {
"blob_sidecar" => Ok(EventKind::BlobSidecar(serde_json::from_str(data).map_err(
|e| ServerError::InvalidServerSentEvent(format!("Blob Sidecar: {:?}", e)),
)?)),
"data_column_sidecar" => Ok(EventKind::DataColumnSidecar(
serde_json::from_str(data).map_err(|e| {
ServerError::InvalidServerSentEvent(format!("Data Column Sidecar: {:?}", e))
})?,
)),
"chain_reorg" => Ok(EventKind::ChainReorg(serde_json::from_str(data).map_err(
|e| ServerError::InvalidServerSentEvent(format!("Chain Reorg: {:?}", e)),
)?)),
@@ -1276,6 +1358,7 @@ pub enum EventTopic {
Head,
Block,
BlobSidecar,
DataColumnSidecar,
Attestation,
SingleAttestation,
VoluntaryExit,
@@ -1303,6 +1386,7 @@ impl FromStr for EventTopic {
"head" => Ok(EventTopic::Head),
"block" => Ok(EventTopic::Block),
"blob_sidecar" => Ok(EventTopic::BlobSidecar),
"data_column_sidecar" => Ok(EventTopic::DataColumnSidecar),
"attestation" => Ok(EventTopic::Attestation),
"single_attestation" => Ok(EventTopic::SingleAttestation),
"voluntary_exit" => Ok(EventTopic::VoluntaryExit),
@@ -1331,6 +1415,7 @@ impl fmt::Display for EventTopic {
EventTopic::Head => write!(f, "head"),
EventTopic::Block => write!(f, "block"),
EventTopic::BlobSidecar => write!(f, "blob_sidecar"),
EventTopic::DataColumnSidecar => write!(f, "data_column_sidecar"),
EventTopic::Attestation => write!(f, "attestation"),
EventTopic::SingleAttestation => write!(f, "single_attestation"),
EventTopic::VoluntaryExit => write!(f, "voluntary_exit"),
@@ -1479,22 +1564,32 @@ pub struct ForkChoiceNode {
pub weight: u64,
pub validity: Option<String>,
pub execution_block_hash: Option<Hash256>,
pub extra_data: ForkChoiceExtraData,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ForkChoiceExtraData {
pub target_root: Hash256,
pub justified_root: Hash256,
pub finalized_root: Hash256,
pub unrealized_justified_root: Option<Hash256>,
pub unrealized_finalized_root: Option<Hash256>,
pub unrealized_justified_epoch: Option<Epoch>,
pub unrealized_finalized_epoch: Option<Epoch>,
pub execution_status: String,
pub best_child: Option<Hash256>,
pub best_descendant: Option<Hash256>,
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BroadcastValidation {
#[default]
Gossip,
Consensus,
ConsensusAndEquivocation,
}
impl Default for BroadcastValidation {
fn default() -> Self {
Self::Gossip
}
}
impl Display for BroadcastValidation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -1527,7 +1622,7 @@ pub struct BroadcastValidationQuery {
pub mod serde_status_code {
use crate::StatusCode;
use serde::{de::Error, Deserialize, Serialize};
use serde::{Deserialize, Serialize, de::Error};
pub fn serialize<S>(status_code: &StatusCode, ser: S) -> Result<S::Ok, S::Error>
where
@@ -1611,8 +1706,8 @@ mod tests {
BeaconBlock::<E>::Deneb(BeaconBlockDeneb::empty(&spec)),
Signature::empty(),
);
let blobs = BlobsList::<E>::from(vec![Blob::<E>::default()]);
let kzg_proofs = KzgProofs::<E>::from(vec![KzgProof::empty()]);
let blobs = BlobsList::<E>::try_from(vec![Blob::<E>::default()]).unwrap();
let kzg_proofs = KzgProofs::<E>::try_from(vec![KzgProof::empty()]).unwrap();
let signed_block_contents =
PublishBlockRequest::new(Arc::new(block), Some((kzg_proofs, blobs)));
@@ -2152,7 +2247,8 @@ pub enum ContentType {
Ssz,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)]
#[cfg_attr(test, derive(TestRandom))]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Encode, Decode)]
#[serde(bound = "E: EthSpec")]
pub struct BlobsBundle<E: EthSpec> {
pub commitments: KzgCommitments<E>,
@@ -2245,6 +2341,14 @@ pub struct StandardAttestationRewards {
pub total_rewards: Vec<TotalAttestationRewards>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
#[serde(bound = "E: EthSpec")]
#[serde(transparent)]
pub struct BlobWrapper<E: EthSpec> {
#[serde(with = "ssz_types::serde_utils::hex_fixed_vec")]
pub blob: Blob<E>,
}
#[cfg(test)]
mod test {
use std::fmt::Debug;
@@ -2353,6 +2457,9 @@ mod test {
rng,
)),
ExecutionPayload::Fulu(ExecutionPayloadFulu::<MainnetEthSpec>::random_for_test(rng)),
ExecutionPayload::Gloas(ExecutionPayloadGloas::<MainnetEthSpec>::random_for_test(
rng,
)),
];
let merged_forks = &ForkName::list_all()[2..];
assert_eq!(
@@ -2418,6 +2525,17 @@ mod test {
blobs_bundle,
}
},
{
let execution_payload =
ExecutionPayload::Gloas(
ExecutionPayloadGloas::<MainnetEthSpec>::random_for_test(rng),
);
let blobs_bundle = BlobsBundle::random_for_test(rng);
ExecutionPayloadAndBlobs {
execution_payload,
blobs_bundle,
}
},
];
let blob_forks = &ForkName::list_all()[4..];