resolve merge conflicts

This commit is contained in:
Eitan Seri-Levi
2026-04-30 01:51:26 +02:00
544 changed files with 48684 additions and 18351 deletions

View File

@@ -17,6 +17,8 @@ pub enum Error {
#[cfg(feature = "events")]
/// The `reqwest_eventsource` client raised an error.
SseClient(Box<reqwest_eventsource::Error>),
#[cfg(feature = "events")]
SseEventSource(reqwest_eventsource::CannotCloneRequestError),
/// 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.
@@ -100,6 +102,8 @@ impl Error {
None
}
}
#[cfg(feature = "events")]
Error::SseEventSource(_) => None,
Error::ServerMessage(msg) => StatusCode::try_from(msg.code).ok(),
Error::ServerIndexedMessage(msg) => StatusCode::try_from(msg.code).ok(),
Error::StatusCode(status) => Some(*status),

View File

@@ -22,8 +22,6 @@ pub use beacon_response::{
};
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};
@@ -35,21 +33,25 @@ use educe::Educe;
use futures::Stream;
#[cfg(feature = "events")]
use futures_util::StreamExt;
#[cfg(feature = "network")]
use libp2p_identity::PeerId;
use reqwest::{
Body, IntoUrl, RequestBuilder, Response,
Body, IntoUrl, RequestBuilder, Response, StatusCode, Url,
header::{HeaderMap, HeaderValue},
};
#[cfg(feature = "events")]
use reqwest_eventsource::{Event, EventSource};
use reqwest_eventsource::{Event, RequestBuilderExt};
use serde::{Serialize, de::DeserializeOwned};
use ssz::Encode;
use ssz::{Decode, Encode};
use std::fmt;
use std::future::Future;
use std::time::Duration;
use types::{PayloadAttestationData, PayloadAttestationMessage};
pub const V1: EndpointVersion = EndpointVersion(1);
pub const V2: EndpointVersion = EndpointVersion(2);
pub const V3: EndpointVersion = EndpointVersion(3);
pub const V4: EndpointVersion = EndpointVersion(4);
pub const CONSENSUS_VERSION_HEADER: &str = "Eth-Consensus-Version";
pub const EXECUTION_PAYLOAD_BLINDED_HEADER: &str = "Eth-Execution-Payload-Blinded";
@@ -72,10 +74,13 @@ 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
// TODO(EIP-7732): Determine what this quotient should be
const HTTP_PTC_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_PAYLOAD_ATTESTATION_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
@@ -94,10 +99,12 @@ pub struct Timeouts {
pub sync_aggregators: Duration,
pub inclusion_list: Duration,
pub inclusion_list_duties: Duration,
pub ptc_duties: Duration,
pub get_beacon_blocks_ssz: Duration,
pub get_debug_beacon_states: Duration,
pub get_deposit_snapshot: Duration,
pub get_validator_block: Duration,
pub payload_attestation: Duration,
pub default: Duration,
}
@@ -116,10 +123,12 @@ impl Timeouts {
sync_aggregators: timeout,
inclusion_list: timeout,
inclusion_list_duties: timeout,
ptc_duties: timeout,
get_beacon_blocks_ssz: timeout,
get_debug_beacon_states: timeout,
get_deposit_snapshot: timeout,
get_validator_block: timeout,
payload_attestation: timeout,
default: timeout,
}
}
@@ -141,10 +150,12 @@ impl Timeouts {
inclusion_list_duties: base_timeout,
inclusion_list: base_timeout,
sync_aggregators: base_timeout / HTTP_SYNC_AGGREGATOR_TIMEOUT_QUOTIENT,
ptc_duties: base_timeout / HTTP_PTC_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,
payload_attestation: base_timeout / HTTP_PAYLOAD_ATTESTATION_TIMEOUT_QUOTIENT,
default: base_timeout / HTTP_DEFAULT_TIMEOUT_QUOTIENT,
}
}
@@ -904,6 +915,47 @@ impl BeaconNodeHttpClient {
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET beacon/states/{state_id}/proposer_lookahead`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_states_proposer_lookahead(
&self,
state_id: StateId,
) -> Result<Option<ExecutionOptimisticFinalizedBeaconResponse<ValidatorIndexData>>, 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("proposer_lookahead");
self.get_fork_contextual(path, |fork| fork)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET beacon/states/{state_id}/proposer_lookahead`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_states_proposer_lookahead_ssz(
&self,
state_id: StateId,
) -> 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("states")
.push(&state_id.to_string())
.push("proposer_lookahead");
self.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.default)
.await
}
/// `GET beacon/light_client/updates`
///
/// Returns `Ok(None)` on a 404 error.
@@ -1744,6 +1796,48 @@ impl BeaconNodeHttpClient {
Ok(())
}
/// `POST beacon/pool/payload_attestations` (JSON)
pub async fn post_beacon_pool_payload_attestations(
&self,
messages: &[PayloadAttestationMessage],
fork_name: ForkName,
) -> 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("payload_attestations");
self.post_generic_with_consensus_version(path, &messages, None, fork_name)
.await?;
Ok(())
}
/// `POST beacon/pool/payload_attestations` (SSZ)
pub async fn post_beacon_pool_payload_attestations_ssz(
&self,
messages: &[PayloadAttestationMessage],
fork_name: ForkName,
) -> 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("payload_attestations");
let ssz_body: Vec<u8> = messages.iter().flat_map(|m| m.as_ssz_bytes()).collect();
self.post_generic_with_consensus_version_and_ssz_body(path, ssz_body, None, fork_name)
.await?;
Ok(())
}
/// `POST beacon/pool/bls_to_execution_changes`
pub async fn post_beacon_pool_bls_to_execution_changes(
&self,
@@ -1786,7 +1880,7 @@ impl BeaconNodeHttpClient {
&self,
block_id: BlockId,
validators: &[ValidatorId],
) -> Result<GenericResponse<Vec<SyncCommitteeReward>>, Error> {
) -> Result<ExecutionOptimisticFinalizedResponse<Vec<SyncCommitteeReward>>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -1803,7 +1897,7 @@ impl BeaconNodeHttpClient {
pub async fn get_beacon_rewards_blocks(
&self,
block_id: BlockId,
) -> Result<GenericResponse<StandardBlockReward>, Error> {
) -> Result<ExecutionOptimisticFinalizedResponse<StandardBlockReward>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -1821,7 +1915,7 @@ impl BeaconNodeHttpClient {
&self,
epoch: Epoch,
validators: &[ValidatorId],
) -> Result<StandardAttestationRewards, Error> {
) -> Result<ExecutionOptimisticFinalizedResponse<StandardAttestationRewards>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
@@ -1960,6 +2054,7 @@ impl BeaconNodeHttpClient {
}
/// `GET node/identity`
#[cfg(feature = "network")]
pub async fn get_node_identity(&self) -> Result<GenericResponse<IdentityData>, Error> {
let mut path = self.eth_path(V1)?;
@@ -2007,9 +2102,10 @@ impl BeaconNodeHttpClient {
}
/// `GET node/peers/{peer_id}`
#[cfg(feature = "network")]
pub async fn get_node_peers_by_id(
&self,
peer_id: &str,
peer_id: PeerId,
) -> Result<GenericResponse<PeerData>, Error> {
let mut path = self.eth_path(V1)?;
@@ -2017,7 +2113,7 @@ impl BeaconNodeHttpClient {
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("node")
.push("peers")
.push(peer_id);
.push(&peer_id.to_string());
self.get(path).await
}
@@ -2167,6 +2263,24 @@ impl BeaconNodeHttpClient {
.await
}
/// `GET v2/validator/duties/proposer/{epoch}`
pub async fn get_validator_duties_proposer_v2(
&self,
epoch: Epoch,
) -> Result<DutiesResponse<Vec<ProposerData>>, Error> {
let mut path = self.eth_path(V2)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("duties")
.push("proposer")
.push(&epoch.to_string());
self.get_with_timeout(path, self.timeouts.proposer_duties)
.await
}
/// `GET v2/validator/blocks/{slot}`
pub async fn get_validator_blocks<E: EthSpec>(
&self,
@@ -2425,6 +2539,326 @@ impl BeaconNodeHttpClient {
opt_response.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))
}
/// returns `GET v4/validator/blocks/{slot}` URL path
pub async fn get_validator_blocks_v4_path(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
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(V4)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("blocks")
.push(&slot.to_string());
path.query_pairs_mut()
.append_pair("randao_reveal", &randao_reveal.to_string());
if let Some(graffiti) = graffiti {
path.query_pairs_mut()
.append_pair("graffiti", &graffiti.to_string());
}
if skip_randao_verification == SkipRandaoVerification::Yes {
path.query_pairs_mut()
.append_pair("skip_randao_verification", "");
}
if let Some(builder_booster_factor) = builder_booster_factor {
path.query_pairs_mut()
.append_pair("builder_boost_factor", &builder_booster_factor.to_string());
}
if let Some(GraffitiPolicy::AppendClientVersions) = graffiti_policy {
path.query_pairs_mut()
.append_pair("graffiti_policy", "AppendClientVersions");
}
Ok(path)
}
/// `GET v4/validator/blocks/{slot}`
pub async fn get_validator_blocks_v4<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<
(
ForkVersionedResponse<BeaconBlock<E>, ProduceBlockV4Metadata>,
ProduceBlockV4Metadata,
),
Error,
> {
self.get_validator_blocks_v4_modular(
slot,
randao_reveal,
graffiti,
SkipRandaoVerification::No,
builder_booster_factor,
graffiti_policy,
)
.await
}
/// `GET v4/validator/blocks/{slot}`
pub async fn get_validator_blocks_v4_modular<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<
(
ForkVersionedResponse<BeaconBlock<E>, ProduceBlockV4Metadata>,
ProduceBlockV4Metadata,
),
Error,
> {
let path = self
.get_validator_blocks_v4_path(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
builder_booster_factor,
graffiti_policy,
)
.await?;
let opt_result = self
.get_response_with_response_headers(
path,
Accept::Json,
self.timeouts.get_validator_block,
|response, headers| async move {
let header_metadata = ProduceBlockV4Metadata::try_from(&headers)
.map_err(Error::InvalidHeaders)?;
let block_response = response
.json::<ForkVersionedResponse<BeaconBlock<E>, ProduceBlockV4Metadata>>()
.await?;
Ok((block_response, header_metadata))
},
)
.await?;
opt_result.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))
}
/// `GET v4/validator/blocks/{slot}` in ssz format
pub async fn get_validator_blocks_v4_ssz<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(BeaconBlock<E>, ProduceBlockV4Metadata), Error> {
self.get_validator_blocks_v4_modular_ssz::<E>(
slot,
randao_reveal,
graffiti,
SkipRandaoVerification::No,
builder_booster_factor,
graffiti_policy,
)
.await
}
/// `GET v4/validator/blocks/{slot}` in ssz format
pub async fn get_validator_blocks_v4_modular_ssz<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(BeaconBlock<E>, ProduceBlockV4Metadata), Error> {
let path = self
.get_validator_blocks_v4_path(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
builder_booster_factor,
graffiti_policy,
)
.await?;
let opt_response = self
.get_response_with_response_headers(
path,
Accept::Ssz,
self.timeouts.get_validator_block,
|response, headers| async move {
let metadata = ProduceBlockV4Metadata::try_from(&headers)
.map_err(Error::InvalidHeaders)?;
let response_bytes = response.bytes().await?;
let block = BeaconBlock::from_ssz_bytes_for_fork(
&response_bytes,
metadata.consensus_version,
)
.map_err(Error::InvalidSsz)?;
Ok((block, metadata))
},
)
.await?;
opt_response.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))
}
/// `GET v1/validator/execution_payload_envelope/{slot}/{builder_index}`
pub async fn get_validator_execution_payload_envelope<E: EthSpec>(
&self,
slot: Slot,
builder_index: u64,
) -> Result<ForkVersionedResponse<ExecutionPayloadEnvelope<E>>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("execution_payload_envelope")
.push(&slot.to_string())
.push(&builder_index.to_string());
self.get(path).await
}
/// `GET v1/validator/execution_payload_envelope/{slot}/{builder_index}` in SSZ format
pub async fn get_validator_execution_payload_envelope_ssz<E: EthSpec>(
&self,
slot: Slot,
builder_index: u64,
) -> Result<ExecutionPayloadEnvelope<E>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("execution_payload_envelope")
.push(&slot.to_string())
.push(&builder_index.to_string());
let opt_response = self
.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_validator_block)
.await?;
let response_bytes = opt_response.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))?;
ExecutionPayloadEnvelope::from_ssz_bytes(&response_bytes).map_err(Error::InvalidSsz)
}
/// `POST v1/beacon/execution_payload_envelope`
pub async fn post_beacon_execution_payload_envelope<E: EthSpec>(
&self,
envelope: &SignedExecutionPayloadEnvelope<E>,
fork_name: ForkName,
) -> Result<(), Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("beacon")
.push("execution_payload_envelope");
self.post_generic_with_consensus_version(
path,
envelope,
Some(self.timeouts.proposal),
fork_name,
)
.await?;
Ok(())
}
/// `POST v1/beacon/execution_payload_envelope` in SSZ format
pub async fn post_beacon_execution_payload_envelope_ssz<E: EthSpec>(
&self,
envelope: &SignedExecutionPayloadEnvelope<E>,
fork_name: ForkName,
) -> Result<(), Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("beacon")
.push("execution_payload_envelope");
self.post_generic_with_consensus_version_and_ssz_body(
path,
envelope.as_ssz_bytes(),
Some(self.timeouts.proposal),
fork_name,
)
.await?;
Ok(())
}
/// Path for `v1/beacon/execution_payload_envelope/{block_id}`
pub fn get_beacon_execution_payload_envelope_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("execution_payload_envelope")
.push(&block_id.to_string());
Ok(path)
}
/// `GET v1/beacon/execution_payload_envelope/{block_id}`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_execution_payload_envelope<E: EthSpec>(
&self,
block_id: BlockId,
) -> Result<
Option<ExecutionOptimisticFinalizedBeaconResponse<SignedExecutionPayloadEnvelope<E>>>,
Error,
> {
let path = self.get_beacon_execution_payload_envelope_path(block_id)?;
self.get_opt(path)
.await
.map(|opt| opt.map(BeaconResponse::ForkVersioned))
}
/// `GET v1/beacon/execution_payload_envelope/{block_id}` in SSZ format
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_execution_payload_envelope_ssz<E: EthSpec>(
&self,
block_id: BlockId,
) -> Result<Option<SignedExecutionPayloadEnvelope<E>>, Error> {
let path = self.get_beacon_execution_payload_envelope_path(block_id)?;
let opt_response = self
.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_beacon_blocks_ssz)
.await?;
match opt_response {
Some(bytes) => SignedExecutionPayloadEnvelope::from_ssz_bytes(&bytes)
.map(Some)
.map_err(Error::InvalidSsz),
None => Ok(None),
}
}
/// `GET v2/validator/blocks/{slot}` in ssz format
pub async fn get_validator_blocks_ssz<E: EthSpec>(
&self,
@@ -2581,6 +3015,46 @@ impl BeaconNodeHttpClient {
self.get_with_timeout(path, self.timeouts.attestation).await
}
/// `GET validator/payload_attestation_data/{slot}`
pub async fn get_validator_payload_attestation_data(
&self,
slot: Slot,
) -> Result<BeaconResponse<PayloadAttestationData>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("payload_attestation_data")
.push(&slot.to_string());
self.get_with_timeout(path, self.timeouts.payload_attestation)
.await
.map(BeaconResponse::ForkVersioned)
}
/// `GET validator/payload_attestation_data/{slot}` in SSZ format
pub async fn get_validator_payload_attestation_data_ssz(
&self,
slot: Slot,
) -> Result<PayloadAttestationData, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("payload_attestation_data")
.push(&slot.to_string());
let opt_response = self
.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.payload_attestation)
.await?;
let response_bytes = opt_response.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))?;
PayloadAttestationData::from_ssz_bytes(&response_bytes).map_err(Error::InvalidSsz)
}
/// `GET v1/validator/aggregate_attestation?slot,attestation_data_root`
pub async fn get_validator_aggregate_attestation_v1<E: EthSpec>(
&self,
@@ -2868,7 +3342,16 @@ impl BeaconNodeHttpClient {
.join(",");
path.query_pairs_mut().append_pair("topics", &topic_string);
let mut es = EventSource::get(path);
// Do not use a timeout for the events endpoint. Using a regular timeout will trigger a
// timeout every `timeout` seconds, regardless of any data streamed from the endpoint.
// In future we could add a read_timeout, but that can only be configured globally on the
// Client.
let mut es = self
.client
.get(path)
.timeout(Duration::MAX)
.eventsource()
.map_err(Error::SseEventSource)?;
// If we don't await `Event::Open` here, then the consumer
// will not get any Message events until they start awaiting the stream.
// This is a way to register the stream with the sse server before
@@ -2951,4 +3434,29 @@ impl BeaconNodeHttpClient {
self.post_with_timeout_and_response(path, &selections, self.timeouts.sync_aggregators)
.await
}
// TODO(EIP-7732): Create corresponding beacon node response endpoint per spec
// https://github.com/ethereum/beacon-APIs/pull/552
/// `POST validator/duties/ptc/{epoch}`
pub async fn post_validator_duties_ptc(
&self,
epoch: Epoch,
indices: &[u64],
) -> Result<DutiesResponse<Vec<PtcDuty>>, Error> {
let mut path = self.eth_path(V1)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("duties")
.push("ptc")
.push(&epoch.to_string());
self.post_with_timeout_and_response(
path,
&ValidatorIndexDataRef(indices),
self.timeouts.ptc_duties,
)
.await
}
}

View File

@@ -1,13 +1,10 @@
//! This module contains endpoints that are non-standard and only available on Lighthouse servers.
mod attestation_performance;
mod block_packing_efficiency;
mod block_rewards;
mod custody;
pub mod sync_state;
use crate::{
BeaconNodeHttpClient, DepositData, Error, Hash256, Slot,
BeaconNodeHttpClient, DepositData, Error, Hash256,
lighthouse::sync_state::SyncState,
types::{AdminPeer, Epoch, GenericResponse, ValidatorId},
};
@@ -16,13 +13,6 @@ use serde::{Deserialize, Serialize};
use ssz::four_byte_option_impl;
use ssz_derive::{Decode, Encode};
pub use attestation_performance::{
AttestationPerformance, AttestationPerformanceQuery, AttestationPerformanceStatistics,
};
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
@@ -312,73 +302,4 @@ impl BeaconNodeHttpClient {
self.post_with_response(path, &req).await
}
/*
Analysis endpoints.
*/
/// `GET` lighthouse/analysis/block_rewards?start_slot,end_slot
pub async fn get_lighthouse_analysis_block_rewards(
&self,
start_slot: Slot,
end_slot: Slot,
) -> Result<Vec<BlockReward>, Error> {
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("lighthouse")
.push("analysis")
.push("block_rewards");
path.query_pairs_mut()
.append_pair("start_slot", &start_slot.to_string())
.append_pair("end_slot", &end_slot.to_string());
self.get(path).await
}
/// `GET` lighthouse/analysis/block_packing?start_epoch,end_epoch
pub async fn get_lighthouse_analysis_block_packing(
&self,
start_epoch: Epoch,
end_epoch: Epoch,
) -> Result<Vec<BlockPackingEfficiency>, Error> {
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("lighthouse")
.push("analysis")
.push("block_packing_efficiency");
path.query_pairs_mut()
.append_pair("start_epoch", &start_epoch.to_string())
.append_pair("end_epoch", &end_epoch.to_string());
self.get(path).await
}
/// `GET` lighthouse/analysis/attestation_performance/{index}?start_epoch,end_epoch
pub async fn get_lighthouse_analysis_attestation_performance(
&self,
start_epoch: Epoch,
end_epoch: Epoch,
target: String,
) -> Result<Vec<AttestationPerformance>, Error> {
let mut path = self.server.expose_full().clone();
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("lighthouse")
.push("analysis")
.push("attestation_performance")
.push(&target);
path.query_pairs_mut()
.append_pair("start_epoch", &start_epoch.to_string())
.append_pair("end_epoch", &end_epoch.to_string());
self.get(path).await
}
}

