add HTTP API to retrieve validator IL duties

This commit is contained in:
jacobkaufmann
2024-12-11 20:49:34 -07:00
parent ba336501f6
commit 2b3c602b8f
8 changed files with 210 additions and 1 deletions

View File

@@ -0,0 +1,97 @@
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes};
use eth2::types::{self as api_types};
use slot_clock::SlotClock;
use types::{Epoch, EthSpec, Hash256, InclusionListDuty};
/// The struct that is returned to the requesting HTTP client.
type ApiDuties = api_types::DutiesResponse<Vec<api_types::InclusionListDutyData>>;
/// Handles a request from the HTTP API for inclusion list duties.
pub fn inclusion_list_duties<T: BeaconChainTypes>(
request_epoch: Epoch,
request_indices: &[u64],
chain: &BeaconChain<T>,
) -> Result<ApiDuties, warp::reject::Rejection> {
let current_epoch = chain
.epoch()
.map_err(warp_utils::reject::beacon_chain_error)?;
// Determine what the current epoch would be if we fast-forward our system clock by
// `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
//
// Most of the time, `tolerant_current_epoch` will be equal to `current_epoch`. However, during
// the first `MAXIMUM_GOSSIP_CLOCK_DISPARITY` duration of the epoch `tolerant_current_epoch`
// will equal `current_epoch + 1`
let tolerant_current_epoch = chain
.slot_clock
.now_with_future_tolerance(chain.spec.maximum_gossip_clock_disparity())
.ok_or_else(|| warp_utils::reject::custom_server_error("unable to read slot clock".into()))?
.epoch(T::EthSpec::slots_per_epoch());
if request_epoch == current_epoch
|| request_epoch == current_epoch + 1
|| request_epoch == tolerant_current_epoch + 1
{
let head_block_root = chain.canonical_head.cached_head().head_block_root();
let (duties, dependent_root) = chain
.validator_inclusion_list_duties(request_indices, request_epoch, head_block_root)
.map_err(warp_utils::reject::beacon_chain_error)?;
convert_to_api_response(duties, request_indices, dependent_root, chain)
} else if request_epoch > current_epoch + 1 {
Err(warp_utils::reject::custom_bad_request(format!(
"request epoch {} is more than one epoch past the current epoch {}",
request_epoch, current_epoch
)))
} else {
// request_epoch < current_epoch
//
// TODO: support historical inclusion list duties requests
Err(warp_utils::reject::custom_bad_request(format!(
"request epoch {} is earlier than the current epoch {}",
request_epoch, current_epoch
)))
}
}
/// Convert the internal representation of attester duties into the format returned to the HTTP
/// client.
fn convert_to_api_response<T: BeaconChainTypes>(
duties: Vec<Option<InclusionListDuty>>,
indices: &[u64],
dependent_root: Hash256,
chain: &BeaconChain<T>,
) -> Result<ApiDuties, warp::reject::Rejection> {
// Protect against an inconsistent slot clock.
if duties.len() != indices.len() {
return Err(warp_utils::reject::custom_server_error(format!(
"duties length {} does not match indices length {}",
duties.len(),
indices.len()
)));
}
let usize_indices = indices.iter().map(|i| *i as usize).collect::<Vec<_>>();
let index_to_pubkey_map = chain
.validator_pubkey_bytes_many(&usize_indices)
.map_err(warp_utils::reject::beacon_chain_error)?;
let data = duties
.into_iter()
.zip(indices)
.filter_map(|(duty_opt, &validator_index)| {
let duty = duty_opt?;
Some(api_types::InclusionListDutyData {
validator_index,
pubkey: *index_to_pubkey_map.get(&(validator_index as usize))?,
slot: duty.slot,
})
})
.collect::<Vec<_>>();
// TODO: account for optimistic execution
Ok(api_types::DutiesResponse {
dependent_root,
execution_optimistic: None,
data,
})
}

View File

@@ -13,6 +13,7 @@ mod block_rewards;
mod build_block_contents;
mod builder_states;
mod database;
mod inclusion_list_duties;
mod light_client;
mod metrics;
mod produce_block;
@@ -257,6 +258,7 @@ pub fn prometheus_metrics() -> warp::filters::log::Log<impl Fn(warp::filters::lo
.or_else(|| starts_with("v1/validator/duties/attester"))
.or_else(|| starts_with("v1/validator/duties/proposer"))
.or_else(|| starts_with("v1/validator/duties/sync"))
.or_else(|| starts_with("v1/validator/duties/inclusion_list"))
.or_else(|| starts_with("v1/validator/attestation_data"))
.or_else(|| starts_with("v1/validator/aggregate_attestation"))
.or_else(|| starts_with("v2/validator/aggregate_attestation"))
@@ -3432,6 +3434,34 @@ pub fn serve<T: BeaconChainTypes>(
},
);
// POST validator/duties/inclusion_list/{epoch}
let post_validator_duties_inclusion_list = eth_v1
.and(warp::path("validator"))
.and(warp::path("duties"))
.and(warp::path("inclusion_list"))
.and(warp::path::param::<Epoch>().or_else(|_| async {
Err(warp_utils::reject::custom_bad_request(
"Invalid epoch".to_string(),
))
}))
.and(warp::path::end())
.and(not_while_syncing_filter.clone())
.and(warp_utils::json::json())
.and(task_spawner_filter.clone())
.and(chain_filter.clone())
.then(
|epoch: Epoch,
not_synced_filter: Result<(), Rejection>,
indices: api_types::ValidatorIndexData,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>| {
task_spawner.blocking_json_task(Priority::P0, move || {
not_synced_filter?;
inclusion_list_duties::inclusion_list_duties(epoch, &indices.0, &chain)
})
},
);
// GET validator/sync_committee_contribution
let get_validator_sync_committee_contribution = eth_v1
.and(warp::path("validator"))
@@ -4743,6 +4773,7 @@ pub fn serve<T: BeaconChainTypes>(
.uor(post_beacon_rewards_sync_committee)
.uor(post_validator_duties_attester)
.uor(post_validator_duties_sync)
.uor(post_validator_duties_inclusion_list)
.uor(post_validator_aggregate_and_proofs)
.uor(post_validator_contribution_and_proofs)
.uor(post_validator_beacon_committee_subscriptions)