mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-09 03:17:55 +00:00
Merge remote-tracking branch 'origin/unstable' into dvt
This commit is contained in:
@@ -4,6 +4,10 @@ version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["lighthouse"]
|
||||
lighthouse = []
|
||||
|
||||
[dependencies]
|
||||
derivative = { workspace = true }
|
||||
either = { workspace = true }
|
||||
@@ -33,7 +37,3 @@ zeroize = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["lighthouse"]
|
||||
lighthouse = []
|
||||
|
||||
@@ -16,9 +16,8 @@ pub mod types;
|
||||
|
||||
use self::mixin::{RequestAccept, ResponseOptional};
|
||||
use self::types::{Error as ResponseError, *};
|
||||
use ::types::fork_versioned_response::ExecutionOptimisticFinalizedForkVersionedResponse;
|
||||
use ::types::beacon_response::ExecutionOptimisticFinalizedBeaconResponse;
|
||||
use derivative::Derivative;
|
||||
use either::Either;
|
||||
use futures::Stream;
|
||||
use futures_util::StreamExt;
|
||||
use libp2p_identity::PeerId;
|
||||
@@ -51,12 +50,28 @@ pub const CONTENT_TYPE_HEADER: &str = "Content-Type";
|
||||
pub const SSZ_CONTENT_TYPE_HEADER: &str = "application/octet-stream";
|
||||
pub const JSON_CONTENT_TYPE_HEADER: &str = "application/json";
|
||||
|
||||
/// Specific optimized timeout constants for HTTP requests involved in different validator duties.
|
||||
/// This can help ensure that proper endpoint fallback occurs.
|
||||
const HTTP_ATTESTATION_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_ATTESTER_DUTIES_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_ATTESTATION_SUBSCRIPTIONS_TIMEOUT_QUOTIENT: u32 = 24;
|
||||
const HTTP_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_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT: u32 = 4;
|
||||
const HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT: u32 = 4;
|
||||
const HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
const HTTP_DEFAULT_TIMEOUT_QUOTIENT: u32 = 4;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// The `reqwest` client raised an error.
|
||||
HttpClient(PrettyReqwestError),
|
||||
/// The `reqwest_eventsource` client raised an error.
|
||||
SseClient(reqwest_eventsource::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.
|
||||
@@ -99,7 +114,7 @@ impl Error {
|
||||
match self {
|
||||
Error::HttpClient(error) => error.inner().status(),
|
||||
Error::SseClient(error) => {
|
||||
if let reqwest_eventsource::Error::InvalidStatusCode(status, _) = error {
|
||||
if let reqwest_eventsource::Error::InvalidStatusCode(status, _) = error.as_ref() {
|
||||
Some(*status)
|
||||
} else {
|
||||
None
|
||||
@@ -169,6 +184,26 @@ 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,
|
||||
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,
|
||||
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
|
||||
@@ -287,6 +322,54 @@ impl BeaconNodeHttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_fork_contextual<T, U, Ctx, Meta>(
|
||||
&self,
|
||||
url: U,
|
||||
ctx_constructor: impl Fn(ForkName) -> Ctx,
|
||||
) -> Result<Option<ForkVersionedResponse<T, Meta>>, Error>
|
||||
where
|
||||
U: IntoUrl,
|
||||
T: ContextDeserialize<'static, Ctx>,
|
||||
Meta: DeserializeOwned,
|
||||
Ctx: Clone,
|
||||
{
|
||||
let response = self
|
||||
.get_response(url, |b| b.accept(Accept::Json))
|
||||
.await
|
||||
.optional()?;
|
||||
|
||||
let Some(resp) = response else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let bytes = resp.bytes().await?;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct Helper {
|
||||
// TODO: remove this default once checkpointz follows the spec
|
||||
#[serde(default = "ForkName::latest_stable")]
|
||||
version: ForkName,
|
||||
#[serde(flatten)]
|
||||
metadata: serde_json::Value,
|
||||
data: serde_json::Value,
|
||||
}
|
||||
|
||||
let helper: Helper = serde_json::from_slice(&bytes).map_err(Error::InvalidJson)?;
|
||||
|
||||
let metadata: Meta = serde_json::from_value(helper.metadata).map_err(Error::InvalidJson)?;
|
||||
|
||||
let ctx = ctx_constructor(helper.version);
|
||||
|
||||
let data: T = ContextDeserialize::context_deserialize(helper.data, ctx)
|
||||
.map_err(Error::InvalidJson)?;
|
||||
|
||||
Ok(Some(ForkVersionedResponse {
|
||||
version: helper.version,
|
||||
metadata,
|
||||
data,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Perform a HTTP GET request using an 'accept' header, returning `None` on a 404 error.
|
||||
pub async fn get_bytes_opt_accept_header<U: IntoUrl>(
|
||||
&self,
|
||||
@@ -622,6 +705,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.
|
||||
@@ -854,7 +960,7 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
start_period: u64,
|
||||
count: u64,
|
||||
) -> Result<Option<Vec<ForkVersionedResponse<LightClientUpdate<E>>>>, Error> {
|
||||
) -> Result<Option<Vec<BeaconResponse<LightClientUpdate<E>>>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -869,7 +975,14 @@ impl BeaconNodeHttpClient {
|
||||
path.query_pairs_mut()
|
||||
.append_pair("count", &count.to_string());
|
||||
|
||||
self.get_opt(path).await
|
||||
self.get_opt(path).await.map(|opt| {
|
||||
opt.map(|updates: Vec<_>| {
|
||||
updates
|
||||
.into_iter()
|
||||
.map(BeaconResponse::ForkVersioned)
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// `GET beacon/light_client/bootstrap`
|
||||
@@ -878,7 +991,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_light_client_bootstrap<E: EthSpec>(
|
||||
&self,
|
||||
block_root: Hash256,
|
||||
) -> Result<Option<ForkVersionedResponse<LightClientBootstrap<E>>>, Error> {
|
||||
) -> Result<Option<BeaconResponse<LightClientBootstrap<E>>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -888,7 +1001,9 @@ impl BeaconNodeHttpClient {
|
||||
.push("bootstrap")
|
||||
.push(&format!("{:?}", block_root));
|
||||
|
||||
self.get_opt(path).await
|
||||
self.get_opt(path)
|
||||
.await
|
||||
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
|
||||
}
|
||||
|
||||
/// `GET beacon/light_client/optimistic_update`
|
||||
@@ -896,7 +1011,7 @@ impl BeaconNodeHttpClient {
|
||||
/// Returns `Ok(None)` on a 404 error.
|
||||
pub async fn get_beacon_light_client_optimistic_update<E: EthSpec>(
|
||||
&self,
|
||||
) -> Result<Option<ForkVersionedResponse<LightClientOptimisticUpdate<E>>>, Error> {
|
||||
) -> Result<Option<BeaconResponse<LightClientOptimisticUpdate<E>>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -905,7 +1020,9 @@ impl BeaconNodeHttpClient {
|
||||
.push("light_client")
|
||||
.push("optimistic_update");
|
||||
|
||||
self.get_opt(path).await
|
||||
self.get_opt(path)
|
||||
.await
|
||||
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
|
||||
}
|
||||
|
||||
/// `GET beacon/light_client/finality_update`
|
||||
@@ -913,7 +1030,7 @@ impl BeaconNodeHttpClient {
|
||||
/// Returns `Ok(None)` on a 404 error.
|
||||
pub async fn get_beacon_light_client_finality_update<E: EthSpec>(
|
||||
&self,
|
||||
) -> Result<Option<ForkVersionedResponse<LightClientFinalityUpdate<E>>>, Error> {
|
||||
) -> Result<Option<BeaconResponse<LightClientFinalityUpdate<E>>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -922,7 +1039,9 @@ impl BeaconNodeHttpClient {
|
||||
.push("light_client")
|
||||
.push("finality_update");
|
||||
|
||||
self.get_opt(path).await
|
||||
self.get_opt(path)
|
||||
.await
|
||||
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
|
||||
}
|
||||
|
||||
/// `GET beacon/headers?slot,parent_root`
|
||||
@@ -985,8 +1104,14 @@ impl BeaconNodeHttpClient {
|
||||
.push("beacon")
|
||||
.push("blocks");
|
||||
|
||||
self.post_with_timeout(path, block_contents, self.timeouts.proposal)
|
||||
.await?;
|
||||
let fork_name = block_contents.signed_block().fork_name_unchecked();
|
||||
self.post_generic_with_consensus_version(
|
||||
path,
|
||||
block_contents,
|
||||
Some(self.timeouts.proposal),
|
||||
fork_name,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1204,16 +1329,12 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_beacon_blocks<E: EthSpec>(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<
|
||||
Option<ExecutionOptimisticFinalizedForkVersionedResponse<SignedBeaconBlock<E>>>,
|
||||
Error,
|
||||
> {
|
||||
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<SignedBeaconBlock<E>>>, Error>
|
||||
{
|
||||
let path = self.get_beacon_blocks_path(block_id)?;
|
||||
let Some(response) = self.get_response(path, |b| b).await.optional()? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(response.json().await?))
|
||||
self.get_opt(path)
|
||||
.await
|
||||
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
|
||||
}
|
||||
|
||||
/// `GET v1/beacon/blob_sidecars/{block_id}`
|
||||
@@ -1223,8 +1344,8 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
indices: Option<&[u64]>,
|
||||
) -> Result<Option<ExecutionOptimisticFinalizedForkVersionedResponse<BlobSidecarList<E>>>, Error>
|
||||
{
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<BlobSidecarList<E>>>, Error> {
|
||||
let mut path = self.get_blobs_path(block_id)?;
|
||||
if let Some(indices) = indices {
|
||||
let indices_string = indices
|
||||
@@ -1236,11 +1357,13 @@ impl BeaconNodeHttpClient {
|
||||
.append_pair("indices", &indices_string);
|
||||
}
|
||||
|
||||
let Some(response) = self.get_response(path, |b| b).await.optional()? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(response.json().await?))
|
||||
self.get_fork_contextual(path, |fork| {
|
||||
// TODO(EIP-7892): this will overestimate the max number of blobs
|
||||
// It would be better if we could get an epoch passed into this function
|
||||
(fork, spec.max_blobs_per_block_within_fork(fork) as usize)
|
||||
})
|
||||
.await
|
||||
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
|
||||
}
|
||||
|
||||
/// `GET v1/beacon/blinded_blocks/{block_id}`
|
||||
@@ -1250,15 +1373,13 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<
|
||||
Option<ExecutionOptimisticFinalizedForkVersionedResponse<SignedBlindedBeaconBlock<E>>>,
|
||||
Option<ExecutionOptimisticFinalizedBeaconResponse<SignedBlindedBeaconBlock<E>>>,
|
||||
Error,
|
||||
> {
|
||||
let path = self.get_beacon_blinded_blocks_path(block_id)?;
|
||||
let Some(response) = self.get_response(path, |b| b).await.optional()? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(response.json().await?))
|
||||
self.get_opt(path)
|
||||
.await
|
||||
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
|
||||
}
|
||||
|
||||
/// `GET v1/beacon/blocks` (LEGACY)
|
||||
@@ -1267,7 +1388,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_beacon_blocks_v1<E: EthSpec>(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<Option<ForkVersionedResponse<SignedBeaconBlock<E>>>, Error> {
|
||||
) -> Result<Option<BeaconResponse<SignedBeaconBlock<E>>>, Error> {
|
||||
let mut path = self.eth_path(V1)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -1276,7 +1397,9 @@ impl BeaconNodeHttpClient {
|
||||
.push("blocks")
|
||||
.push(&block_id.to_string());
|
||||
|
||||
self.get_opt(path).await
|
||||
self.get_opt(path)
|
||||
.await
|
||||
.map(|opt| opt.map(BeaconResponse::Unversioned))
|
||||
}
|
||||
|
||||
/// `GET beacon/blocks` as SSZ
|
||||
@@ -1357,7 +1480,7 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_beacon_blocks_attestations_v2<E: EthSpec>(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<Option<ExecutionOptimisticFinalizedForkVersionedResponse<Vec<Attestation<E>>>>, Error>
|
||||
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<Vec<Attestation<E>>>>, Error>
|
||||
{
|
||||
let mut path = self.eth_path(V2)?;
|
||||
|
||||
@@ -1368,32 +1491,15 @@ impl BeaconNodeHttpClient {
|
||||
.push(&block_id.to_string())
|
||||
.push("attestations");
|
||||
|
||||
self.get_opt(path).await
|
||||
}
|
||||
|
||||
/// `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(())
|
||||
self.get_opt(path)
|
||||
.await
|
||||
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
|
||||
}
|
||||
|
||||
/// `POST v2/beacon/pool/attestations`
|
||||
pub async fn post_beacon_pool_attestations_v2<E: EthSpec>(
|
||||
&self,
|
||||
attestations: Either<Vec<Attestation<E>>, Vec<SingleAttestation>>,
|
||||
attestations: Vec<SingleAttestation>,
|
||||
fork_name: ForkName,
|
||||
) -> Result<(), Error> {
|
||||
let mut path = self.eth_path(V2)?;
|
||||
@@ -1404,26 +1510,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(())
|
||||
}
|
||||
@@ -1460,7 +1553,7 @@ impl BeaconNodeHttpClient {
|
||||
&self,
|
||||
slot: Option<Slot>,
|
||||
committee_index: Option<u64>,
|
||||
) -> Result<ForkVersionedResponse<Vec<Attestation<E>>>, Error> {
|
||||
) -> Result<BeaconResponse<Vec<Attestation<E>>>, Error> {
|
||||
let mut path = self.eth_path(V2)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -1479,7 +1572,7 @@ impl BeaconNodeHttpClient {
|
||||
.append_pair("committee_index", &index.to_string());
|
||||
}
|
||||
|
||||
self.get(path).await
|
||||
self.get(path).await.map(BeaconResponse::ForkVersioned)
|
||||
}
|
||||
|
||||
/// `POST v1/beacon/pool/attester_slashings`
|
||||
@@ -1538,7 +1631,7 @@ impl BeaconNodeHttpClient {
|
||||
/// `GET v2/beacon/pool/attester_slashings`
|
||||
pub async fn get_beacon_pool_attester_slashings_v2<E: EthSpec>(
|
||||
&self,
|
||||
) -> Result<ForkVersionedResponse<Vec<AttesterSlashing<E>>>, Error> {
|
||||
) -> Result<BeaconResponse<Vec<AttesterSlashing<E>>>, Error> {
|
||||
let mut path = self.eth_path(V2)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -1547,7 +1640,7 @@ impl BeaconNodeHttpClient {
|
||||
.push("pool")
|
||||
.push("attester_slashings");
|
||||
|
||||
self.get(path).await
|
||||
self.get(path).await.map(BeaconResponse::ForkVersioned)
|
||||
}
|
||||
|
||||
/// `POST beacon/pool/proposer_slashings`
|
||||
@@ -1652,18 +1745,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,
|
||||
@@ -1968,10 +2049,11 @@ impl BeaconNodeHttpClient {
|
||||
pub async fn get_debug_beacon_states<E: EthSpec>(
|
||||
&self,
|
||||
state_id: StateId,
|
||||
) -> Result<Option<ExecutionOptimisticFinalizedForkVersionedResponse<BeaconState<E>>>, Error>
|
||||
{
|
||||
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<BeaconState<E>>>, Error> {
|
||||
let path = self.get_debug_beacon_states_path(state_id)?;
|
||||
self.get_opt(path).await
|
||||
self.get_opt(path)
|
||||
.await
|
||||
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
|
||||
}
|
||||
|
||||
/// `GET debug/beacon/states/{state_id}`
|
||||
@@ -2055,7 +2137,7 @@ impl BeaconNodeHttpClient {
|
||||
slot: Slot,
|
||||
randao_reveal: &SignatureBytes,
|
||||
graffiti: Option<&Graffiti>,
|
||||
) -> Result<ForkVersionedResponse<FullBlockContents<E>>, Error> {
|
||||
) -> Result<BeaconResponse<FullBlockContents<E>>, Error> {
|
||||
self.get_validator_blocks_modular(slot, randao_reveal, graffiti, SkipRandaoVerification::No)
|
||||
.await
|
||||
}
|
||||
@@ -2067,12 +2149,12 @@ impl BeaconNodeHttpClient {
|
||||
randao_reveal: &SignatureBytes,
|
||||
graffiti: Option<&Graffiti>,
|
||||
skip_randao_verification: SkipRandaoVerification,
|
||||
) -> Result<ForkVersionedResponse<FullBlockContents<E>>, Error> {
|
||||
) -> Result<BeaconResponse<FullBlockContents<E>>, Error> {
|
||||
let path = self
|
||||
.get_validator_blocks_path::<E>(slot, randao_reveal, graffiti, skip_randao_verification)
|
||||
.await?;
|
||||
|
||||
self.get(path).await
|
||||
self.get(path).await.map(BeaconResponse::ForkVersioned)
|
||||
}
|
||||
|
||||
/// returns `GET v2/validator/blocks/{slot}` URL path
|
||||
@@ -2328,7 +2410,7 @@ impl BeaconNodeHttpClient {
|
||||
slot: Slot,
|
||||
randao_reveal: &SignatureBytes,
|
||||
graffiti: Option<&Graffiti>,
|
||||
) -> Result<ForkVersionedResponse<BlindedBeaconBlock<E>>, Error> {
|
||||
) -> Result<BeaconResponse<BlindedBeaconBlock<E>>, Error> {
|
||||
self.get_validator_blinded_blocks_modular(
|
||||
slot,
|
||||
randao_reveal,
|
||||
@@ -2377,7 +2459,7 @@ impl BeaconNodeHttpClient {
|
||||
randao_reveal: &SignatureBytes,
|
||||
graffiti: Option<&Graffiti>,
|
||||
skip_randao_verification: SkipRandaoVerification,
|
||||
) -> Result<ForkVersionedResponse<BlindedBeaconBlock<E>>, Error> {
|
||||
) -> Result<BeaconResponse<BlindedBeaconBlock<E>>, Error> {
|
||||
let path = self
|
||||
.get_validator_blinded_blocks_path::<E>(
|
||||
slot,
|
||||
@@ -2387,7 +2469,7 @@ impl BeaconNodeHttpClient {
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.get(path).await
|
||||
self.get(path).await.map(BeaconResponse::ForkVersioned)
|
||||
}
|
||||
|
||||
/// `GET v2/validator/blinded_blocks/{slot}` in ssz format
|
||||
@@ -2476,7 +2558,7 @@ impl BeaconNodeHttpClient {
|
||||
slot: Slot,
|
||||
attestation_data_root: Hash256,
|
||||
committee_index: CommitteeIndex,
|
||||
) -> Result<Option<ForkVersionedResponse<Attestation<E>>>, Error> {
|
||||
) -> Result<Option<BeaconResponse<Attestation<E>>>, Error> {
|
||||
let mut path = self.eth_path(V2)?;
|
||||
|
||||
path.path_segments_mut()
|
||||
@@ -2494,6 +2576,7 @@ impl BeaconNodeHttpClient {
|
||||
|
||||
self.get_opt_with_timeout(path, self.timeouts.attestation)
|
||||
.await
|
||||
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
|
||||
}
|
||||
|
||||
/// `GET validator/sync_committee_contribution`
|
||||
@@ -2697,7 +2780,7 @@ impl BeaconNodeHttpClient {
|
||||
while let Some(event) = es.next().await {
|
||||
match event {
|
||||
Ok(Event::Open) => break,
|
||||
Err(err) => return Err(Error::SseClient(err)),
|
||||
Err(err) => return Err(Error::SseClient(err.into())),
|
||||
// This should never happen as we are guaranteed to get the
|
||||
// Open event before any message starts coming through.
|
||||
Ok(Event::Message(_)) => continue,
|
||||
@@ -2709,7 +2792,7 @@ impl BeaconNodeHttpClient {
|
||||
Ok(Event::Message(message)) => {
|
||||
Some(EventKind::from_sse_bytes(&message.event, &message.data))
|
||||
}
|
||||
Err(err) => Some(Err(Error::SseClient(err))),
|
||||
Err(err) => Some(Err(Error::SseClient(err.into()))),
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -7,11 +7,8 @@ pub mod sync_state;
|
||||
|
||||
use crate::{
|
||||
lighthouse::sync_state::SyncState,
|
||||
types::{
|
||||
AdminPeer, DepositTreeSnapshot, Epoch, FinalizedExecutionBlock, GenericResponse,
|
||||
ValidatorId,
|
||||
},
|
||||
BeaconNodeHttpClient, DepositData, Error, Eth1Data, Hash256, Slot,
|
||||
types::{AdminPeer, Epoch, GenericResponse, ValidatorId},
|
||||
BeaconNodeHttpClient, DepositData, Error, Hash256, Slot,
|
||||
};
|
||||
use proto_array::core::ProtoArray;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -159,18 +156,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,41 +168,6 @@ 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> {
|
||||
@@ -298,63 +248,6 @@ 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();
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use mediatype::{names, MediaType, MediaTypeList};
|
||||
use multiaddr::Multiaddr;
|
||||
use reqwest::header::HeaderMap;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_json::Value;
|
||||
use serde_utils::quoted_u64::Quoted;
|
||||
use ssz::{Decode, DecodeError};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
@@ -350,6 +349,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
|
||||
@@ -689,12 +696,18 @@ pub struct ValidatorBalancesQuery {
|
||||
pub id: Option<Vec<ValidatorId>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
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 {
|
||||
@@ -702,6 +715,13 @@ pub struct BlobIndicesQuery {
|
||||
pub indices: Option<Vec<u64>>,
|
||||
}
|
||||
|
||||
#[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>);
|
||||
@@ -978,6 +998,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,
|
||||
@@ -1067,51 +1116,56 @@ pub struct SseExtendedPayloadAttributesGeneric<T> {
|
||||
pub type SseExtendedPayloadAttributes = SseExtendedPayloadAttributesGeneric<SsePayloadAttributes>;
|
||||
pub type VersionedSsePayloadAttributes = ForkVersionedResponse<SseExtendedPayloadAttributes>;
|
||||
|
||||
impl ForkVersionDeserialize for SsePayloadAttributes {
|
||||
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
|
||||
value: serde_json::value::Value,
|
||||
fork_name: ForkName,
|
||||
) -> Result<Self, D::Error> {
|
||||
match fork_name {
|
||||
ForkName::Bellatrix => serde_json::from_value(value)
|
||||
.map(Self::V1)
|
||||
.map_err(serde::de::Error::custom),
|
||||
ForkName::Capella => serde_json::from_value(value)
|
||||
.map(Self::V2)
|
||||
.map_err(serde::de::Error::custom),
|
||||
ForkName::Deneb => serde_json::from_value(value)
|
||||
.map(Self::V3)
|
||||
.map_err(serde::de::Error::custom),
|
||||
ForkName::Electra => serde_json::from_value(value)
|
||||
.map(Self::V3)
|
||||
.map_err(serde::de::Error::custom),
|
||||
ForkName::Fulu => serde_json::from_value(value)
|
||||
.map(Self::V3)
|
||||
.map_err(serde::de::Error::custom),
|
||||
ForkName::Base | ForkName::Altair => Err(serde::de::Error::custom(format!(
|
||||
"SsePayloadAttributes deserialization for {fork_name} not implemented"
|
||||
))),
|
||||
}
|
||||
impl<'de> ContextDeserialize<'de, ForkName> for SsePayloadAttributes {
|
||||
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let convert_err = |e| {
|
||||
serde::de::Error::custom(format!(
|
||||
"SsePayloadAttributes failed to deserialize: {:?}",
|
||||
e
|
||||
))
|
||||
};
|
||||
Ok(match context {
|
||||
ForkName::Base | ForkName::Altair => {
|
||||
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)?)
|
||||
}
|
||||
ForkName::Capella => {
|
||||
Self::V2(Deserialize::deserialize(deserializer).map_err(convert_err)?)
|
||||
}
|
||||
ForkName::Deneb | ForkName::Electra | ForkName::Fulu => {
|
||||
Self::V3(Deserialize::deserialize(deserializer).map_err(convert_err)?)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ForkVersionDeserialize for SseExtendedPayloadAttributes {
|
||||
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
|
||||
value: serde_json::value::Value,
|
||||
fork_name: ForkName,
|
||||
) -> Result<Self, D::Error> {
|
||||
let helper: SseExtendedPayloadAttributesGeneric<serde_json::Value> =
|
||||
serde_json::from_value(value).map_err(serde::de::Error::custom)?;
|
||||
impl<'de> ContextDeserialize<'de, ForkName> for SseExtendedPayloadAttributes {
|
||||
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let helper =
|
||||
SseExtendedPayloadAttributesGeneric::<serde_json::Value>::deserialize(deserializer)?;
|
||||
|
||||
Ok(Self {
|
||||
proposal_slot: helper.proposal_slot,
|
||||
proposer_index: helper.proposer_index,
|
||||
parent_block_root: helper.parent_block_root,
|
||||
parent_block_number: helper.parent_block_number,
|
||||
parent_block_hash: helper.parent_block_hash,
|
||||
payload_attributes: SsePayloadAttributes::deserialize_by_fork::<D>(
|
||||
payload_attributes: SsePayloadAttributes::context_deserialize(
|
||||
helper.payload_attributes,
|
||||
fork_name,
|
||||
)?,
|
||||
context,
|
||||
)
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1123,14 +1177,15 @@ pub enum EventKind<E: EthSpec> {
|
||||
SingleAttestation(Box<SingleAttestation>),
|
||||
Block(SseBlock),
|
||||
BlobSidecar(SseBlobSidecar),
|
||||
DataColumnSidecar(SseDataColumnSidecar),
|
||||
FinalizedCheckpoint(SseFinalizedCheckpoint),
|
||||
Head(SseHead),
|
||||
VoluntaryExit(SignedVoluntaryExit),
|
||||
ChainReorg(SseChainReorg),
|
||||
ContributionAndProof(Box<SignedContributionAndProof<E>>),
|
||||
LateHead(SseLateHead),
|
||||
LightClientFinalityUpdate(Box<LightClientFinalityUpdate<E>>),
|
||||
LightClientOptimisticUpdate(Box<LightClientOptimisticUpdate<E>>),
|
||||
LightClientFinalityUpdate(Box<BeaconResponse<LightClientFinalityUpdate<E>>>),
|
||||
LightClientOptimisticUpdate(Box<BeaconResponse<LightClientOptimisticUpdate<E>>>),
|
||||
#[cfg(feature = "lighthouse")]
|
||||
BlockReward(BlockReward),
|
||||
PayloadAttributes(VersionedSsePayloadAttributes),
|
||||
@@ -1146,6 +1201,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",
|
||||
@@ -1181,6 +1237,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)),
|
||||
)?)),
|
||||
@@ -1210,22 +1271,24 @@ impl<E: EthSpec> EventKind<E> {
|
||||
ServerError::InvalidServerSentEvent(format!("Payload Attributes: {:?}", e))
|
||||
})?,
|
||||
)),
|
||||
"light_client_finality_update" => Ok(EventKind::LightClientFinalityUpdate(
|
||||
serde_json::from_str(data).map_err(|e| {
|
||||
"light_client_finality_update" => Ok(EventKind::LightClientFinalityUpdate(Box::new(
|
||||
BeaconResponse::ForkVersioned(serde_json::from_str(data).map_err(|e| {
|
||||
ServerError::InvalidServerSentEvent(format!(
|
||||
"Light Client Finality Update: {:?}",
|
||||
e
|
||||
))
|
||||
})?,
|
||||
)),
|
||||
"light_client_optimistic_update" => Ok(EventKind::LightClientOptimisticUpdate(
|
||||
serde_json::from_str(data).map_err(|e| {
|
||||
ServerError::InvalidServerSentEvent(format!(
|
||||
"Light Client Optimistic Update: {:?}",
|
||||
e
|
||||
))
|
||||
})?,
|
||||
)),
|
||||
})?),
|
||||
))),
|
||||
"light_client_optimistic_update" => {
|
||||
Ok(EventKind::LightClientOptimisticUpdate(Box::new(
|
||||
BeaconResponse::ForkVersioned(serde_json::from_str(data).map_err(|e| {
|
||||
ServerError::InvalidServerSentEvent(format!(
|
||||
"Light Client Optimistic Update: {:?}",
|
||||
e
|
||||
))
|
||||
})?),
|
||||
)))
|
||||
}
|
||||
#[cfg(feature = "lighthouse")]
|
||||
"block_reward" => Ok(EventKind::BlockReward(serde_json::from_str(data).map_err(
|
||||
|e| ServerError::InvalidServerSentEvent(format!("Block Reward: {:?}", e)),
|
||||
@@ -1268,6 +1331,7 @@ pub enum EventTopic {
|
||||
Head,
|
||||
Block,
|
||||
BlobSidecar,
|
||||
DataColumnSidecar,
|
||||
Attestation,
|
||||
SingleAttestation,
|
||||
VoluntaryExit,
|
||||
@@ -1294,6 +1358,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),
|
||||
@@ -1321,6 +1386,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"),
|
||||
@@ -1614,7 +1680,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Encode, Serialize, Deserialize)]
|
||||
#[derive(Debug, Encode, Serialize)]
|
||||
#[serde(untagged)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
#[ssz(enum_behaviour = "transparent")]
|
||||
@@ -1627,7 +1693,7 @@ pub type JsonProduceBlockV3Response<E> =
|
||||
ForkVersionedResponse<ProduceBlockV3Response<E>, ProduceBlockV3Metadata>;
|
||||
|
||||
/// A wrapper over a [`BeaconBlock`] or a [`BlockContents`].
|
||||
#[derive(Debug, Encode, Serialize, Deserialize)]
|
||||
#[derive(Debug, Encode, Serialize)]
|
||||
#[serde(untagged)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
#[ssz(enum_behaviour = "transparent")]
|
||||
@@ -1741,18 +1807,18 @@ impl<E: EthSpec> FullBlockContents<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> ForkVersionDeserialize for FullBlockContents<E> {
|
||||
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
|
||||
value: serde_json::value::Value,
|
||||
fork_name: ForkName,
|
||||
) -> Result<Self, D::Error> {
|
||||
if fork_name.deneb_enabled() {
|
||||
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for FullBlockContents<E> {
|
||||
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
if context.deneb_enabled() {
|
||||
Ok(FullBlockContents::BlockContents(
|
||||
BlockContents::deserialize_by_fork::<'de, D>(value, fork_name)?,
|
||||
BlockContents::context_deserialize::<D>(deserializer, context)?,
|
||||
))
|
||||
} else {
|
||||
Ok(FullBlockContents::Block(
|
||||
BeaconBlock::deserialize_by_fork::<'de, D>(value, fork_name)?,
|
||||
BeaconBlock::context_deserialize::<D>(deserializer, context)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1819,7 +1885,7 @@ impl TryFrom<&HeaderMap> for ProduceBlockV3Metadata {
|
||||
}
|
||||
|
||||
/// A wrapper over a [`SignedBeaconBlock`] or a [`SignedBlockContents`].
|
||||
#[derive(Clone, Debug, Encode, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Encode, Serialize)]
|
||||
#[serde(untagged)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
#[ssz(enum_behaviour = "transparent")]
|
||||
@@ -1828,6 +1894,26 @@ pub enum PublishBlockRequest<E: EthSpec> {
|
||||
Block(Arc<SignedBeaconBlock<E>>),
|
||||
}
|
||||
|
||||
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for PublishBlockRequest<E> {
|
||||
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value =
|
||||
serde_json::Value::deserialize(deserializer).map_err(serde::de::Error::custom)?;
|
||||
|
||||
SignedBlockContents::<E>::context_deserialize(&value, context)
|
||||
.map(PublishBlockRequest::BlockContents)
|
||||
.or_else(|_| {
|
||||
Arc::<SignedBeaconBlock<E>>::context_deserialize(&value, context)
|
||||
.map(PublishBlockRequest::Block)
|
||||
})
|
||||
.map_err(|_| {
|
||||
serde::de::Error::custom("could not match any variant of PublishBlockRequest")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> PublishBlockRequest<E> {
|
||||
pub fn new(
|
||||
block: Arc<SignedBeaconBlock<E>>,
|
||||
@@ -1904,7 +1990,7 @@ impl<E: EthSpec> From<SignedBlockContentsTuple<E>> for PublishBlockRequest<E> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Encode)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Encode)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
pub struct SignedBlockContents<E: EthSpec> {
|
||||
pub signed_block: Arc<SignedBeaconBlock<E>>,
|
||||
@@ -1913,7 +1999,33 @@ pub struct SignedBlockContents<E: EthSpec> {
|
||||
pub blobs: BlobsList<E>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Encode)]
|
||||
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for SignedBlockContents<E> {
|
||||
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
struct Helper<E: EthSpec> {
|
||||
signed_block: serde_json::Value,
|
||||
kzg_proofs: KzgProofs<E>,
|
||||
#[serde(with = "ssz_types::serde_utils::list_of_hex_fixed_vec")]
|
||||
blobs: BlobsList<E>,
|
||||
}
|
||||
let helper = Helper::<E>::deserialize(deserializer).map_err(serde::de::Error::custom)?;
|
||||
|
||||
let block = SignedBeaconBlock::context_deserialize(helper.signed_block, context)
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(Self {
|
||||
signed_block: Arc::new(block),
|
||||
kzg_proofs: helper.kzg_proofs,
|
||||
blobs: helper.blobs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Encode)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
pub struct BlockContents<E: EthSpec> {
|
||||
pub block: BeaconBlock<E>,
|
||||
@@ -1922,11 +2034,11 @@ pub struct BlockContents<E: EthSpec> {
|
||||
pub blobs: BlobsList<E>,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> ForkVersionDeserialize for BlockContents<E> {
|
||||
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
|
||||
value: serde_json::value::Value,
|
||||
fork_name: ForkName,
|
||||
) -> Result<Self, D::Error> {
|
||||
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for BlockContents<E> {
|
||||
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
struct Helper<E: EthSpec> {
|
||||
@@ -1935,10 +2047,13 @@ impl<E: EthSpec> ForkVersionDeserialize for BlockContents<E> {
|
||||
#[serde(with = "ssz_types::serde_utils::list_of_hex_fixed_vec")]
|
||||
blobs: BlobsList<E>,
|
||||
}
|
||||
let helper: Helper<E> = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
|
||||
let helper = Helper::<E>::deserialize(deserializer).map_err(serde::de::Error::custom)?;
|
||||
|
||||
let block = BeaconBlock::context_deserialize(helper.block, context)
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(Self {
|
||||
block: BeaconBlock::deserialize_by_fork::<'de, D>(helper.block, fork_name)?,
|
||||
block,
|
||||
kzg_proofs: helper.kzg_proofs,
|
||||
blobs: helper.blobs,
|
||||
})
|
||||
@@ -2010,22 +2125,22 @@ impl<E: EthSpec> FullPayloadContents<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> ForkVersionDeserialize for FullPayloadContents<E> {
|
||||
fn deserialize_by_fork<'de, D: Deserializer<'de>>(
|
||||
value: Value,
|
||||
fork_name: ForkName,
|
||||
) -> Result<Self, D::Error> {
|
||||
if fork_name.deneb_enabled() {
|
||||
ExecutionPayloadAndBlobs::deserialize_by_fork::<'de, D>(value, fork_name)
|
||||
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for FullPayloadContents<E> {
|
||||
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
if context.deneb_enabled() {
|
||||
ExecutionPayloadAndBlobs::context_deserialize::<D>(deserializer, context)
|
||||
.map(Self::PayloadAndBlobs)
|
||||
.map_err(serde::de::Error::custom)
|
||||
} else if fork_name.bellatrix_enabled() {
|
||||
ExecutionPayload::deserialize_by_fork::<'de, D>(value, fork_name)
|
||||
} else if context.bellatrix_enabled() {
|
||||
ExecutionPayload::context_deserialize::<D>(deserializer, context)
|
||||
.map(Self::Payload)
|
||||
.map_err(serde::de::Error::custom)
|
||||
} else {
|
||||
Err(serde::de::Error::custom(format!(
|
||||
"FullPayloadContents deserialization for {fork_name} not implemented"
|
||||
"FullPayloadContents deserialization for {context} not implemented"
|
||||
)))
|
||||
}
|
||||
}
|
||||
@@ -2038,23 +2153,25 @@ pub struct ExecutionPayloadAndBlobs<E: EthSpec> {
|
||||
pub blobs_bundle: BlobsBundle<E>,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> ForkVersionDeserialize for ExecutionPayloadAndBlobs<E> {
|
||||
fn deserialize_by_fork<'de, D: Deserializer<'de>>(
|
||||
value: Value,
|
||||
fork_name: ForkName,
|
||||
) -> Result<Self, D::Error> {
|
||||
impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadAndBlobs<E> {
|
||||
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(bound = "E: EthSpec")]
|
||||
struct Helper<E: EthSpec> {
|
||||
execution_payload: serde_json::Value,
|
||||
blobs_bundle: BlobsBundle<E>,
|
||||
}
|
||||
let helper: Helper<E> = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
|
||||
let helper = Helper::<E>::deserialize(deserializer).map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(Self {
|
||||
execution_payload: ExecutionPayload::deserialize_by_fork::<'de, D>(
|
||||
execution_payload: ExecutionPayload::context_deserialize(
|
||||
helper.execution_payload,
|
||||
fork_name,
|
||||
)?,
|
||||
context,
|
||||
)
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
blobs_bundle: helper.blobs_bundle,
|
||||
})
|
||||
}
|
||||
@@ -2203,6 +2320,73 @@ mod test {
|
||||
assert_eq!(serde_json::to_string(&y).unwrap(), pubkey_str);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_publish_block_request_context_deserialize() {
|
||||
let round_trip_test = |request: PublishBlockRequest<MainnetEthSpec>| {
|
||||
let fork_name = request.signed_block().fork_name_unchecked();
|
||||
let json_str = serde_json::to_string(&request).unwrap();
|
||||
let mut de = serde_json::Deserializer::from_str(&json_str);
|
||||
let deserialized_request =
|
||||
PublishBlockRequest::<MainnetEthSpec>::context_deserialize(&mut de, fork_name)
|
||||
.unwrap();
|
||||
assert_eq!(request, deserialized_request);
|
||||
};
|
||||
|
||||
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
||||
for fork_name in ForkName::list_all() {
|
||||
let signed_beacon_block =
|
||||
map_fork_name!(fork_name, SignedBeaconBlock, <_>::random_for_test(rng));
|
||||
let request = if fork_name.deneb_enabled() {
|
||||
let kzg_proofs = KzgProofs::<MainnetEthSpec>::random_for_test(rng);
|
||||
let blobs = BlobsList::<MainnetEthSpec>::random_for_test(rng);
|
||||
let block_contents = SignedBlockContents {
|
||||
signed_block: Arc::new(signed_beacon_block),
|
||||
kzg_proofs,
|
||||
blobs,
|
||||
};
|
||||
PublishBlockRequest::BlockContents(block_contents)
|
||||
} else {
|
||||
PublishBlockRequest::Block(Arc::new(signed_beacon_block))
|
||||
};
|
||||
round_trip_test(request);
|
||||
println!("fork_name: {:?} PASSED", fork_name);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signed_block_contents_context_deserialize() {
|
||||
let round_trip_test = |contents: SignedBlockContents<MainnetEthSpec>| {
|
||||
let fork_name = contents.signed_block.fork_name_unchecked();
|
||||
let json_str = serde_json::to_string(&contents).unwrap();
|
||||
let mut de = serde_json::Deserializer::from_str(&json_str);
|
||||
let deserialized_contents =
|
||||
SignedBlockContents::<MainnetEthSpec>::context_deserialize(&mut de, fork_name)
|
||||
.unwrap();
|
||||
assert_eq!(contents, deserialized_contents);
|
||||
};
|
||||
|
||||
let mut fork_name = ForkName::Deneb;
|
||||
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
||||
loop {
|
||||
let signed_beacon_block =
|
||||
map_fork_name!(fork_name, SignedBeaconBlock, <_>::random_for_test(rng));
|
||||
let kzg_proofs = KzgProofs::<MainnetEthSpec>::random_for_test(rng);
|
||||
let blobs = BlobsList::<MainnetEthSpec>::random_for_test(rng);
|
||||
let block_contents = SignedBlockContents {
|
||||
signed_block: Arc::new(signed_beacon_block),
|
||||
kzg_proofs,
|
||||
blobs,
|
||||
};
|
||||
round_trip_test(block_contents);
|
||||
println!("fork_name: {:?} PASSED", fork_name);
|
||||
if let Some(next_fork_name) = fork_name.next_fork() {
|
||||
fork_name = next_fork_name;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execution_payload_execution_payload_deserialize_by_fork() {
|
||||
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
||||
@@ -2295,14 +2479,13 @@ mod test {
|
||||
fn generic_deserialize_by_fork<
|
||||
'de,
|
||||
D: Deserializer<'de>,
|
||||
O: ForkVersionDeserialize + PartialEq + Debug,
|
||||
O: ContextDeserialize<'de, ForkName> + PartialEq + Debug,
|
||||
>(
|
||||
deserializer: D,
|
||||
original: O,
|
||||
fork_name: ForkName,
|
||||
) {
|
||||
let val = Value::deserialize(deserializer).unwrap();
|
||||
let roundtrip = O::deserialize_by_fork::<'de, D>(val, fork_name).unwrap();
|
||||
let roundtrip = O::context_deserialize::<D>(deserializer, fork_name).unwrap();
|
||||
assert_eq!(original, roundtrip);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user