Skip to content

Commit cef04ee

Browse files
authored
Implement validator_identities Beacon API endpoint (#7462)
* #7442
1 parent 3fefda6 commit cef04ee

File tree

5 files changed

+200
-2
lines changed

5 files changed

+200
-2
lines changed

beacon_node/http_api/src/lib.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ use directory::DEFAULT_ROOT_DIR;
4848
use eth2::types::{
4949
self as api_types, BroadcastValidation, ContextDeserialize, EndpointVersion, ForkChoice,
5050
ForkChoiceNode, LightClientUpdatesQuery, PublishBlockRequest, StateId as CoreStateId,
51-
ValidatorBalancesRequestBody, ValidatorId, ValidatorStatus, ValidatorsRequestBody,
51+
ValidatorBalancesRequestBody, ValidatorId, ValidatorIdentitiesRequestBody, ValidatorStatus,
52+
ValidatorsRequestBody,
5253
};
5354
use eth2::{CONSENSUS_VERSION_HEADER, CONTENT_TYPE_HEADER, SSZ_CONTENT_TYPE_HEADER};
5455
use health_metrics::observe::Observe;
@@ -702,6 +703,34 @@ pub fn serve<T: BeaconChainTypes>(
702703
},
703704
);
704705

706+
// POST beacon/states/{state_id}/validator_identities
707+
let post_beacon_state_validator_identities = beacon_states_path
708+
.clone()
709+
.and(warp::path("validator_identities"))
710+
.and(warp::path::end())
711+
.and(warp_utils::json::json_no_body())
712+
.then(
713+
|state_id: StateId,
714+
task_spawner: TaskSpawner<T::EthSpec>,
715+
chain: Arc<BeaconChain<T>>,
716+
query: ValidatorIdentitiesRequestBody| {
717+
// Prioritise requests for validators at the head. These should be fast to service
718+
// and could be required by the validator client.
719+
let priority = if let StateId(eth2::types::StateId::Head) = state_id {
720+
Priority::P0
721+
} else {
722+
Priority::P1
723+
};
724+
task_spawner.blocking_json_task(priority, move || {
725+
crate::validators::get_beacon_state_validator_identities(
726+
state_id,
727+
chain,
728+
Some(&query.ids),
729+
)
730+
})
731+
},
732+
);
733+
705734
// GET beacon/states/{state_id}/validators?id,status
706735
let get_beacon_state_validators = beacon_states_path
707736
.clone()
@@ -4852,6 +4881,7 @@ pub fn serve<T: BeaconChainTypes>(
48524881
.uor(post_beacon_pool_bls_to_execution_changes)
48534882
.uor(post_beacon_state_validators)
48544883
.uor(post_beacon_state_validator_balances)
4884+
.uor(post_beacon_state_validator_identities)
48554885
.uor(post_beacon_rewards_attestations)
48564886
.uor(post_beacon_rewards_sync_committee)
48574887
.uor(post_validator_duties_attester)

beacon_node/http_api/src/validators.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::state_id::StateId;
22
use beacon_chain::{BeaconChain, BeaconChainTypes};
33
use eth2::types::{
44
self as api_types, ExecutionOptimisticFinalizedResponse, ValidatorBalanceData, ValidatorData,
5-
ValidatorId, ValidatorStatus,
5+
ValidatorId, ValidatorIdentityData, ValidatorStatus,
66
};
77
use std::{collections::HashSet, sync::Arc};
88

@@ -119,3 +119,51 @@ pub fn get_beacon_state_validator_balances<T: BeaconChainTypes>(
119119
finalized: Some(finalized),
120120
})
121121
}
122+
123+
pub fn get_beacon_state_validator_identities<T: BeaconChainTypes>(
124+
state_id: StateId,
125+
chain: Arc<BeaconChain<T>>,
126+
optional_ids: Option<&[ValidatorId]>,
127+
) -> Result<ExecutionOptimisticFinalizedResponse<Vec<ValidatorIdentityData>>, warp::Rejection> {
128+
let (data, execution_optimistic, finalized) = state_id
129+
.map_state_and_execution_optimistic_and_finalized(
130+
&chain,
131+
|state, execution_optimistic, finalized| {
132+
let ids_filter_set: Option<HashSet<&ValidatorId>> = match optional_ids {
133+
// Same logic as validator_balances endpoint above
134+
Some([]) => None,
135+
Some(ids) => Some(HashSet::from_iter(ids.iter())),
136+
None => None,
137+
};
138+
139+
Ok((
140+
// From the BeaconState, extract the Validator data and convert it into ValidatorIdentityData type
141+
state
142+
.validators()
143+
.iter()
144+
.enumerate()
145+
// filter by validator id(s) if provided
146+
.filter(|(index, validator)| {
147+
ids_filter_set.as_ref().is_none_or(|ids_set| {
148+
ids_set.contains(&ValidatorId::PublicKey(validator.pubkey))
149+
|| ids_set.contains(&ValidatorId::Index(*index as u64))
150+
})
151+
})
152+
.map(|(index, validator)| ValidatorIdentityData {
153+
index: index as u64,
154+
pubkey: validator.pubkey,
155+
activation_epoch: validator.activation_epoch,
156+
})
157+
.collect::<Vec<_>>(),
158+
execution_optimistic,
159+
finalized,
160+
))
161+
},
162+
)?;
163+
164+
Ok(api_types::ExecutionOptimisticFinalizedResponse {
165+
data,
166+
execution_optimistic: Some(execution_optimistic),
167+
finalized: Some(finalized),
168+
})
169+
}

beacon_node/http_api/tests/tests.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,87 @@ impl ApiTester {
964964
self
965965
}
966966

