mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-14 10:22:38 +00:00
Support duplicate keys in HTTP API query strings (#2908)
## Issues Addressed Closes #2739 Closes #2812 ## Proposed Changes Support the deserialization of query strings containing duplicate keys into their corresponding types. As `warp` does not support this feature natively (as discussed in #2739), it relies on the external library [`serde_array_query`](https://github.com/sigp/serde_array_query) (written by @michaelsproul) This is backwards compatible meaning that both of the following requests will produce the same output: ``` curl "http://localhost:5052/eth/v1/events?topics=head,block" ``` ``` curl "http://localhost:5052/eth/v1/events?topics=head&topics=block" ``` ## Additional Info Certain error messages have changed slightly. This only affects endpoints which accept multiple values. For example: ``` {"code":400,"message":"BAD_REQUEST: invalid query: Invalid query string","stacktraces":[]} ``` is now ``` {"code":400,"message":"BAD_REQUEST: unable to parse query","stacktraces":[]} ``` The serve order of the endpoints `get_beacon_state_validators` and `get_beacon_state_validators_id` have flipped: ```rust .or(get_beacon_state_validators_id.boxed()) .or(get_beacon_state_validators.boxed()) ``` This is to ensure proper error messages when filter fallback occurs due to the use of the `and_then` filter. ## Future Work - Cleanup / remove filter fallback behaviour by substituting `and_then` with `then` where appropriate. - Add regression tests for HTTP API error messages. ## Credits - @mooori for doing the ground work of investigating possible solutions within the existing Rust ecosystem. - @michaelsproul for writing [`serde_array_query`](https://github.com/sigp/serde_array_query) and for helping debug the behaviour of the `warp` filter fallback leading to incorrect error messages.
This commit is contained in:
@@ -55,7 +55,10 @@ use warp::http::StatusCode;
|
||||
use warp::sse::Event;
|
||||
use warp::Reply;
|
||||
use warp::{http::Response, Filter};
|
||||
use warp_utils::task::{blocking_json_task, blocking_task};
|
||||
use warp_utils::{
|
||||
query::multi_key_query,
|
||||
task::{blocking_json_task, blocking_task},
|
||||
};
|
||||
|
||||
const API_PREFIX: &str = "eth";
|
||||
|
||||
@@ -505,12 +508,13 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.clone()
|
||||
.and(warp::path("validator_balances"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::query::<api_types::ValidatorBalancesQuery>())
|
||||
.and(multi_key_query::<api_types::ValidatorBalancesQuery>())
|
||||
.and_then(
|
||||
|state_id: StateId,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
query: api_types::ValidatorBalancesQuery| {
|
||||
query_res: Result<api_types::ValidatorBalancesQuery, warp::Rejection>| {
|
||||
blocking_json_task(move || {
|
||||
let query = query_res?;
|
||||
state_id
|
||||
.map_state(&chain, |state| {
|
||||
Ok(state
|
||||
@@ -521,7 +525,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
// 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 {
|
||||
ids.iter().any(|id| match id {
|
||||
ValidatorId::PublicKey(pubkey) => {
|
||||
&validator.pubkey == pubkey
|
||||
}
|
||||
@@ -548,11 +552,14 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
let get_beacon_state_validators = beacon_states_path
|
||||
.clone()
|
||||
.and(warp::path("validators"))
|
||||
.and(warp::query::<api_types::ValidatorsQuery>())
|
||||
.and(warp::path::end())
|
||||
.and(multi_key_query::<api_types::ValidatorsQuery>())
|
||||
.and_then(
|
||||
|state_id: StateId, chain: Arc<BeaconChain<T>>, query: api_types::ValidatorsQuery| {
|
||||
|state_id: StateId,
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
query_res: Result<api_types::ValidatorsQuery, warp::Rejection>| {
|
||||
blocking_json_task(move || {
|
||||
let query = query_res?;
|
||||
state_id
|
||||
.map_state(&chain, |state| {
|
||||
let epoch = state.current_epoch();
|
||||
@@ -566,7 +573,7 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
// 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 {
|
||||
ids.iter().any(|id| match id {
|
||||
ValidatorId::PublicKey(pubkey) => {
|
||||
&validator.pubkey == pubkey
|
||||
}
|
||||
@@ -586,8 +593,8 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
|
||||
let status_matches =
|
||||
query.status.as_ref().map_or(true, |statuses| {
|
||||
statuses.0.contains(&status)
|
||||
|| statuses.0.contains(&status.superstatus())
|
||||
statuses.contains(&status)
|
||||
|| statuses.contains(&status.superstatus())
|
||||
});
|
||||
|
||||
if status_matches {
|
||||
@@ -1721,11 +1728,13 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.and(warp::path("node"))
|
||||
.and(warp::path("peers"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::query::<api_types::PeersQuery>())
|
||||
.and(multi_key_query::<api_types::PeersQuery>())
|
||||
.and(network_globals.clone())
|
||||
.and_then(
|
||||
|query: api_types::PeersQuery, network_globals: Arc<NetworkGlobals<T::EthSpec>>| {
|
||||
|query_res: Result<api_types::PeersQuery, warp::Rejection>,
|
||||
network_globals: Arc<NetworkGlobals<T::EthSpec>>| {
|
||||
blocking_json_task(move || {
|
||||
let query = query_res?;
|
||||
let mut peers: Vec<api_types::PeerData> = Vec::new();
|
||||
network_globals
|
||||
.peers
|
||||
@@ -1755,11 +1764,11 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
);
|
||||
|
||||
let state_matches = query.state.as_ref().map_or(true, |states| {
|
||||
states.0.iter().any(|state_param| *state_param == state)
|
||||
states.iter().any(|state_param| *state_param == state)
|
||||
});
|
||||
let direction_matches =
|
||||
query.direction.as_ref().map_or(true, |directions| {
|
||||
directions.0.iter().any(|dir_param| *dir_param == direction)
|
||||
directions.iter().any(|dir_param| *dir_param == direction)
|
||||
});
|
||||
|
||||
if state_matches && direction_matches {
|
||||
@@ -2534,16 +2543,18 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
let get_events = eth1_v1
|
||||
.and(warp::path("events"))
|
||||
.and(warp::path::end())
|
||||
.and(warp::query::<api_types::EventQuery>())
|
||||
.and(multi_key_query::<api_types::EventQuery>())
|
||||
.and(chain_filter)
|
||||
.and_then(
|
||||
|topics: api_types::EventQuery, chain: Arc<BeaconChain<T>>| {
|
||||
|topics_res: Result<api_types::EventQuery, warp::Rejection>,
|
||||
chain: Arc<BeaconChain<T>>| {
|
||||
blocking_task(move || {
|
||||
let topics = topics_res?;
|
||||
// for each topic subscribed spawn a new subscription
|
||||
let mut receivers = Vec::with_capacity(topics.topics.0.len());
|
||||
let mut receivers = Vec::with_capacity(topics.topics.len());
|
||||
|
||||
if let Some(event_handler) = chain.event_handler.as_ref() {
|
||||
for topic in topics.topics.0.clone() {
|
||||
for topic in topics.topics {
|
||||
let receiver = match topic {
|
||||
api_types::EventTopic::Head => event_handler.subscribe_head(),
|
||||
api_types::EventTopic::Block => event_handler.subscribe_block(),
|
||||
@@ -2606,8 +2617,8 @@ pub fn serve<T: BeaconChainTypes>(
|
||||
.or(get_beacon_state_fork.boxed())
|
||||
.or(get_beacon_state_finality_checkpoints.boxed())
|
||||
.or(get_beacon_state_validator_balances.boxed())
|
||||
.or(get_beacon_state_validators.boxed())
|
||||
.or(get_beacon_state_validators_id.boxed())
|
||||
.or(get_beacon_state_validators.boxed())
|
||||
.or(get_beacon_state_committees.boxed())
|
||||
.or(get_beacon_state_sync_committees.boxed())
|
||||
.or(get_beacon_headers.boxed())
|
||||
|
||||
Reference in New Issue
Block a user