Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion http_api/src/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ use crate::{
validator_attestation_data, validator_attester_duties,
validator_beacon_committee_selections, validator_blinded_block, validator_block,
validator_block_v3, validator_execution_payload_bid, validator_liveness,
validator_prepare_beacon_proposer, validator_proposer_duties, validator_proposer_duties_v2,
validator_payload_attestation_data, validator_prepare_beacon_proposer,
validator_proposer_duties, validator_proposer_duties_v2,
validator_publish_aggregate_and_proofs_v1, validator_publish_aggregate_and_proofs_v2,
validator_publish_contributions_and_proofs, validator_register_validator,
validator_subscribe_to_beacon_committee, validator_subscribe_to_sync_committees,
Expand Down Expand Up @@ -603,6 +604,10 @@ fn eth_v1_validator_routes<P: Preset, W: Wait>(
"/eth/v1/validator/execution_payload_bid/{slot}/{builder_index}",
get(validator_execution_payload_bid),
)
.route(
"/eth/v1/validator/payload_attestation_data/{slot}",
get(validator_payload_attestation_data),
)
.layer(axum::middleware::map_request_with_state(
state,
middleware::is_synced,
Expand Down
118 changes: 117 additions & 1 deletion http_api/src/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ use types::{
primitives::ColumnIndex,
},
gloas::{
containers::{ExecutionPayloadBid, SignedExecutionPayloadBid},
containers::{ExecutionPayloadBid, PayloadAttestationData, SignedExecutionPayloadBid},
primitives::BuilderIndex,
},
nonstandard::{
Expand Down Expand Up @@ -3534,6 +3534,122 @@ pub async fn validator_execution_payload_bid<P: Preset, W: Wait>(
Ok(EthResponse::json_or_ssz(signed_bid.message.clone(), &headers)?.version(version))
}

/// `GET /eth/v1/validator/payload_attestation_data/{slot}`
#[instrument(
parent = None,
level = "trace",
fields(
service = "http_api",
slot = %slot,
),
name = "http_api",
skip_all,
)]
pub async fn validator_payload_attestation_data<P: Preset, W: Wait>(
State(controller): State<ApiController<P, W>>,
State(event_channels): State<Arc<EventChannels<P>>>,
State(metrics): State<Option<Arc<Metrics>>>,
State(validator_config): State<Arc<ValidatorConfig>>,
EthPath(slot): EthPath<Slot>,
headers: HeaderMap,
) -> Result<EthResponse<PayloadAttestationData, (), JsonOrSsz>, Error> {
const BLOCK_EVENT_WAIT_TIMEOUT: Duration = Duration::from_secs(1);

let _timer = metrics.map(|metrics| {
metrics
.validator_api_payload_attestation_data_times
.start_timer()
});

let WithStatus {
value: head,
status,
..
} = controller.head();

let head_slot = head.slot();
let max_empty_slots = validator_config.max_empty_slots;

if head_slot + max_empty_slots < slot {
return Err(Error::HeadFarBehind {
head_slot,
max_empty_slots,
slot,
});
}

let requested_epoch = misc::compute_epoch_at_slot::<P>(slot);
let previous_epoch = misc::previous_epoch(misc::compute_epoch_at_slot::<P>(head_slot));

// Prevent DoS attacks by limiting how far in the past the attested block can be searched.
if requested_epoch < previous_epoch {
return Err(Error::EpochBeforePrevious);
}

let block_root;
let mut state;

let is_optimistic = if slot < head_slot {
// Search for the latest canonical block before or at slot.
let block = controller
.block_by_slot(slot)?
.ok_or(Error::BlockNotFound)?;

block_root = block.value.root;
state = controller
.state_before_or_at_slot(block_root, slot)
.ok_or(Error::StateNotFound)?;
block.status.is_optimistic()
} else {
block_root = head.block_root;
state = controller.state_by_chain_link(&head);
status.is_optimistic()
};

if is_optimistic
&& let Err(error) = timeout(BLOCK_EVENT_WAIT_TIMEOUT, async {
loop {
let block_event = match event_channels.receiver_for(Topic::Block).recv().await {
Ok(Event::Block(block_event)) => block_event,
Ok(_) => continue,
Err(error) => {
debug_with_peers!("error receiving block event: {error:?}");
continue;
}
};

if block_event.block == block_root && !block_event.execution_optimistic {
break;
}
}
})
.await
{
debug_with_peers!("timeout while waiting for block event: {error:?}");
return Err(Error::HeadIsOptimistic);
}

if state.slot() < slot {
state = tokio::task::spawn_blocking(move || {
controller.preprocessed_state_post_block_blocking(block_root, slot)
})
.await?
.map_err(Error::UnableToProduceAttestation)?;
}

let version = state.phase();
let payload_attestation_data = PayloadAttestationData {
slot,
beacon_block_root: block_root,
// TODO: (gloas): set to `true` if signed envelope reference by `block_root` has been seen in fork choice
payload_present: true,
// TODO: (gloas): set to `true` if blob data is available defined by fork choice
blob_data_available: true,
};

Ok(EthResponse::json_or_ssz(payload_attestation_data, &headers)?.version(version))
}

/// `POST /eth/v1/validator/aggregate_and_proofs`
///
/// This deviates from [the specification] by returning errors as [`IndexedError`].
Expand Down
12 changes: 12 additions & 0 deletions prometheus_metrics/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ pub struct Metrics {
// eth/v1/validator/attestation_data
pub validator_api_attestation_data_times: Histogram,

// eth/v1/validator/payload_attestation_data
pub validator_api_payload_attestation_data_times: Histogram,

// Blocks
pub validator_propose_times: Histogram,
pub validator_propose_successes: IntCounter,
Expand Down Expand Up @@ -517,6 +520,12 @@ impl Metrics {
"Singular attestation data production times in HTTP API",
))?,

// eth/v1/validator/payload_attestation_data
validator_api_payload_attestation_data_times: Histogram::with_opts(histogram_opts!(
"VALIDATOR_API_PAYLOAD_ATTESTATION_DATA_TIMES",
"Payload attestation data production times in HTTP API",
))?,

// Blocks
validator_propose_times: Histogram::with_opts(histogram_opts!(
"VALIDATOR_PROPOSE_TIMES",
Expand Down Expand Up @@ -960,6 +969,9 @@ impl Metrics {
self.validator_attest_slashing_protector_times.clone(),
))?;
default_registry.register(Box::new(self.validator_api_attestation_data_times.clone()))?;
default_registry.register(Box::new(
self.validator_api_payload_attestation_data_times.clone(),
))?;
default_registry.register(Box::new(self.validator_propose_times.clone()))?;
default_registry.register(Box::new(self.validator_propose_successes.clone()))?;
default_registry.register(Box::new(
Expand Down
Loading