Skip to content
Closed
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
105 changes: 67 additions & 38 deletions beacon_node/http_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,40 +402,69 @@ pub fn serve<T: BeaconChainTypes>(
})
});

// GET beacon/states/{state_id}/validators
// GET beacon/states/{state_id}/validators?id,status
let get_beacon_state_validators = beacon_states_path
.clone()
.and(warp::path("validators"))
.and(warp::query::<api_types::ValidatorsQuery>())
.and(warp::path::end())
.and_then(|state_id: StateId, chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || {
state_id
.map_state(&chain, |state| {
let epoch = state.current_epoch();
let finalized_epoch = state.finalized_checkpoint.epoch;
let far_future_epoch = chain.spec.far_future_epoch;

Ok(state
.validators
.iter()
.zip(state.balances.iter())
.enumerate()
.map(|(index, (validator, balance))| api_types::ValidatorData {
index: index as u64,
balance: *balance,
status: api_types::ValidatorStatus::from_validator(
Some(validator),
epoch,
finalized_epoch,
far_future_epoch,
),
validator: validator.clone(),
})
.collect::<Vec<_>>())
})
.map(api_types::GenericResponse::from)
})
});
.and_then(
|state_id: StateId, chain: Arc<BeaconChain<T>>, query: api_types::ValidatorsQuery| {
blocking_json_task(move || {
state_id
.map_state(&chain, |state| {
let epoch = state.current_epoch();
let finalized_epoch = state.finalized_checkpoint.epoch;
let far_future_epoch = chain.spec.far_future_epoch;

Ok(state
.validators
.iter()
.zip(state.balances.iter())
.enumerate()
// filter by validator id(s) if provided
.filter(|(index, (validator, _))| {
query.id.as_ref().map_or(true, |ids| {
ids.0.iter().any(|id| match id {
ValidatorId::PublicKey(pubkey) => {
&validator.pubkey == pubkey
}
ValidatorId::Index(param_index) => {
*param_index == *index as u64
}
})
})
})
// filter by status(es) if provided and map the result
.filter_map(|(index, (validator, balance))| {
let status = api_types::ValidatorStatus::from_validator(
Some(validator),
epoch,
finalized_epoch,
far_future_epoch,
);

if query
.status
.as_ref()
.map_or(true, |statuses| statuses.0.contains(&status))
{
Some(api_types::ValidatorData {
index: index as u64,
balance: *balance,
status,
validator: validator.clone(),
})
} else {
None
}
})
.collect::<Vec<_>>())
})
.map(api_types::GenericResponse::from)
})
},
);

// GET beacon/states/{state_id}/validators/{validator_id}
let get_beacon_state_validators_id = beacon_states_path
Expand Down Expand Up @@ -518,8 +547,8 @@ pub fn serve<T: BeaconChainTypes>(
} else {
CommitteeCache::initialized(state, epoch, &chain.spec).map(Cow::Owned)
}
.map_err(BeaconChainError::BeaconStateError)
.map_err(warp_utils::reject::beacon_chain_error)?;
.map_err(BeaconChainError::BeaconStateError)
.map_err(warp_utils::reject::beacon_chain_error)?;

// Use either the supplied slot or all slots in the epoch.
let slots = query.slot.map(|slot| vec![slot]).unwrap_or_else(|| {
Expand Down Expand Up @@ -547,11 +576,11 @@ pub fn serve<T: BeaconChainTypes>(
let committee = committee_cache
.get_beacon_committee(slot, index)
.ok_or_else(|| {
warp_utils::reject::custom_bad_request(format!(
"committee index {} does not exist in epoch {}",
index, epoch
))
})?;
warp_utils::reject::custom_bad_request(format!(
"committee index {} does not exist in epoch {}",
index, epoch
))
})?;

response.push(api_types::CommitteeData {
index,
Expand Down Expand Up @@ -1452,7 +1481,7 @@ pub fn serve<T: BeaconChainTypes>(
return Err(warp_utils::reject::object_invalid(format!(
"gossip verification failed: {:?}",
e
)))
)));
}
};

