mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 12:47:05 +00:00
Resolve merge conflicts
This commit is contained in:
@@ -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 = []
|
||||
|
||||
257
common/eth2/src/beacon_response.rs
Normal file
257
common/eth2/src/beacon_response.rs
Normal 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
167
common/eth2/src/error.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()))?
|
||||
|
||||
11
common/eth2/src/lighthouse/custody.rs
Normal file
11
common/eth2/src/lighthouse/custody.rs
Normal 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>,
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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..];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user