View File

@@ -1,39 +0,0 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use types::Epoch;
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
pub struct AttestationPerformanceStatistics {
pub active: bool,
pub head: bool,
pub target: bool,
pub source: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<u64>,
}
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
pub struct AttestationPerformance {
pub index: u64,
pub epochs: HashMap<u64, AttestationPerformanceStatistics>,
}
impl AttestationPerformance {
pub fn initialize(indices: Vec<u64>) -> Vec<Self> {
let mut vec = Vec::with_capacity(indices.len());
for index in indices {
vec.push(Self {
index,
..Default::default()
})
}
vec
}
}
/// Query parameters for the `/lighthouse/analysis/attestation_performance` endpoint.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct AttestationPerformanceQuery {
pub start_epoch: Epoch,
pub end_epoch: Epoch,
}

View File

@@ -1,34 +0,0 @@
use serde::{Deserialize, Serialize};
use types::{Epoch, Hash256, Slot};
type CommitteePosition = usize;
type Committee = u64;
type ValidatorIndex = u64;
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
pub struct UniqueAttestation {
pub slot: Slot,
pub committee_index: Committee,
pub committee_position: CommitteePosition,
}
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
pub struct ProposerInfo {
pub validator_index: ValidatorIndex,
pub graffiti: String,
}
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
pub struct BlockPackingEfficiency {
pub slot: Slot,
pub block_hash: Hash256,
pub proposer_info: ProposerInfo,
pub available_attestations: usize,
pub included_attestations: usize,
pub prior_skip_slots: u64,
}
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
pub struct BlockPackingEfficiencyQuery {
pub start_epoch: Epoch,
pub end_epoch: Epoch,
}