967+
pub async fn test_beacon_states_validator_identities(self) -> Self {
968+
for state_id in self.interesting_state_ids() {
969+
for validator_indices in self.interesting_validator_indices() {
970+
let state_opt = state_id.state(&self.chain).ok();
971+
let validators: Vec<Validator> = match state_opt.as_ref() {
972+
Some((state, _execution_optimistic, _finalized)) => {
973+
state.validators().clone().to_vec()
974+
}
975+
None => vec![],
976+
};
977+
978+
let validator_index_ids = validator_indices
979+
.iter()
980+
.cloned()
981+
.map(ValidatorId::Index)
982+
.collect::<Vec<ValidatorId>>();
983+
984+
let validator_pubkey_ids = validator_indices
985+
.iter()
986+
.cloned()
987+
.map(|i| {
988+
ValidatorId::PublicKey(
989+
validators
990+
.get(i as usize)
991+
.map_or(PublicKeyBytes::empty(), |val| val.pubkey),
992+
)
993+
})
994+
.collect::<Vec<ValidatorId>>();
995+
996+
let result_index_ids = self
997+
.client
998+
.post_beacon_states_validator_identities(state_id.0, validator_index_ids)
999+
.await
1000+
.unwrap()
1001+
.map(|res| res.data);
1002+
let result_pubkey_ids = self
1003+
.client
1004+
.post_beacon_states_validator_identities(state_id.0, validator_pubkey_ids)
1005+
.await
1006+
.unwrap()
1007+
.map(|res| res.data);
1008+
1009+
let expected = state_opt.map(|(state, _execution_optimistic, _finalized)| {
1010+
// If validator_indices is empty, return identities for all validators
1011+
if validator_indices.is_empty() {
1012+
state
1013+
.validators()
1014+
.iter()
1015+
.enumerate()
1016+
.map(|(index, validator)| ValidatorIdentityData {
1017+
index: index as u64,
1018+
pubkey: validator.pubkey,
1019+
activation_epoch: validator.activation_epoch,
1020+
})
1021+
.collect()
1022+
} else {
1023+
let mut validators = Vec::with_capacity(validator_indices.len());
1024+
1025+
for i in validator_indices {
1026+
if i < state.validators().len() as u64 {
1027+
// access each validator, and then transform the data into ValidatorIdentityData
1028+
let validator = state.validators().get(i as usize).unwrap();
1029+
validators.push(ValidatorIdentityData {
1030+
index: i,
1031+
pubkey: validator.pubkey,
1032+
activation_epoch: validator.activation_epoch,
1033+
});
1034+
}
1035+
}
1036+
1037+
validators
1038+
}
1039+
});
1040+
1041+
assert_eq!(result_index_ids, expected, "{:?}", state_id);
1042+
assert_eq!(result_pubkey_ids, expected, "{:?}", state_id);
1043+
}
1044+
}
1045+
self
1046+
}
1047+
9671048
pub async fn test_beacon_states_validators(self) -> Self {
9681049
for state_id in self.interesting_state_ids() {
9691050
for statuses in self.interesting_validator_statuses() {
@@ -6685,6 +6766,8 @@ async fn beacon_get_state_info() {
66856766
.await
66866767
.test_beacon_states_validator_balances()
66876768
.await
6769+
.test_beacon_states_validator_identities()
6770+
.await
66886771
.test_beacon_states_committees()
66896772
.await
66906773
.test_beacon_states_validator_id()

common/eth2/src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,29 @@ impl BeaconNodeHttpClient {
701701
self.post_with_opt_response(path, &request).await
702702
}
703703

704+
/// `POST beacon/states/{state_id}/validator_identities`
705+
///
706+
/// Returns `Ok(None)` on a 404 error.
707+
pub async fn post_beacon_states_validator_identities(
708+
&self,
709+
state_id: StateId,
710+
ids: Vec<ValidatorId>,
711+
) -> Result<Option<ExecutionOptimisticFinalizedResponse<Vec<ValidatorIdentityData>>>, Error>
712+
{
713+
let mut path = self.eth_path(V1)?;
714+
715+
path.path_segments_mut()
716+
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
717+
.push("beacon")
718+
.push("states")
719+
.push(&state_id.to_string())
720+
.push("validator_identities");
721+
722+
let request = ValidatorIdentitiesRequestBody { ids };
723+
724+
self.post_with_opt_response(path, &request).await
725+
}
726+
704727
/// `GET beacon/states/{state_id}/validators?id,status`
705728
///
706729
/// Returns `Ok(None)` on a 404 error.

common/eth2/src/types.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,14 @@ pub struct ValidatorBalanceData {
349349
pub balance: u64,
350350
}
351351

352+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
353+
pub struct ValidatorIdentityData {
354+
#[serde(with = "serde_utils::quoted_u64")]
355+
pub index: u64,
356+
pub pubkey: PublicKeyBytes,
357+
pub activation_epoch: Epoch,
358+
}
359+
352360
// Implemented according to what is described here:
353361
//
354362
// https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ
@@ -694,6 +702,12 @@ pub struct ValidatorBalancesRequestBody {
694702
pub ids: Vec<ValidatorId>,
695703
}
696704

705+
#[derive(Clone, Default, Serialize, Deserialize)]
706+
#[serde(transparent)]
707+
pub struct ValidatorIdentitiesRequestBody {
708+
pub ids: Vec<ValidatorId>,
709+
}
710+
697711
#[derive(Clone, Deserialize)]
698712
#[serde(deny_unknown_fields)]
699713
pub struct BlobIndicesQuery {

0 commit comments

Comments
 (0)