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:
realbigsean
2021-01-06 03:01:46 +00:00
parent 939fa717fd
commit 588b90157d
4 changed files with 120 additions and 10 deletions

View File

@@ -20,6 +20,7 @@ pub use reqwest;
use reqwest::{IntoUrl, Response};
pub use reqwest::{StatusCode, Url};
use serde::{de::DeserializeOwned, Serialize};
use ssz::Decode;
use std::convert::TryFrom;
use std::fmt;
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.
async fn post<T: Serialize, U: IntoUrl>(&self, url: U, body: &T) -> Result<(), Error> {
let response = self
@@ -824,6 +856,27 @@ impl BeaconNodeHttpClient {
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`
pub async fn get_debug_beacon_heads(
&self,

View File

@@ -3,6 +3,7 @@
use crate::Error as ServerError;
use eth2_libp2p::{ConnectionDirection, Enr, Multiaddr, PeerConnectionStatus};
pub use reqwest::header::ACCEPT;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
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)]
mod tests {
use super::*;