View File

@@ -1,60 +0,0 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use types::{AttestationData, Hash256, Slot};
/// Details about the rewards paid to a block proposer for proposing a block.
///
/// All rewards in GWei.
///
/// Presently this only counts attestation rewards, but in future should be expanded
/// to include information on slashings and sync committee aggregates too.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct BlockReward {
/// Sum of all reward components.
pub total: u64,
/// Block root of the block that these rewards are for.
pub block_root: Hash256,
/// Metadata about the block, particularly reward-relevant metadata.
pub meta: BlockRewardMeta,
/// Rewards due to attestations.
pub attestation_rewards: AttestationRewards,
/// Sum of rewards due to sync committee signatures.
pub sync_committee_rewards: u64,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct BlockRewardMeta {
pub slot: Slot,
pub parent_slot: Slot,
pub proposer_index: u64,
pub graffiti: String,
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct AttestationRewards {
/// Total block reward from attestations included.
pub total: u64,
/// Total rewards from previous epoch attestations.
pub prev_epoch_total: u64,
/// Total rewards from current epoch attestations.
pub curr_epoch_total: u64,
/// Vec of attestation rewards for each attestation included.
///
/// Each element of the vec is a map from validator index to reward.
pub per_attestation_rewards: Vec<HashMap<u64, u64>>,
/// The attestations themselves (optional).
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub attestations: Vec<AttestationData>,
}
/// Query parameters for the `/lighthouse/block_rewards` endpoint.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct BlockRewardsQuery {
/// Lower slot limit for block rewards returned (inclusive).
pub start_slot: Slot,
/// Upper slot limit for block rewards returned (inclusive).
pub end_slot: Slot,
/// Include the full attestations themselves?
#[serde(default)]
pub include_attestations: bool,
}

