mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 12:47:05 +00:00
Empty list [] to return all validators balances (#7474)
The endpoint `/eth/v1/beacon/states/head/validator_balances` returns an empty data when the data field is `[]`. According to the beacon API spec, it should return the balances of all validators: Reference: https://ethereum.github.io/beacon-APIs/#/Beacon/postStateValidatorBalances `If the supplied list is empty (i.e. the body is []) or no body is supplied then balances will be returned for all validators.` This PR changes so that: `curl -X 'POST' 'http://localhost:5052/eth/v1/beacon/states/head/validator_balances' -d '[]' | jq` returns balances of all validators.
This commit is contained in:
@@ -709,7 +709,7 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
.clone()
|
.clone()
|
||||||
.and(warp::path("validator_balances"))
|
.and(warp::path("validator_balances"))
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
.and(warp_utils::json::json())
|
.and(warp_utils::json::json_no_body())
|
||||||
.then(
|
.then(
|
||||||
|state_id: StateId,
|
|state_id: StateId,
|
||||||
task_spawner: TaskSpawner<T::EthSpec>,
|
task_spawner: TaskSpawner<T::EthSpec>,
|
||||||
|
|||||||
@@ -81,8 +81,13 @@ pub fn get_beacon_state_validator_balances<T: BeaconChainTypes>(
|
|||||||
.map_state_and_execution_optimistic_and_finalized(
|
.map_state_and_execution_optimistic_and_finalized(
|
||||||
&chain,
|
&chain,
|
||||||
|state, execution_optimistic, finalized| {
|
|state, execution_optimistic, finalized| {
|
||||||
let ids_filter_set: Option<HashSet<&ValidatorId>> =
|
let ids_filter_set: Option<HashSet<&ValidatorId>> = match optional_ids {
|
||||||
optional_ids.map(|f| HashSet::from_iter(f.iter()));
|
// if optional_ids (the request data body) is [], returns a `None`, so that later when calling .is_none_or() will return True
|
||||||
|
// Hence, all validators will pass through .filter(), and balances of all validators are returned, in accordance to the spec
|
||||||
|
Some([]) => None,
|
||||||
|
Some(ids) => Some(HashSet::from_iter(ids.iter())),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
state
|
state
|
||||||
|
|||||||
@@ -927,6 +927,19 @@ impl ApiTester {
|
|||||||
.map(|res| res.data);
|
.map(|res| res.data);
|
||||||
|
|
||||||
let expected = state_opt.map(|(state, _execution_optimistic, _finalized)| {
|
let expected = state_opt.map(|(state, _execution_optimistic, _finalized)| {
|
||||||
|
// If validator_indices is empty, return balances for all validators
|
||||||
|
if validator_indices.is_empty() {
|
||||||
|
state
|
||||||
|
.balances()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, balance)| ValidatorBalanceData {
|
||||||
|
index: index as u64,
|
||||||
|
balance: *balance,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
// Same behaviour as before for the else branch
|
||||||
let mut validators = Vec::with_capacity(validator_indices.len());
|
let mut validators = Vec::with_capacity(validator_indices.len());
|
||||||
|
|
||||||
for i in validator_indices {
|
for i in validator_indices {
|
||||||
@@ -939,6 +952,7 @@ impl ApiTester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validators
|
validators
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(result_index_ids, expected, "{:?}", state_id);
|
assert_eq!(result_index_ids, expected, "{:?}", state_id);
|
||||||
|
|||||||
@@ -688,7 +688,7 @@ pub struct ValidatorBalancesQuery {
|
|||||||
pub id: Option<Vec<ValidatorId>>,
|
pub id: Option<Vec<ValidatorId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Default, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ValidatorBalancesRequestBody {
|
pub struct ValidatorBalancesRequestBody {
|
||||||
pub ids: Vec<ValidatorId>,
|
pub ids: Vec<ValidatorId>,
|
||||||
|
|||||||
@@ -31,3 +31,27 @@ pub fn json<T: DeserializeOwned + Send>() -> impl Filter<Extract = (T,), Error =
|
|||||||
.map_err(|err| reject::custom_deserialize_error(format!("{:?}", err)))
|
.map_err(|err| reject::custom_deserialize_error(format!("{:?}", err)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a json_no_body function to handle the case when no body is provided in the HTTP request
|
||||||
|
pub fn json_no_body<T: DeserializeOwned + Default + Send>(
|
||||||
|
) -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
|
||||||
|
warp::header::optional::<String>(CONTENT_TYPE_HEADER)
|
||||||
|
.and(warp::body::bytes())
|
||||||
|
.and_then(|header: Option<String>, bytes: Bytes| async move {
|
||||||
|
if let Some(header) = header {
|
||||||
|
if header == SSZ_CONTENT_TYPE_HEADER {
|
||||||
|
return Err(reject::unsupported_media_type(
|
||||||
|
"The request's content-type is not supported".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the case when the HTTP request has no body, i.e., without the -d header
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return Ok(T::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::decode(bytes)
|
||||||
|
.map_err(|err| reject::custom_deserialize_error(format!("{:?}", err)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user