Expand Down
131 changes: 100 additions & 31 deletions beacon_node/http_api/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,40 +390,87 @@ impl ApiTester {

pub async fn test_beacon_states_validators(self) -> Self {
for state_id in self.interesting_state_ids() {
let result = self
.client
.get_beacon_states_validators(state_id)
.await
.unwrap()
.map(|res| res.data);
for statuses in self.interesting_validator_statuses() {
for validator_indices in self.interesting_validator_indices() {
let state_opt = self.get_state(state_id);
let validators: Vec<Validator> = match state_opt.as_ref() {
Some(state) => state.validators.clone().into(),
None => vec![],
};
let validator_index_ids = validator_indices
.iter()
.cloned()
.map(|i| ValidatorId::Index(i))
.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.clone()),
)
})
.collect::<Vec<ValidatorId>>();

let result_index_ids = self
.client
.get_beacon_states_validators(
state_id,
Some(validator_index_ids.as_slice()),
None,
)
.await
.unwrap()
.map(|res| res.data);

let expected = self.get_state(state_id).map(|state| {
let epoch = state.current_epoch();
let finalized_epoch = state.finalized_checkpoint.epoch;
let far_future_epoch = self.chain.spec.far_future_epoch;

let mut validators = Vec::with_capacity(state.validators.len());

for i in 0..state.validators.len() {
let validator = state.validators[i].clone();

validators.push(ValidatorData {
index: i as u64,
balance: state.balances[i],
status: ValidatorStatus::from_validator(
Some(&validator),
epoch,
finalized_epoch,
far_future_epoch,
),
validator,
})
}
let result_pubkey_ids = self
.client
.get_beacon_states_validators(
state_id,
Some(validator_pubkey_ids.as_slice()),
None,
)
.await
.unwrap()
.map(|res| res.data);

let expected = state_opt.map(|state| {
let epoch = state.current_epoch();
let finalized_epoch = state.finalized_checkpoint.epoch;
let far_future_epoch = self.chain.spec.far_future_epoch;

validators
});
let mut validators = Vec::with_capacity(validator_indices.len());

assert_eq!(result, expected, "{:?}", state_id);
for i in validator_indices {
if i >= state.validators.len() as u64 {
continue;
}
let validator = state.validators[i as usize].clone();
let status = ValidatorStatus::from_validator(
Some(&validator),
epoch,
finalized_epoch,
far_future_epoch,
);
if statuses.contains(&status) || statuses.is_empty() {
validators.push(ValidatorData {
index: i as u64,
balance: state.balances[i as usize],
status,
validator,
});
}
}

validators
});

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

self
Expand Down Expand Up @@ -1065,6 +1112,28 @@ impl ApiTester {
interesting
}

fn interesting_validator_statuses(&self) -> Vec<Vec<ValidatorStatus>> {
let interesting = vec![
vec![],
vec![ValidatorStatus::Active],
vec![
ValidatorStatus::Unknown,
ValidatorStatus::WaitingForEligibility,
ValidatorStatus::WaitingForFinality,
ValidatorStatus::WaitingInQueue,
ValidatorStatus::StandbyForActive,
ValidatorStatus::Active,
ValidatorStatus::ActiveAwaitingVoluntaryExit,
ValidatorStatus::ActiveAwaitingSlashedExit,
ValidatorStatus::ExitedVoluntarily,
ValidatorStatus::ExitedSlashed,
ValidatorStatus::Withdrawable,
ValidatorStatus::Withdrawn,
],
];
interesting
}

pub async fn test_get_validator_duties_attester(self) -> Self {
let current_epoch = self.chain.epoch().unwrap().as_u64();

Expand Down
22 changes: 21 additions & 1 deletion common/eth2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,14 @@ impl BeaconNodeHttpClient {
self.get_opt(path).await
}

/// `GET beacon/states/{state_id}/validators`
/// `GET beacon/states/{state_id}/validators?id,status`
///
/// Returns `Ok(None)` on a 404 error.
pub async fn get_beacon_states_validators(
&self,
state_id: StateId,
ids: Option<&[ValidatorId]>,
statuses: Option<&[ValidatorStatus]>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that giving Some(&[]) would yield no results. This seemed odd at first, but after giving it some thought I think it's my preference.

No change requested :)

) -> Result<Option<GenericResponse<Vec<ValidatorData>>>, Error> {
let mut path = self.eth_path()?;

Expand All @@ -222,6 +224,24 @@ impl BeaconNodeHttpClient {
.push(&state_id.to_string())
.push("validators");

if let Some(ids) = ids {
let id_string = ids
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(",");
path.query_pairs_mut().append_pair("id", &id_string);
}

if let Some(statuses) = statuses {
let status_string = statuses
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(",");
path.query_pairs_mut().append_pair("status", &status_string);
}

self.get_opt(path).await
}

Expand Down
Loading