Skip to content

Commit 7e2df6b

Browse files
authored
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.
1 parent 805c2dc commit 7e2df6b

File tree

5 files changed

+57
-14
lines changed

5 files changed

+57
-14
lines changed

beacon_node/http_api/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ pub fn serve<T: BeaconChainTypes>(
709709
.clone()
710710
.and(warp::path("validator_balances"))
711711
.and(warp::path::end())
712-
.and(warp_utils::json::json())
712+
.and(warp_utils::json::json_no_body())
713713
.then(
714714
|state_id: StateId,
715715
task_spawner: TaskSpawner<T::EthSpec>,

beacon_node/http_api/src/validators.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,13 @@ pub fn get_beacon_state_validator_balances<T: BeaconChainTypes>(
8181
.map_state_and_execution_optimistic_and_finalized(
8282
&chain,
8383
|state, execution_optimistic, finalized| {
84-
let ids_filter_set: Option<HashSet<&ValidatorId>> =
85-
optional_ids.map(|f| HashSet::from_iter(f.iter()));
84+
let ids_filter_set: Option<HashSet<&ValidatorId>> = match optional_ids {
85+
// if optional_ids (the request data body) is [], returns a `None`, so that later when calling .is_none_or() will return True
86+
// Hence, all validators will pass through .filter(), and balances of all validators are returned, in accordance to the spec
87+
Some([]) => None,
88+
Some(ids) => Some(HashSet::from_iter(ids.iter())),
89+
None => None,
90+
};
8691

8792
Ok((
8893
state

beacon_node/http_api/tests/tests.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -927,18 +927,32 @@ impl ApiTester {
927927
.map(|res| res.data);
928928

929929
let expected = state_opt.map(|(state, _execution_optimistic, _finalized)| {
930-
let mut validators = Vec::with_capacity(validator_indices.len());
931-
932-
for i in validator_indices {
933-
if i < state.balances().len() as u64 {
934-
validators.push(ValidatorBalanceData {
935-
index: i,
936-
balance: *state.balances().get(i as usize).unwrap(),
937-
});
930+
// If validator_indices is empty, return balances for all validators
931+
if validator_indices.is_empty() {
932+
state
933+
.balances()
934+
.iter()
935+
.enumerate()
936+
.map(|(index, balance)| ValidatorBalanceData {
937+
index: index as u64,
938+
balance: *balance,
939+
})
940+
.collect()
941+
} else {
942+
// Same behaviour as before for the else branch
943+
let mut validators = Vec::with_capacity(validator_indices.len());
944+
945+
for i in validator_indices {
946+
if i < state.balances().len() as u64 {
947+
validators.push(ValidatorBalanceData {
948+
index: i,
949+
balance: *state.balances().get(i as usize).unwrap(),
950+
});
951+
}
938952
}
939-
}
940953

941-
validators
954+
validators
955+
}
942956
});
943957

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

common/eth2/src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ pub struct ValidatorBalancesQuery {
688688
pub id: Option<Vec<ValidatorId>>,
689689
}
690690

691-
#[derive(Clone, Serialize, Deserialize)]
691+
#[derive(Clone, Default, Serialize, Deserialize)]
692692
#[serde(transparent)]
693693
pub struct ValidatorBalancesRequestBody {
694694
pub ids: Vec<ValidatorId>,

common/warp_utils/src/json.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,27 @@ pub fn json<T: DeserializeOwned + Send>() -> impl Filter<Extract = (T,), Error =
3131
.map_err(|err| reject::custom_deserialize_error(format!("{:?}", err)))
3232
})
3333
}
34+
35+
// Add a json_no_body function to handle the case when no body is provided in the HTTP request
36+
pub fn json_no_body<T: DeserializeOwned + Default + Send>(
37+
) -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
38+
warp::header::optional::<String>(CONTENT_TYPE_HEADER)
39+
.and(warp::body::bytes())
40+
.and_then(|header: Option<String>, bytes: Bytes| async move {
41+
if let Some(header) = header {
42+
if header == SSZ_CONTENT_TYPE_HEADER {
43+
return Err(reject::unsupported_media_type(
44+
"The request's content-type is not supported".to_string(),
45+
));
46+
}
47+
}
48+
49+
// Handle the case when the HTTP request has no body, i.e., without the -d header
50+
if bytes.is_empty() {
51+
return Ok(T::default());
52+
}
53+
54+
Json::decode(bytes)
55+
.map_err(|err| reject::custom_deserialize_error(format!("{:?}", err)))
56+
})
57+
}

0 commit comments

Comments
 (0)