Ssz state api endpoint (#2111)
## Issue Addressed Catching up to a recently merged API spec PR: https://github.com/ethereum/eth2.0-APIs/pull/119 ## Proposed Changes - Return an SSZ beacon state on `/eth/v1/debug/beacon/states/{stateId}` when passed this header: `accept: application/octet-stream`. - requests to this endpoint with no `accept` header or an `accept` header and a value of `application/json` or `*/*` , or will result in a JSON response ## Additional Info Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
parent
939fa717fd
commit
588b90157d
@ -43,6 +43,7 @@ use types::{
|
|||||||
};
|
};
|
||||||
use warp::http::StatusCode;
|
use warp::http::StatusCode;
|
||||||
use warp::sse::ServerSentEvent;
|
use warp::sse::ServerSentEvent;
|
||||||
|
use warp::Reply;
|
||||||
use warp::{http::Response, Filter, Stream};
|
use warp::{http::Response, Filter, Stream};
|
||||||
use warp_utils::reject::ServerSentEventError;
|
use warp_utils::reject::ServerSentEventError;
|
||||||
use warp_utils::task::{blocking_json_task, blocking_task};
|
use warp_utils::task::{blocking_json_task, blocking_task};
|
||||||
@ -1242,16 +1243,35 @@ pub fn serve<T: BeaconChainTypes>(
|
|||||||
))
|
))
|
||||||
}))
|
}))
|
||||||
.and(warp::path::end())
|
.and(warp::path::end())
|
||||||
|
.and(warp::header::optional::<api_types::Accept>("accept"))
|
||||||
.and(chain_filter.clone())
|
.and(chain_filter.clone())
|
||||||
.and_then(|state_id: StateId, chain: Arc<BeaconChain<T>>| {
|
.and_then(
|
||||||
blocking_task(move || {
|
|state_id: StateId,
|
||||||
state_id.map_state(&chain, |state| {
|
accept_header: Option<api_types::Accept>,
|
||||||
Ok(warp::reply::json(&api_types::GenericResponseRef::from(
|
chain: Arc<BeaconChain<T>>| {
|
||||||
&state,
|
blocking_task(move || match accept_header {
|
||||||
)))
|
Some(api_types::Accept::Ssz) => {
|
||||||
|
let state = state_id.state(&chain)?;
|
||||||
|
Response::builder()
|
||||||
|
.status(200)
|
||||||
|
.header("Content-Type", "application/octet-stream")
|
||||||
|
.body(state.as_ssz_bytes().into())
|
||||||
|
.map_err(|e| {
|
||||||
|
warp_utils::reject::custom_server_error(format!(
|
||||||
|
"failed to create response: {}",
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => state_id.map_state(&chain, |state| {
|
||||||
|
Ok(
|
||||||
|
warp::reply::json(&api_types::GenericResponseRef::from(&state))
|
||||||
|
.into_response(),
|
||||||
|
)
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
})
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
// GET debug/beacon/heads
|
// GET debug/beacon/heads
|
||||||
let get_debug_beacon_heads = eth1_v1
|
let get_debug_beacon_heads = eth1_v1
|
||||||
|
@ -1374,7 +1374,12 @@ impl ApiTester {
|
|||||||
|
|
||||||
pub async fn test_get_debug_beacon_states(self) -> Self {
|
pub async fn test_get_debug_beacon_states(self) -> Self {
|
||||||
for state_id in self.interesting_state_ids() {
|
for state_id in self.interesting_state_ids() {
|
||||||
let result = self
|
let result_ssz = self
|
||||||
|
.client
|
||||||
|
.get_debug_beacon_states_ssz(state_id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let result_json = self
|
||||||
.client
|
.client
|
||||||
.get_debug_beacon_states(state_id)
|
.get_debug_beacon_states(state_id)
|
||||||
.await
|
.await
|
||||||
@ -1384,7 +1389,8 @@ impl ApiTester {
|
|||||||
let mut expected = self.get_state(state_id);
|
let mut expected = self.get_state(state_id);
|
||||||
expected.as_mut().map(|state| state.drop_all_caches());
|
expected.as_mut().map(|state| state.drop_all_caches());
|
||||||
|
|
||||||
assert_eq!(result, expected, "{:?}", state_id);
|
assert_eq!(result_ssz, expected, "{:?}", state_id);
|
||||||
|
assert_eq!(result_json, expected, "{:?}", state_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
@ -20,6 +20,7 @@ pub use reqwest;
|
|||||||
use reqwest::{IntoUrl, Response};
|
use reqwest::{IntoUrl, Response};
|
||||||
pub use reqwest::{StatusCode, Url};
|
pub use reqwest::{StatusCode, Url};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use ssz::Decode;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
@ -144,6 +145,37 @@ impl BeaconNodeHttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform a HTTP GET request using an 'accept' header, returning `None` on a 404 error.
|
||||||
|
pub async fn get_bytes_opt_accept_header<U: IntoUrl>(
|
||||||
|
&self,
|
||||||
|
url: U,
|
||||||
|
accept_header: Accept,
|
||||||
|
) -> Result<Option<Vec<u8>>, Error> {
|
||||||
|
let response = self
|
||||||
|
.client
|
||||||
|
.get(url)
|
||||||
|
.header(ACCEPT, accept_header.to_string())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(Error::Reqwest)?;
|
||||||
|
match ok_or_error(response).await {
|
||||||
|
Ok(resp) => Ok(Some(
|
||||||
|
resp.bytes()
|
||||||
|
.await
|
||||||
|
.map_err(Error::Reqwest)?
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)),
|
||||||
|
Err(err) => {
|
||||||
|
if err.status() == Some(StatusCode::NOT_FOUND) {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Perform a HTTP POST request.
|
/// Perform a HTTP POST request.
|
||||||
async fn post<T: Serialize, U: IntoUrl>(&self, url: U, body: &T) -> Result<(), Error> {
|
async fn post<T: Serialize, U: IntoUrl>(&self, url: U, body: &T) -> Result<(), Error> {
|
||||||
let response = self
|
let response = self
|
||||||
@ -824,6 +856,27 @@ impl BeaconNodeHttpClient {
|
|||||||
self.get_opt(path).await
|
self.get_opt(path).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `GET debug/beacon/states/{state_id}`
|
||||||
|
/// `-H "accept: application/octet-stream"`
|
||||||
|
pub async fn get_debug_beacon_states_ssz<T: EthSpec>(
|
||||||
|
&self,
|
||||||
|
state_id: StateId,
|
||||||
|
) -> Result<Option<BeaconState<T>>, Error> {
|
||||||
|
let mut path = self.eth_path()?;
|
||||||
|
|
||||||
|
path.path_segments_mut()
|
||||||
|
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
|
||||||
|
.push("debug")
|
||||||
|
.push("beacon")
|
||||||
|
.push("states")
|
||||||
|
.push(&state_id.to_string());
|
||||||
|
|
||||||
|
self.get_bytes_opt_accept_header(path, Accept::Ssz)
|
||||||
|
.await?
|
||||||
|
.map(|bytes| BeaconState::from_ssz_bytes(&bytes).map_err(Error::InvalidSsz))
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
|
||||||
/// `GET debug/beacon/heads`
|
/// `GET debug/beacon/heads`
|
||||||
pub async fn get_debug_beacon_heads(
|
pub async fn get_debug_beacon_heads(
|
||||||
&self,
|
&self,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
use crate::Error as ServerError;
|
use crate::Error as ServerError;
|
||||||
use eth2_libp2p::{ConnectionDirection, Enr, Multiaddr, PeerConnectionStatus};
|
use eth2_libp2p::{ConnectionDirection, Enr, Multiaddr, PeerConnectionStatus};
|
||||||
|
pub use reqwest::header::ACCEPT;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@ -768,6 +769,36 @@ impl fmt::Display for EventTopic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum Accept {
|
||||||
|
Json,
|
||||||
|
Ssz,
|
||||||
|
Any,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Accept {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Accept::Ssz => write!(f, "application/octet-stream"),
|
||||||
|
Accept::Json => write!(f, "application/json"),
|
||||||
|
Accept::Any => write!(f, "*/*"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Accept {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"application/octet-stream" => Ok(Accept::Ssz),
|
||||||
|
"application/json" => Ok(Accept::Json),
|
||||||
|
"*/*" => Ok(Accept::Any),
|
||||||
|
_ => Err("accept header cannot be parsed.".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
Loading…
Reference in New Issue
Block a user