Skip to content
24 changes: 23 additions & 1 deletion beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ use either::Either;
use eth2::types::{
self as api_types, BroadcastValidation, EndpointVersion, ForkChoice, ForkChoiceNode,
LightClientUpdatesQuery, PublishBlockRequest, ValidatorBalancesRequestBody, ValidatorId,
ValidatorStatus, ValidatorsRequestBody,
ValidatorIdentitiesRequestBody, ValidatorStatus, ValidatorsRequestBody,
};
use eth2::{CONSENSUS_VERSION_HEADER, CONTENT_TYPE_HEADER, SSZ_CONTENT_TYPE_HEADER};
use health_metrics::observe::Observe;
Expand Down Expand Up @@ -726,6 +726,27 @@ pub fn serve<T: BeaconChainTypes>(
},
);

// POST beacon/states/{state_id}/validator_identities
let post_beacon_state_validator_identities = beacon_states_path
.clone()
.and(warp::path("validator_identities"))
.and(warp::path::end())
.and(warp_utils::json::json())
.then(
|state_id: StateId,
task_spawner: TaskSpawner<T::EthSpec>,
chain: Arc<BeaconChain<T>>,
query: ValidatorIdentitiesRequestBody| {
task_spawner.blocking_json_task(Priority::P1, move || {
crate::validators::get_beacon_state_validator_identities(
state_id,
chain,
Some(&query.ids),
)
})
},
);

// GET beacon/states/{state_id}/validators?id,status
let get_beacon_state_validators = beacon_states_path
.clone()
Expand Down Expand Up @@ -4923,6 +4944,7 @@ pub fn serve<T: BeaconChainTypes>(
.uor(post_beacon_pool_bls_to_execution_changes)
.uor(post_beacon_state_validators)
.uor(post_beacon_state_validator_balances)
.uor(post_beacon_state_validator_identities)
.uor(post_beacon_rewards_attestations)
.uor(post_beacon_rewards_sync_committee)
.uor(post_validator_duties_attester)
Expand Down
46 changes: 45 additions & 1 deletion beacon_node/http_api/src/validators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::state_id::StateId;
use beacon_chain::{BeaconChain, BeaconChainTypes};
use eth2::types::{
self as api_types, ExecutionOptimisticFinalizedResponse, ValidatorBalanceData, ValidatorData,
ValidatorId, ValidatorStatus,
ValidatorId, ValidatorIdentityData, ValidatorStatus,
};
use std::{collections::HashSet, sync::Arc};

Expand Down Expand Up @@ -114,3 +114,47 @@ pub fn get_beacon_state_validator_balances<T: BeaconChainTypes>(
finalized: Some(finalized),
})
}

pub fn get_beacon_state_validator_identities<T: BeaconChainTypes>(
state_id: StateId,
chain: Arc<BeaconChain<T>>,
optional_ids: Option<&[ValidatorId]>,
) -> Result<ExecutionOptimisticFinalizedResponse<Vec<ValidatorIdentityData>>, warp::Rejection> {
let (data, execution_optimistic, finalized) = state_id
.map_state_and_execution_optimistic_and_finalized(
&chain,
|state, execution_optimistic, finalized| {
let ids_filter_set: Option<HashSet<&ValidatorId>> =
optional_ids.map(|f| HashSet::from_iter(f.iter()));

Ok((
// From the BeaconState, extract the Validator data and convert it into ValidatorIdentityData type
state
.validators()
.iter()
.enumerate()
// filter by validator id(s) if provided
.filter(|(index, validator)| {
ids_filter_set.as_ref().is_none_or(|ids_set| {
ids_set.contains(&ValidatorId::PublicKey(validator.pubkey))
|| ids_set.contains(&ValidatorId::Index(*index as u64))
})
})
.map(|(index, validator)| ValidatorIdentityData {
index: index as u64,
pubkey: validator.pubkey,
activation_epoch: validator.activation_epoch,
})
.collect::<Vec<_>>(),
execution_optimistic,
finalized,
))
},
)?;

Ok(api_types::ExecutionOptimisticFinalizedResponse {
data,
execution_optimistic: Some(execution_optimistic),
finalized: Some(finalized),
})
}
69 changes: 69 additions & 0 deletions beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,73 @@ impl ApiTester {
self
}

pub async fn test_beacon_states_validator_identities(self) -> Self {
for state_id in self.interesting_state_ids() {
for validator_indices in self.interesting_validator_indices() {
let state_opt = state_id.state(&self.chain).ok();
let validators: Vec<Validator> = match state_opt.as_ref() {
Some((state, _execution_optimistic, _finalized)) => {
state.validators().clone().to_vec()
}
None => vec![],
};

let validator_index_ids = validator_indices
.iter()
.cloned()
.map(ValidatorId::Index)
.collect::<Vec<ValidatorId>>();

let validator_pubkey_ids = validator_indices
.iter()
.cloned()
.map(|i| {
ValidatorId::PublicKey(
validators
.get(i as usize)
.map_or(PublicKeyBytes::empty(), |val| val.pubkey),
)
})
.collect::<Vec<ValidatorId>>();

let result_index_ids = self
.client
.post_beacon_states_validator_identities(state_id.0, validator_index_ids)
.await
.unwrap()
.map(|res| res.data);
let result_pubkey_ids = self
.client
.post_beacon_states_validator_identities(state_id.0, validator_pubkey_ids)
.await
.unwrap()
.map(|res| res.data);

let expected = state_opt.map(|(state, _execution_optimistic, _finalized)| {
let mut validators = Vec::with_capacity(validator_indices.len());

for i in validator_indices {
if i < state.validators().len() as u64 {
// access each validator, and then transform the data into ValidatorIdentityData
let validator = state.validators().get(i as usize).unwrap();
validators.push(ValidatorIdentityData {
index: i,
pubkey: validator.pubkey,
activation_epoch: validator.activation_epoch,
});
}
}

validators
});

assert_eq!(result_index_ids, expected, "{:?}", state_id);
assert_eq!(result_pubkey_ids, expected, "{:?}", state_id);
}
}
self
}

pub async fn test_beacon_states_validators(self) -> Self {
for state_id in self.interesting_state_ids() {
for statuses in self.interesting_validator_statuses() {
Expand Down Expand Up @@ -6492,6 +6559,8 @@ async fn beacon_get_state_info() {
.await
.test_beacon_states_validator_balances()
.await
.test_beacon_states_validator_identities()
.await
.test_beacon_states_committees()
.await
.test_beacon_states_validator_id()
Expand Down
23 changes: 23 additions & 0 deletions common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,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.
Expand Down
14 changes: 14 additions & 0 deletions common/eth2/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,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
Expand Down Expand Up @@ -695,6 +703,12 @@ pub struct ValidatorBalancesRequestBody {
pub ids: Vec<ValidatorId>,
}

#[derive(Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ValidatorIdentitiesRequestBody {
pub ids: Vec<ValidatorId>,
}

#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BlobIndicesQuery {
Expand Down
Loading