diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index ff3bfce19d..2eaa33a964 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -709,7 +709,7 @@ pub fn serve( .clone() .and(warp::path("validator_balances")) .and(warp::path::end()) - .and(warp_utils::json::json()) + .and(warp_utils::json::json_no_body()) .then( |state_id: StateId, task_spawner: TaskSpawner, diff --git a/beacon_node/http_api/src/validators.rs b/beacon_node/http_api/src/validators.rs index f3d78e6fcd..90ddd1ee8f 100644 --- a/beacon_node/http_api/src/validators.rs +++ b/beacon_node/http_api/src/validators.rs @@ -81,8 +81,13 @@ pub fn get_beacon_state_validator_balances( .map_state_and_execution_optimistic_and_finalized( &chain, |state, execution_optimistic, finalized| { - let ids_filter_set: Option> = - optional_ids.map(|f| HashSet::from_iter(f.iter())); + let ids_filter_set: Option> = match optional_ids { + // 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(( state diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 33ac8e413d..a5a21fd985 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -927,18 +927,32 @@ impl ApiTester { .map(|res| res.data); let expected = state_opt.map(|(state, _execution_optimistic, _finalized)| { - let mut validators = Vec::with_capacity(validator_indices.len()); + // 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()); - for i in validator_indices { - if i < state.balances().len() as u64 { - validators.push(ValidatorBalanceData { - index: i, - balance: *state.balances().get(i as usize).unwrap(), - }); + for i in validator_indices { + if i < state.balances().len() as u64 { + validators.push(ValidatorBalanceData { + index: i, + balance: *state.balances().get(i as usize).unwrap(), + }); + } } - } - validators + validators + } }); assert_eq!(result_index_ids, expected, "{:?}", state_id); diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index f895e5cb8f..b8c74d4dcd 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -688,7 +688,7 @@ pub struct ValidatorBalancesQuery { pub id: Option>, } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Default, Serialize, Deserialize)] #[serde(transparent)] pub struct ValidatorBalancesRequestBody { pub ids: Vec, diff --git a/common/warp_utils/src/json.rs b/common/warp_utils/src/json.rs index 6ee5e77261..bc7d61557b 100644 --- a/common/warp_utils/src/json.rs +++ b/common/warp_utils/src/json.rs @@ -31,3 +31,27 @@ pub fn json() -> impl Filter( +) -> impl Filter + Copy { + warp::header::optional::(CONTENT_TYPE_HEADER) + .and(warp::body::bytes()) + .and_then(|header: Option, 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))) + }) +}