View File

@@ -9,7 +9,11 @@ use crate::{
};
use bls::{PublicKeyBytes, SecretKey, Signature, SignatureBytes};
use context_deserialize::ContextDeserialize;
#[cfg(feature = "network")]
use enr::{CombinedKey, Enr};
use mediatype::{MediaType, MediaTypeList, names};
#[cfg(feature = "network")]
use multiaddr::Multiaddr;
use reqwest::header::HeaderMap;
use serde::{Deserialize, Deserializer, Serialize};
use serde_utils::quoted_u64::Quoted;
@@ -33,9 +37,6 @@ pub mod beacon_response {
pub use crate::beacon_response::*;
}
#[cfg(feature = "lighthouse")]
use crate::lighthouse::BlockReward;
// Re-export error types from the unified error module
pub use crate::error::{ErrorMessage, Failure, IndexedErrorMessage, ResponseError as Error};
@@ -559,12 +560,13 @@ pub struct ChainHeadData {
pub execution_optimistic: Option<bool>,
}
#[cfg(feature = "network")]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IdentityData {
pub peer_id: String,
pub enr: String,
pub p2p_addresses: Vec<String>,
pub discovery_addresses: Vec<String>,
pub enr: Enr<CombinedKey>,
pub p2p_addresses: Vec<Multiaddr>,
pub discovery_addresses: Vec<Multiaddr>,
pub metadata: MetaData,
}
@@ -706,6 +708,15 @@ pub struct DataColumnIndicesQuery {
#[serde(transparent)]
pub struct ValidatorIndexData(#[serde(with = "serde_utils::quoted_u64_vec")] pub Vec<u64>);
impl<'de, T> ContextDeserialize<'de, T> for ValidatorIndexData {
fn context_deserialize<D>(deserializer: D, _context: T) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Self::deserialize(deserializer)
}
}
/// Borrowed variant of `ValidatorIndexData`, for serializing/sending.
#[derive(Clone, Copy, Serialize)]
#[serde(transparent)]
@@ -759,6 +770,14 @@ pub enum GraffitiPolicy {
AppendClientVersions,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PtcDuty {
pub pubkey: PublicKeyBytes,
#[serde(with = "serde_utils::quoted_u64")]
pub validator_index: u64,
pub slot: Slot,
}
#[derive(Clone, Deserialize)]
pub struct ValidatorBlocksQuery {
pub randao_reveal: SignatureBytes,
@@ -1021,14 +1040,18 @@ 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();
// TODO(gloas): fetch kzg_commitments from block for Gloas SSE events
let kzg_commitments: Vec<KzgCommitment> = match data_column_sidecar {
DataColumnSidecar::Fulu(dc) => dc.kzg_commitments.to_vec(),
DataColumnSidecar::Gloas(_) => 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,
index: *data_column_sidecar.index(),
slot: data_column_sidecar.slot(),
kzg_commitments,
versioned_hashes,
@@ -1067,6 +1090,31 @@ pub struct BlockGossip {
pub slot: Slot,
pub block: Hash256,
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct SseExecutionPayload {
pub slot: Slot,
#[serde(with = "serde_utils::quoted_u64")]
pub builder_index: u64,
pub block_hash: ExecutionBlockHash,
pub block_root: Hash256,
pub execution_optimistic: bool,
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct SseExecutionPayloadGossip {
pub slot: Slot,
#[serde(with = "serde_utils::quoted_u64")]
pub builder_index: u64,
pub block_hash: ExecutionBlockHash,
pub block_root: Hash256,
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct SseExecutionPayloadAvailable {
pub slot: Slot,
pub block_root: Hash256,
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
pub struct SseChainReorg {
pub slot: Slot,
@@ -1131,6 +1179,8 @@ pub struct SseExtendedPayloadAttributesGeneric<T> {
pub type SseExtendedPayloadAttributes = SseExtendedPayloadAttributesGeneric<SsePayloadAttributes>;
pub type VersionedSsePayloadAttributes = ForkVersionedResponse<SseExtendedPayloadAttributes>;
pub type VersionedSseExecutionPayloadBid<E> = ForkVersionedResponse<SignedExecutionPayloadBid<E>>;
pub type VersionedSsePayloadAttestationMessage = ForkVersionedResponse<PayloadAttestationMessage>;
impl<'de> ContextDeserialize<'de, ForkName> for SsePayloadAttributes {
fn context_deserialize<D>(deserializer: D, context: ForkName) -> Result<Self, D::Error>
@@ -1206,14 +1256,17 @@ pub enum EventKind<E: EthSpec> {
LateHead(SseLateHead),
LightClientFinalityUpdate(Box<BeaconResponse<LightClientFinalityUpdate<E>>>),
LightClientOptimisticUpdate(Box<BeaconResponse<LightClientOptimisticUpdate<E>>>),
#[cfg(feature = "lighthouse")]
BlockReward(BlockReward),
PayloadAttributes(VersionedSsePayloadAttributes),
ProposerSlashing(Box<ProposerSlashing>),
AttesterSlashing(Box<AttesterSlashing<E>>),
BlsToExecutionChange(Box<SignedBlsToExecutionChange>),
BlockGossip(Box<BlockGossip>),
InclusionList(SseInclusionList<E>),
ExecutionPayload(SseExecutionPayload),
ExecutionPayloadGossip(SseExecutionPayloadGossip),
ExecutionPayloadAvailable(SseExecutionPayloadAvailable),
ExecutionPayloadBid(Box<VersionedSseExecutionPayloadBid<E>>),
PayloadAttestationMessage(Box<VersionedSsePayloadAttestationMessage>),
}
impl<E: EthSpec> EventKind<E> {
@@ -1233,13 +1286,16 @@ impl<E: EthSpec> EventKind<E> {
EventKind::LateHead(_) => "late_head",
EventKind::LightClientFinalityUpdate(_) => "light_client_finality_update",
EventKind::LightClientOptimisticUpdate(_) => "light_client_optimistic_update",
#[cfg(feature = "lighthouse")]
EventKind::BlockReward(_) => "block_reward",
EventKind::ProposerSlashing(_) => "proposer_slashing",
EventKind::AttesterSlashing(_) => "attester_slashing",
EventKind::BlsToExecutionChange(_) => "bls_to_execution_change",
EventKind::BlockGossip(_) => "block_gossip",
EventKind::InclusionList(_) => "inclusion_list",
EventKind::ExecutionPayload(_) => "execution_payload",
EventKind::ExecutionPayloadGossip(_) => "execution_payload_gossip",
EventKind::ExecutionPayloadAvailable(_) => "execution_payload_available",
EventKind::ExecutionPayloadBid(_) => "execution_payload_bid",
EventKind::PayloadAttestationMessage(_) => "payload_attestation_message",
}
}
@@ -1311,10 +1367,6 @@ impl<E: EthSpec> EventKind<E> {
})?),
)))
}
#[cfg(feature = "lighthouse")]
"block_reward" => Ok(EventKind::BlockReward(serde_json::from_str(data).map_err(
|e| ServerError::InvalidServerSentEvent(format!("Block Reward: {:?}", e)),
)?)),
"attester_slashing" => Ok(EventKind::AttesterSlashing(
serde_json::from_str(data).map_err(|e| {
ServerError::InvalidServerSentEvent(format!("Attester Slashing: {:?}", e))
@@ -1338,6 +1390,40 @@ impl<E: EthSpec> EventKind<E> {
ServerError::InvalidServerSentEvent(format!("Inclusion List {:?}", e))
})?,
)),
"execution_payload" => Ok(EventKind::ExecutionPayload(
serde_json::from_str(data).map_err(|e| {
ServerError::InvalidServerSentEvent(format!("Execution Payload: {:?}", e))
})?,
)),
"execution_payload_gossip" => Ok(EventKind::ExecutionPayloadGossip(
serde_json::from_str(data).map_err(|e| {
ServerError::InvalidServerSentEvent(format!(
"Execution Payload Gossip: {:?}",
e
))
})?,
)),
"execution_payload_available" => Ok(EventKind::ExecutionPayloadAvailable(
serde_json::from_str(data).map_err(|e| {
ServerError::InvalidServerSentEvent(format!(
"Execution Payload Available: {:?}",
e
))
})?,
)),
"execution_payload_bid" => Ok(EventKind::ExecutionPayloadBid(Box::new(
serde_json::from_str(data).map_err(|e| {
ServerError::InvalidServerSentEvent(format!("Execution Payload Bid: {:?}", e))
})?,
))),
"payload_attestation_message" => Ok(EventKind::PayloadAttestationMessage(Box::new(
serde_json::from_str(data).map_err(|e| {
ServerError::InvalidServerSentEvent(format!(
"Payload Attestation Message: {:?}",
e
))
})?,
))),
_ => Err(ServerError::InvalidServerSentEvent(
"Could not parse event tag".to_string(),
)),
@@ -1369,13 +1455,16 @@ pub enum EventTopic {
PayloadAttributes,
LightClientFinalityUpdate,
LightClientOptimisticUpdate,
#[cfg(feature = "lighthouse")]
BlockReward,
AttesterSlashing,
ProposerSlashing,
BlsToExecutionChange,
BlockGossip,
InclusionList,
ExecutionPayload,
ExecutionPayloadGossip,
ExecutionPayloadAvailable,
ExecutionPayloadBid,
PayloadAttestationMessage,
}
impl FromStr for EventTopic {
@@ -1397,13 +1486,16 @@ impl FromStr for EventTopic {
"late_head" => Ok(EventTopic::LateHead),
"light_client_finality_update" => Ok(EventTopic::LightClientFinalityUpdate),
"light_client_optimistic_update" => Ok(EventTopic::LightClientOptimisticUpdate),
#[cfg(feature = "lighthouse")]
"block_reward" => Ok(EventTopic::BlockReward),
"attester_slashing" => Ok(EventTopic::AttesterSlashing),
"proposer_slashing" => Ok(EventTopic::ProposerSlashing),
"bls_to_execution_change" => Ok(EventTopic::BlsToExecutionChange),
"block_gossip" => Ok(EventTopic::BlockGossip),
"inclusion_list" => Ok(EventTopic::InclusionList),
"execution_payload" => Ok(EventTopic::ExecutionPayload),
"execution_payload_gossip" => Ok(EventTopic::ExecutionPayloadGossip),
"execution_payload_available" => Ok(EventTopic::ExecutionPayloadAvailable),
"execution_payload_bid" => Ok(EventTopic::ExecutionPayloadBid),
"payload_attestation_message" => Ok(EventTopic::PayloadAttestationMessage),
_ => Err("event topic cannot be parsed.".to_string()),
}
}
@@ -1426,13 +1518,20 @@ impl fmt::Display for EventTopic {
EventTopic::LateHead => write!(f, "late_head"),
EventTopic::LightClientFinalityUpdate => write!(f, "light_client_finality_update"),
EventTopic::LightClientOptimisticUpdate => write!(f, "light_client_optimistic_update"),
#[cfg(feature = "lighthouse")]
EventTopic::BlockReward => write!(f, "block_reward"),
EventTopic::AttesterSlashing => write!(f, "attester_slashing"),
EventTopic::ProposerSlashing => write!(f, "proposer_slashing"),
EventTopic::BlsToExecutionChange => write!(f, "bls_to_execution_change"),
EventTopic::BlockGossip => write!(f, "block_gossip"),
EventTopic::InclusionList => write!(f, "inclusion_list",),
EventTopic::InclusionList => write!(f, "inclusion_list"),
EventTopic::ExecutionPayload => write!(f, "execution_payload"),
EventTopic::ExecutionPayloadGossip => write!(f, "execution_payload_gossip"),
EventTopic::ExecutionPayloadAvailable => {
write!(f, "execution_payload_available")
}
EventTopic::ExecutionPayloadBid => write!(f, "execution_payload_bid"),
EventTopic::PayloadAttestationMessage => {
write!(f, "payload_attestation_message")
}
}
}
}
@@ -1621,7 +1720,7 @@ pub struct BroadcastValidationQuery {
}
pub mod serde_status_code {
use crate::StatusCode;
use reqwest::StatusCode;
use serde::{Deserialize, Serialize, de::Error};
pub fn serialize<S>(status_code: &StatusCode, ser: S) -> Result<S::Ok, S::Error>
@@ -1740,7 +1839,7 @@ pub type JsonProduceBlockV3Response<E> =
pub enum FullBlockContents<E: EthSpec> {
/// This is a full deneb variant with block and blobs.
BlockContents(BlockContents<E>),
/// This variant is for all pre-deneb full blocks.
/// This variant is for all pre-deneb full blocks or post-gloas beacon block.
Block(BeaconBlock<E>),
}
@@ -1768,6 +1867,20 @@ pub struct ProduceBlockV3Metadata {
pub consensus_block_value: Uint256,
}
/// Metadata about a `produce_block_v4` response which is returned in the body & headers.
#[derive(Debug, Deserialize, Serialize)]
pub struct ProduceBlockV4Metadata {
// The consensus version is serialized & deserialized by `ForkVersionedResponse`.
#[serde(
skip_serializing,
skip_deserializing,
default = "dummy_consensus_version"
)]
pub consensus_version: ForkName,
#[serde(with = "serde_utils::u256_dec")]
pub consensus_block_value: Uint256,
}
impl<E: EthSpec> FullBlockContents<E> {
pub fn new(block: BeaconBlock<E>, blob_data: Option<(KzgProofs<E>, BlobsList<E>)>) -> Self {
match blob_data {
@@ -1924,6 +2037,27 @@ impl TryFrom<&HeaderMap> for ProduceBlockV3Metadata {
}
}
impl TryFrom<&HeaderMap> for ProduceBlockV4Metadata {
type Error = String;
fn try_from(headers: &HeaderMap) -> Result<Self, Self::Error> {
let consensus_version = parse_required_header(headers, CONSENSUS_VERSION_HEADER, |s| {
s.parse::<ForkName>()
.map_err(|e| format!("invalid {CONSENSUS_VERSION_HEADER}: {e:?}"))
})?;
let consensus_block_value =
parse_required_header(headers, CONSENSUS_BLOCK_VALUE_HEADER, |s| {
Uint256::from_str_radix(s, 10)
.map_err(|e| format!("invalid {CONSENSUS_BLOCK_VALUE_HEADER}: {e:?}"))
})?;
Ok(ProduceBlockV4Metadata {
consensus_version,
consensus_block_value,
})
}
}
/// A wrapper over a [`SignedBeaconBlock`] or a [`SignedBlockContents`].
#[derive(Clone, Debug, PartialEq, Encode, Serialize)]
#[serde(untagged)]
@@ -1971,7 +2105,7 @@ impl<E: EthSpec> PublishBlockRequest<E> {
/// SSZ decode with fork variant determined by `fork_name`.
pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result<Self, DecodeError> {
if fork_name.deneb_enabled() {
if fork_name.deneb_enabled() && !fork_name.gloas_enabled() {
let mut builder = ssz::SszDecoderBuilder::new(bytes);
builder.register_anonymous_variable_length_item()?;
builder.register_type::<KzgProofs<E>>()?;