Merge latest master
This commit is contained in:
commit
af5372fde4
@ -296,7 +296,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
/// It is important to note that the `beacon_state` returned may not match the present slot. It
|
/// It is important to note that the `beacon_state` returned may not match the present slot. It
|
||||||
/// is the state as it was when the head block was received, which could be some slots prior to
|
/// is the state as it was when the head block was received, which could be some slots prior to
|
||||||
/// now.
|
/// now.
|
||||||
pub fn head(&self) -> RwLockReadGuard<CheckPoint<T::EthSpec>> {
|
pub fn head<'a>(&'a self) -> RwLockReadGuard<'a, CheckPoint<T::EthSpec>> {
|
||||||
self.canonical_head.read()
|
self.canonical_head.read()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ pub struct Config {
|
|||||||
pub network: network::NetworkConfig,
|
pub network: network::NetworkConfig,
|
||||||
pub rpc: rpc::RPCConfig,
|
pub rpc: rpc::RPCConfig,
|
||||||
pub http: HttpServerConfig,
|
pub http: HttpServerConfig,
|
||||||
pub rest_api: rest_api::APIConfig,
|
pub rest_api: rest_api::ApiConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -60,7 +60,7 @@ impl Default for Config {
|
|||||||
network: NetworkConfig::new(),
|
network: NetworkConfig::new(),
|
||||||
rpc: rpc::RPCConfig::default(),
|
rpc: rpc::RPCConfig::default(),
|
||||||
http: HttpServerConfig::default(),
|
http: HttpServerConfig::default(),
|
||||||
rest_api: rest_api::APIConfig::default(),
|
rest_api: rest_api::ApiConfig::default(),
|
||||||
spec_constants: TESTNET_SPEC_CONSTANTS.into(),
|
spec_constants: TESTNET_SPEC_CONSTANTS.into(),
|
||||||
genesis_state: GenesisState::RecentGenesis {
|
genesis_state: GenesisState::RecentGenesis {
|
||||||
validator_count: TESTNET_VALIDATOR_COUNT,
|
validator_count: TESTNET_VALIDATOR_COUNT,
|
||||||
|
@ -3,6 +3,7 @@ use crate::discovery::Discovery;
|
|||||||
use crate::rpc::{RPCEvent, RPCMessage, RPC};
|
use crate::rpc::{RPCEvent, RPCMessage, RPC};
|
||||||
use crate::{error, NetworkConfig};
|
use crate::{error, NetworkConfig};
|
||||||
use crate::{Topic, TopicHash};
|
use crate::{Topic, TopicHash};
|
||||||
|
use crate::{BEACON_ATTESTATION_TOPIC, BEACON_BLOCK_TOPIC};
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use libp2p::{
|
use libp2p::{
|
||||||
core::identity::Keypair,
|
core::identity::Keypair,
|
||||||
@ -15,6 +16,7 @@ use libp2p::{
|
|||||||
NetworkBehaviour, PeerId,
|
NetworkBehaviour, PeerId,
|
||||||
};
|
};
|
||||||
use slog::{debug, o, trace};
|
use slog::{debug, o, trace};
|
||||||
|
use ssz::{ssz_encode, Encode};
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
@ -7,16 +7,19 @@ edition = "2018"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
[dependencies]
|
[dependencies]
|
||||||
beacon_chain = { path = "../beacon_chain" }
|
beacon_chain = { path = "../beacon_chain" }
|
||||||
|
store = { path = "../store" }
|
||||||
version = { path = "../version" }
|
version = { path = "../version" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "^1.0"
|
serde_json = "^1.0"
|
||||||
slog = "^2.2.3"
|
slog = "^2.2.3"
|
||||||
slog-term = "^2.4.0"
|
slog-term = "^2.4.0"
|
||||||
slog-async = "^2.3.0"
|
slog-async = "^2.3.0"
|
||||||
|
state_processing = { path = "../../eth2/state_processing" }
|
||||||
|
types = { path = "../../eth2/types" }
|
||||||
clap = "2.32.0"
|
clap = "2.32.0"
|
||||||
http = "^0.1.17"
|
http = "^0.1.17"
|
||||||
hyper = "0.12.32"
|
hyper = "0.12.32"
|
||||||
hyper-router = "^0.5"
|
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
exit-future = "0.1.3"
|
exit-future = "0.1.3"
|
||||||
tokio = "0.1.17"
|
tokio = "0.1.17"
|
||||||
|
url = "2.0"
|
||||||
|
60
beacon_node/rest_api/src/beacon.rs
Normal file
60
beacon_node/rest_api/src/beacon.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use super::{success_response, ApiResult};
|
||||||
|
use crate::{helpers::*, ApiError, UrlQuery};
|
||||||
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
|
use hyper::{Body, Request};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use store::Store;
|
||||||
|
use types::BeaconState;
|
||||||
|
|
||||||
|
/// HTTP handler to return a `BeaconState` at a given `root` or `slot`.
|
||||||
|
///
|
||||||
|
/// Will not return a state if the request slot is in the future. Will return states higher than
|
||||||
|
/// the current head by skipping slots.
|
||||||
|
pub fn get_state<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||||
|
let beacon_chain = req
|
||||||
|
.extensions()
|
||||||
|
.get::<Arc<BeaconChain<T>>>()
|
||||||
|
.ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?;
|
||||||
|
|
||||||
|
let query_params = ["root", "slot"];
|
||||||
|
let (key, value) = UrlQuery::from_request(&req)?.first_of(&query_params)?;
|
||||||
|
|
||||||
|
let state: BeaconState<T::EthSpec> = match (key.as_ref(), value) {
|
||||||
|
("slot", value) => state_at_slot(&beacon_chain, parse_slot(&value)?)?,
|
||||||
|
("root", value) => {
|
||||||
|
let root = &parse_root(&value)?;
|
||||||
|
|
||||||
|
beacon_chain
|
||||||
|
.store
|
||||||
|
.get(root)?
|
||||||
|
.ok_or_else(|| ApiError::NotFound(format!("No state for root: {}", root)))?
|
||||||
|
}
|
||||||
|
_ => unreachable!("Guarded by UrlQuery::from_request()"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let json: String = serde_json::to_string(&state)
|
||||||
|
.map_err(|e| ApiError::ServerError(format!("Unable to serialize BeaconState: {:?}", e)))?;
|
||||||
|
|
||||||
|
Ok(success_response(Body::from(json)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// HTTP handler to return a `BeaconState` root at a given or `slot`.
|
||||||
|
///
|
||||||
|
/// Will not return a state if the request slot is in the future. Will return states higher than
|
||||||
|
/// the current head by skipping slots.
|
||||||
|
pub fn get_state_root<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||||
|
let beacon_chain = req
|
||||||
|
.extensions()
|
||||||
|
.get::<Arc<BeaconChain<T>>>()
|
||||||
|
.ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?;
|
||||||
|
|
||||||
|
let slot_string = UrlQuery::from_request(&req)?.only_one("slot")?;
|
||||||
|
let slot = parse_slot(&slot_string)?;
|
||||||
|
|
||||||
|
let root = state_root_at_slot(&beacon_chain, slot)?;
|
||||||
|
|
||||||
|
let json: String = serde_json::to_string(&root)
|
||||||
|
.map_err(|e| ApiError::ServerError(format!("Unable to serialize root: {:?}", e)))?;
|
||||||
|
|
||||||
|
Ok(success_response(Body::from(json)))
|
||||||
|
}
|
@ -1,65 +0,0 @@
|
|||||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
|
||||||
use serde::Serialize;
|
|
||||||
use slog::info;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use version;
|
|
||||||
|
|
||||||
use super::{path_from_request, success_response, APIResult, APIService};
|
|
||||||
|
|
||||||
use hyper::{Body, Request, Response};
|
|
||||||
use hyper_router::{Route, RouterBuilder};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct BeaconNodeServiceInstance<T: BeaconChainTypes + 'static> {
|
|
||||||
pub marker: std::marker::PhantomData<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A string which uniquely identifies the client implementation and its version; similar to [HTTP User-Agent](https://tools.ietf.org/html/rfc7231#section-5.5.3).
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct Version(String);
|
|
||||||
impl From<String> for Version {
|
|
||||||
fn from(x: String) -> Self {
|
|
||||||
Version(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The genesis_time configured for the beacon node, which is the unix time at which the Eth2.0 chain began.
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct GenesisTime(u64);
|
|
||||||
impl From<u64> for GenesisTime {
|
|
||||||
fn from(x: u64) -> Self {
|
|
||||||
GenesisTime(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: BeaconChainTypes + 'static> APIService for BeaconNodeServiceInstance<T> {
|
|
||||||
fn add_routes(&mut self, router_builder: RouterBuilder) -> Result<RouterBuilder, hyper::Error> {
|
|
||||||
let router_builder = router_builder
|
|
||||||
.add(Route::get("/version").using(result_to_response!(get_version)))
|
|
||||||
.add(Route::get("/genesis_time").using(result_to_response!(get_genesis_time::<T>)));
|
|
||||||
Ok(router_builder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the version string from the current Lighthouse build.
|
|
||||||
fn get_version(_req: Request<Body>) -> APIResult {
|
|
||||||
let ver = Version::from(version::version());
|
|
||||||
let body = Body::from(
|
|
||||||
serde_json::to_string(&ver).expect("Version should always be serialializable as JSON."),
|
|
||||||
);
|
|
||||||
Ok(success_response(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the genesis time from the current beacon chain state.
|
|
||||||
fn get_genesis_time<T: BeaconChainTypes + 'static>(req: Request<Body>) -> APIResult {
|
|
||||||
let beacon_chain = req.extensions().get::<Arc<BeaconChain<T>>>().unwrap();
|
|
||||||
let gen_time = {
|
|
||||||
let state = &beacon_chain.head().beacon_state;
|
|
||||||
state.genesis_time
|
|
||||||
};
|
|
||||||
let body = Body::from(
|
|
||||||
serde_json::to_string(&gen_time)
|
|
||||||
.expect("Genesis should time always have a valid JSON serialization."),
|
|
||||||
);
|
|
||||||
Ok(success_response(body))
|
|
||||||
}
|
|
156
beacon_node/rest_api/src/helpers.rs
Normal file
156
beacon_node/rest_api/src/helpers.rs
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
use crate::ApiError;
|
||||||
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
|
use store::{iter::AncestorIter, Store};
|
||||||
|
use types::{BeaconState, EthSpec, Hash256, RelativeEpoch, Slot};
|
||||||
|
|
||||||
|
/// Parse a slot from a `0x` preixed string.
|
||||||
|
///
|
||||||
|
/// E.g., `"1234"`
|
||||||
|
pub fn parse_slot(string: &str) -> Result<Slot, ApiError> {
|
||||||
|
string
|
||||||
|
.parse::<u64>()
|
||||||
|
.map(Slot::from)
|
||||||
|
.map_err(|e| ApiError::InvalidQueryParams(format!("Unable to parse slot: {:?}", e)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a root from a `0x` preixed string.
|
||||||
|
///
|
||||||
|
/// E.g., `"0x0000000000000000000000000000000000000000000000000000000000000000"`
|
||||||
|
pub fn parse_root(string: &str) -> Result<Hash256, ApiError> {
|
||||||
|
const PREFIX: &str = "0x";
|
||||||
|
|
||||||
|
if string.starts_with(PREFIX) {
|
||||||
|
let trimmed = string.trim_start_matches(PREFIX);
|
||||||
|
trimmed
|
||||||
|
.parse()
|
||||||
|
.map_err(|e| ApiError::InvalidQueryParams(format!("Unable to parse root: {:?}", e)))
|
||||||
|
} else {
|
||||||
|
Err(ApiError::InvalidQueryParams(
|
||||||
|
"Root must have a '0x' prefix".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a `BeaconState` in the canonical chain of `beacon_chain` at the given `slot`, if
|
||||||
|
/// possible.
|
||||||
|
///
|
||||||
|
/// Will not return a state if the request slot is in the future. Will return states higher than
|
||||||
|
/// the current head by skipping slots.
|
||||||
|
pub fn state_at_slot<T: BeaconChainTypes>(
|
||||||
|
beacon_chain: &BeaconChain<T>,
|
||||||
|
slot: Slot,
|
||||||
|
) -> Result<BeaconState<T::EthSpec>, ApiError> {
|
||||||
|
let head_state = &beacon_chain.head().beacon_state;
|
||||||
|
|
||||||
|
if head_state.slot == slot {
|
||||||
|
// The request slot is the same as the best block (head) slot.
|
||||||
|
|
||||||
|
// I'm not sure if this `.clone()` will be optimized out. If not, it seems unnecessary.
|
||||||
|
Ok(beacon_chain.head().beacon_state.clone())
|
||||||
|
} else {
|
||||||
|
let root = state_root_at_slot(beacon_chain, slot)?;
|
||||||
|
|
||||||
|
let state: BeaconState<T::EthSpec> = beacon_chain
|
||||||
|
.store
|
||||||
|
.get(&root)?
|
||||||
|
.ok_or_else(|| ApiError::NotFound(format!("Unable to find state at root {}", root)))?;
|
||||||
|
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the root of the `BeaconState` in the canonical chain of `beacon_chain` at the given
|
||||||
|
/// `slot`, if possible.
|
||||||
|
///
|
||||||
|
/// Will not return a state root if the request slot is in the future. Will return state roots
|
||||||
|
/// higher than the current head by skipping slots.
|
||||||
|
pub fn state_root_at_slot<T: BeaconChainTypes>(
|
||||||
|
beacon_chain: &BeaconChain<T>,
|
||||||
|
slot: Slot,
|
||||||
|
) -> Result<Hash256, ApiError> {
|
||||||
|
let head_state = &beacon_chain.head().beacon_state;
|
||||||
|
let current_slot = beacon_chain
|
||||||
|
.read_slot_clock()
|
||||||
|
.ok_or_else(|| ApiError::ServerError("Unable to read slot clock".to_string()))?;
|
||||||
|
|
||||||
|
// There are four scenarios when obtaining a state for a given slot:
|
||||||
|
//
|
||||||
|
// 1. The request slot is in the future.
|
||||||
|
// 2. The request slot is the same as the best block (head) slot.
|
||||||
|
// 3. The request slot is prior to the head slot.
|
||||||
|
// 4. The request slot is later than the head slot.
|
||||||
|
if current_slot < slot {
|
||||||
|
// 1. The request slot is in the future. Reject the request.
|
||||||
|
//
|
||||||
|
// We could actually speculate about future state roots by skipping slots, however that's
|
||||||
|
// likely to cause confusion for API users.
|
||||||
|
Err(ApiError::InvalidQueryParams(format!(
|
||||||
|
"Requested slot {} is past the current slot {}",
|
||||||
|
slot, current_slot
|
||||||
|
)))
|
||||||
|
} else if head_state.slot == slot {
|
||||||
|
// 2. The request slot is the same as the best block (head) slot.
|
||||||
|
//
|
||||||
|
// The head state root is stored in memory, return a reference.
|
||||||
|
Ok(beacon_chain.head().beacon_state_root)
|
||||||
|
} else if head_state.slot > slot {
|
||||||
|
// 3. The request slot is prior to the head slot.
|
||||||
|
//
|
||||||
|
// Iterate through the state roots on the head state to find the root for that
|
||||||
|
// slot. Once the root is found, load it from the database.
|
||||||
|
Ok(head_state
|
||||||
|
.try_iter_ancestor_roots(beacon_chain.store.clone())
|
||||||
|
.ok_or_else(|| ApiError::ServerError("Failed to create roots iterator".to_string()))?
|
||||||
|
.find(|(_root, s)| *s == slot)
|
||||||
|
.map(|(root, _slot)| root)
|
||||||
|
.ok_or_else(|| ApiError::NotFound(format!("Unable to find state at slot {}", slot)))?)
|
||||||
|
} else {
|
||||||
|
// 4. The request slot is later than the head slot.
|
||||||
|
//
|
||||||
|
// Use `per_slot_processing` to advance the head state to the present slot,
|
||||||
|
// assuming that all slots do not contain a block (i.e., they are skipped slots).
|
||||||
|
let mut state = beacon_chain.head().beacon_state.clone();
|
||||||
|
let spec = &T::EthSpec::default_spec();
|
||||||
|
|
||||||
|
for _ in state.slot.as_u64()..slot.as_u64() {
|
||||||
|
// Ensure the next epoch state caches are built in case of an epoch transition.
|
||||||
|
state.build_committee_cache(RelativeEpoch::Next, spec)?;
|
||||||
|
|
||||||
|
state_processing::per_slot_processing(&mut state, spec)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this is an expensive operation. Once the tree hash cache is implement it may be
|
||||||
|
// used here.
|
||||||
|
Ok(state.canonical_root())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_root_works() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_root("0x0000000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
Ok(Hash256::zero())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_root("0x000000000000000000000000000000000000000000000000000000000000002a"),
|
||||||
|
Ok(Hash256::from_low_u64_be(42))
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
parse_root("0000000000000000000000000000000000000000000000000000000000000042").is_err()
|
||||||
|
);
|
||||||
|
assert!(parse_root("0x").is_err());
|
||||||
|
assert!(parse_root("0x00").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_slot_works() {
|
||||||
|
assert_eq!(parse_slot("0"), Ok(Slot::new(0)));
|
||||||
|
assert_eq!(parse_slot("42"), Ok(Slot::new(42)));
|
||||||
|
assert_eq!(parse_slot("10000000"), Ok(Slot::new(10_000_000)));
|
||||||
|
assert!(parse_slot("cats").is_err());
|
||||||
|
}
|
||||||
|
}
|
@ -1,37 +1,42 @@
|
|||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate hyper;
|
extern crate hyper;
|
||||||
#[macro_use]
|
mod beacon;
|
||||||
mod macros;
|
mod config;
|
||||||
mod beacon_node;
|
mod helpers;
|
||||||
pub mod config;
|
mod node;
|
||||||
|
mod url_query;
|
||||||
|
|
||||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
pub use config::Config as APIConfig;
|
pub use config::Config as ApiConfig;
|
||||||
|
use hyper::rt::Future;
|
||||||
|
use hyper::service::service_fn_ok;
|
||||||
|
use hyper::{Body, Method, Response, Server, StatusCode};
|
||||||
use slog::{info, o, warn};
|
use slog::{info, o, warn};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::runtime::TaskExecutor;
|
use tokio::runtime::TaskExecutor;
|
||||||
|
use url_query::UrlQuery;
|
||||||
|
|
||||||
use crate::beacon_node::BeaconNodeServiceInstance;
|
#[derive(PartialEq, Debug)]
|
||||||
use hyper::rt::Future;
|
pub enum ApiError {
|
||||||
use hyper::service::{service_fn, Service};
|
MethodNotAllowed(String),
|
||||||
use hyper::{Body, Request, Response, Server, StatusCode};
|
ServerError(String),
|
||||||
use hyper_router::{RouterBuilder, RouterService};
|
NotImplemented(String),
|
||||||
|
InvalidQueryParams(String),
|
||||||
pub enum APIError {
|
NotFound(String),
|
||||||
MethodNotAllowed { desc: String },
|
ImATeapot(String), // Just in case.
|
||||||
ServerError { desc: String },
|
|
||||||
NotImplemented { desc: String },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type APIResult = Result<Response<Body>, APIError>;
|
pub type ApiResult = Result<Response<Body>, ApiError>;
|
||||||
|
|
||||||
impl Into<Response<Body>> for APIError {
|
impl Into<Response<Body>> for ApiError {
|
||||||
fn into(self) -> Response<Body> {
|
fn into(self) -> Response<Body> {
|
||||||
let status_code: (StatusCode, String) = match self {
|
let status_code: (StatusCode, String) = match self {
|
||||||
APIError::MethodNotAllowed { desc } => (StatusCode::METHOD_NOT_ALLOWED, desc),
|
ApiError::MethodNotAllowed(desc) => (StatusCode::METHOD_NOT_ALLOWED, desc),
|
||||||
APIError::ServerError { desc } => (StatusCode::INTERNAL_SERVER_ERROR, desc),
|
ApiError::ServerError(desc) => (StatusCode::INTERNAL_SERVER_ERROR, desc),
|
||||||
APIError::NotImplemented { desc } => (StatusCode::NOT_IMPLEMENTED, desc),
|
ApiError::NotImplemented(desc) => (StatusCode::NOT_IMPLEMENTED, desc),
|
||||||
|
ApiError::InvalidQueryParams(desc) => (StatusCode::BAD_REQUEST, desc),
|
||||||
|
ApiError::NotFound(desc) => (StatusCode::NOT_FOUND, desc),
|
||||||
|
ApiError::ImATeapot(desc) => (StatusCode::IM_A_TEAPOT, desc),
|
||||||
};
|
};
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(status_code.0)
|
.status(status_code.0)
|
||||||
@ -40,17 +45,31 @@ impl Into<Response<Body>> for APIError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait APIService {
|
impl From<store::Error> for ApiError {
|
||||||
fn add_routes(&mut self, router_builder: RouterBuilder) -> Result<RouterBuilder, hyper::Error>;
|
fn from(e: store::Error) -> ApiError {
|
||||||
|
ApiError::ServerError(format!("Database error: {:?}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<types::BeaconStateError> for ApiError {
|
||||||
|
fn from(e: types::BeaconStateError) -> ApiError {
|
||||||
|
ApiError::ServerError(format!("BeaconState error: {:?}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<state_processing::per_slot_processing::Error> for ApiError {
|
||||||
|
fn from(e: state_processing::per_slot_processing::Error) -> ApiError {
|
||||||
|
ApiError::ServerError(format!("PerSlotProcessing error: {:?}", e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_server<T: BeaconChainTypes + Clone + 'static>(
|
pub fn start_server<T: BeaconChainTypes + Clone + 'static>(
|
||||||
config: &APIConfig,
|
config: &ApiConfig,
|
||||||
executor: &TaskExecutor,
|
executor: &TaskExecutor,
|
||||||
beacon_chain: Arc<BeaconChain<T>>,
|
beacon_chain: Arc<BeaconChain<T>>,
|
||||||
log: &slog::Logger,
|
log: &slog::Logger,
|
||||||
) -> Result<exit_future::Signal, hyper::Error> {
|
) -> Result<exit_future::Signal, hyper::Error> {
|
||||||
let log = log.new(o!("Service" => "API"));
|
let log = log.new(o!("Service" => "Api"));
|
||||||
|
|
||||||
// build a channel to kill the HTTP server
|
// build a channel to kill the HTTP server
|
||||||
let (exit_signal, exit) = exit_future::signal();
|
let (exit_signal, exit) = exit_future::signal();
|
||||||
@ -68,62 +87,65 @@ pub fn start_server<T: BeaconChainTypes + Clone + 'static>(
|
|||||||
let server_log = log.clone();
|
let server_log = log.clone();
|
||||||
let server_bc = beacon_chain.clone();
|
let server_bc = beacon_chain.clone();
|
||||||
|
|
||||||
// Create the service closure
|
|
||||||
let service = move || {
|
let service = move || {
|
||||||
//TODO: This router must be moved out of this closure, so it isn't rebuilt for every connection.
|
let log = server_log.clone();
|
||||||
let mut router = build_router_service::<T>();
|
let beacon_chain = server_bc.clone();
|
||||||
|
|
||||||
// Clone our stateful objects, for use in handler closure
|
|
||||||
let service_log = server_log.clone();
|
|
||||||
let service_bc = server_bc.clone();
|
|
||||||
|
|
||||||
// Create a simple handler for the router, inject our stateful objects into the request.
|
// Create a simple handler for the router, inject our stateful objects into the request.
|
||||||
service_fn(move |mut req| {
|
service_fn_ok(move |mut req| {
|
||||||
|
req.extensions_mut().insert::<slog::Logger>(log.clone());
|
||||||
req.extensions_mut()
|
req.extensions_mut()
|
||||||
.insert::<slog::Logger>(service_log.clone());
|
.insert::<Arc<BeaconChain<T>>>(beacon_chain.clone());
|
||||||
req.extensions_mut()
|
|
||||||
.insert::<Arc<BeaconChain<T>>>(service_bc.clone());
|
let path = req.uri().path().to_string();
|
||||||
router.call(req)
|
|
||||||
|
// Route the request to the correct handler.
|
||||||
|
let result = match (req.method(), path.as_ref()) {
|
||||||
|
(&Method::GET, "/beacon/state") => beacon::get_state::<T>(req),
|
||||||
|
(&Method::GET, "/beacon/state_root") => beacon::get_state_root::<T>(req),
|
||||||
|
(&Method::GET, "/node/version") => node::get_version(req),
|
||||||
|
(&Method::GET, "/node/genesis_time") => node::get_genesis_time::<T>(req),
|
||||||
|
_ => Err(ApiError::MethodNotAllowed(path.clone())),
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
// Return the `hyper::Response`.
|
||||||
|
Ok(response) => {
|
||||||
|
slog::debug!(log, "Request successful: {:?}", path);
|
||||||
|
response
|
||||||
|
}
|
||||||
|
// Map the `ApiError` into `hyper::Response`.
|
||||||
|
Err(e) => {
|
||||||
|
slog::debug!(log, "Request failure: {:?}", path);
|
||||||
|
e.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let log_clone = log.clone();
|
||||||
let server = Server::bind(&bind_addr)
|
let server = Server::bind(&bind_addr)
|
||||||
.serve(service)
|
.serve(service)
|
||||||
.with_graceful_shutdown(server_exit)
|
.with_graceful_shutdown(server_exit)
|
||||||
.map_err(move |e| {
|
.map_err(move |e| {
|
||||||
warn!(
|
warn!(
|
||||||
log,
|
log_clone,
|
||||||
"API failed to start, Unable to bind"; "address" => format!("{:?}", e)
|
"API failed to start, Unable to bind"; "address" => format!("{:?}", e)
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
info!(
|
||||||
|
log,
|
||||||
|
"REST API started";
|
||||||
|
"address" => format!("{}", config.listen_address),
|
||||||
|
"port" => config.port,
|
||||||
|
);
|
||||||
|
|
||||||
executor.spawn(server);
|
executor.spawn(server);
|
||||||
|
|
||||||
Ok(exit_signal)
|
Ok(exit_signal)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_router_service<T: BeaconChainTypes + 'static>() -> RouterService {
|
|
||||||
let mut router_builder = RouterBuilder::new();
|
|
||||||
|
|
||||||
let mut bn_service: BeaconNodeServiceInstance<T> = BeaconNodeServiceInstance {
|
|
||||||
marker: std::marker::PhantomData,
|
|
||||||
};
|
|
||||||
|
|
||||||
router_builder = bn_service
|
|
||||||
.add_routes(router_builder)
|
|
||||||
.expect("The routes should always be made.");
|
|
||||||
|
|
||||||
RouterService::new(router_builder.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path_from_request(req: &Request<Body>) -> String {
|
|
||||||
req.uri()
|
|
||||||
.path_and_query()
|
|
||||||
.as_ref()
|
|
||||||
.map(|pq| String::from(pq.as_str()))
|
|
||||||
.unwrap_or(String::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn success_response(body: Body) -> Response<Body> {
|
fn success_response(body: Body) -> Response<Body> {
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(StatusCode::OK)
|
.status(StatusCode::OK)
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
macro_rules! result_to_response {
|
|
||||||
($handler: path) => {
|
|
||||||
|req: Request<Body>| -> Response<Body> {
|
|
||||||
let log = req
|
|
||||||
.extensions()
|
|
||||||
.get::<slog::Logger>()
|
|
||||||
.expect("Our logger should be on req.")
|
|
||||||
.clone();
|
|
||||||
let path = path_from_request(&req);
|
|
||||||
let result = $handler(req);
|
|
||||||
match result {
|
|
||||||
Ok(response) => {
|
|
||||||
info!(log, "Request successful: {:?}", path);
|
|
||||||
response
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
info!(log, "Request failure: {:?}", path);
|
|
||||||
e.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
25
beacon_node/rest_api/src/node.rs
Normal file
25
beacon_node/rest_api/src/node.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use crate::{success_response, ApiResult};
|
||||||
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
|
use hyper::{Body, Request};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use version;
|
||||||
|
|
||||||
|
/// Read the version string from the current Lighthouse build.
|
||||||
|
pub fn get_version(_req: Request<Body>) -> ApiResult {
|
||||||
|
let body = Body::from(
|
||||||
|
serde_json::to_string(&version::version())
|
||||||
|
.expect("Version should always be serialializable as JSON."),
|
||||||
|
);
|
||||||
|
Ok(success_response(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the genesis time from the current beacon chain state.
|
||||||
|
pub fn get_genesis_time<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||||
|
let beacon_chain = req.extensions().get::<Arc<BeaconChain<T>>>().unwrap();
|
||||||
|
let gen_time: u64 = beacon_chain.head().beacon_state.genesis_time;
|
||||||
|
let body = Body::from(
|
||||||
|
serde_json::to_string(&gen_time)
|
||||||
|
.expect("Genesis should time always have a valid JSON serialization."),
|
||||||
|
);
|
||||||
|
Ok(success_response(body))
|
||||||
|
}
|
112
beacon_node/rest_api/src/url_query.rs
Normal file
112
beacon_node/rest_api/src/url_query.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use crate::ApiError;
|
||||||
|
use hyper::Request;
|
||||||
|
|
||||||
|
/// Provides handy functions for parsing the query parameters of a URL.
|
||||||
|
pub struct UrlQuery<'a>(url::form_urlencoded::Parse<'a>);
|
||||||
|
|
||||||
|
impl<'a> UrlQuery<'a> {
|
||||||
|
/// Instantiate from an existing `Request`.
|
||||||
|
///
|
||||||
|
/// Returns `Err` if `req` does not contain any query parameters.
|
||||||
|
pub fn from_request<T>(req: &'a Request<T>) -> Result<Self, ApiError> {
|
||||||
|
let query_str = req.uri().query().ok_or_else(|| {
|
||||||
|
ApiError::InvalidQueryParams(
|
||||||
|
"URL query must be valid and contain at least one
|
||||||
|
key."
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(UrlQuery(url::form_urlencoded::parse(query_str.as_bytes())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the first `(key, value)` pair found where the `key` is in `keys`.
|
||||||
|
///
|
||||||
|
/// If no match is found, an `InvalidQueryParams` error is returned.
|
||||||
|
pub fn first_of(mut self, keys: &[&str]) -> Result<(String, String), ApiError> {
|
||||||
|
self.0
|
||||||
|
.find(|(key, _value)| keys.contains(&&**key))
|
||||||
|
.map(|(key, value)| (key.into_owned(), value.into_owned()))
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ApiError::InvalidQueryParams(format!(
|
||||||
|
"URL query must contain at least one of the following keys: {:?}",
|
||||||
|
keys
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the value for `key`, if and only if `key` is the only key present in the query
|
||||||
|
/// parameters.
|
||||||
|
pub fn only_one(self, key: &str) -> Result<String, ApiError> {
|
||||||
|
let queries: Vec<_> = self
|
||||||
|
.0
|
||||||
|
.map(|(k, v)| (k.into_owned(), v.into_owned()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if queries.len() == 1 {
|
||||||
|
let (first_key, first_value) = &queries[0]; // Must have 0 index if len is 1.
|
||||||
|
if first_key == key {
|
||||||
|
Ok(first_value.to_string())
|
||||||
|
} else {
|
||||||
|
Err(ApiError::InvalidQueryParams(format!(
|
||||||
|
"Only the {} query parameter is supported",
|
||||||
|
key
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ApiError::InvalidQueryParams(format!(
|
||||||
|
"Only one query parameter is allowed, {} supplied",
|
||||||
|
queries.len()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_one() {
|
||||||
|
let get_result = |addr: &str, key: &str| -> Result<String, ApiError> {
|
||||||
|
UrlQuery(url::Url::parse(addr).unwrap().query_pairs()).only_one(key)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(get_result("http://cat.io/?a=42", "a"), Ok("42".to_string()));
|
||||||
|
assert!(get_result("http://cat.io/?a=42", "b").is_err());
|
||||||
|
assert!(get_result("http://cat.io/?a=42&b=12", "a").is_err());
|
||||||
|
assert!(get_result("http://cat.io/", "").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn first_of() {
|
||||||
|
let url = url::Url::parse("http://lighthouse.io/cats?a=42&b=12&c=100").unwrap();
|
||||||
|
let get_query = || UrlQuery(url.query_pairs());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_query().first_of(&["a"]),
|
||||||
|
Ok(("a".to_string(), "42".to_string()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_query().first_of(&["a", "b", "c"]),
|
||||||
|
Ok(("a".to_string(), "42".to_string()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_query().first_of(&["a", "a", "a"]),
|
||||||
|
Ok(("a".to_string(), "42".to_string()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_query().first_of(&["a", "b", "c"]),
|
||||||
|
Ok(("a".to_string(), "42".to_string()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_query().first_of(&["b", "c"]),
|
||||||
|
Ok(("b".to_string(), "12".to_string()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
get_query().first_of(&["c"]),
|
||||||
|
Ok(("c".to_string(), "100".to_string()))
|
||||||
|
);
|
||||||
|
assert!(get_query().first_of(&["nothing"]).is_err());
|
||||||
|
}
|
||||||
|
}
|
@ -209,11 +209,10 @@ fn main() {
|
|||||||
Arg::with_name("debug-level")
|
Arg::with_name("debug-level")
|
||||||
.long("debug-level")
|
.long("debug-level")
|
||||||
.value_name("LEVEL")
|
.value_name("LEVEL")
|
||||||
.short("s")
|
|
||||||
.help("The title of the spec constants for chain config.")
|
.help("The title of the spec constants for chain config.")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.possible_values(&["info", "debug", "trace", "warn", "error", "crit"])
|
.possible_values(&["info", "debug", "trace", "warn", "error", "crit"])
|
||||||
.default_value("info"),
|
.default_value("trace"),
|
||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
|
@ -24,6 +24,15 @@ impl<'a, U: Store, E: EthSpec> AncestorIter<U, BlockRootsIterator<'a, E, U>> for
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, U: Store, E: EthSpec> AncestorIter<U, StateRootsIterator<'a, E, U>> for BeaconState<E> {
|
||||||
|
/// Iterates across all the prior state roots of `self`, starting at the most recent and ending
|
||||||
|
/// at genesis.
|
||||||
|
fn try_iter_ancestor_roots(&self, store: Arc<U>) -> Option<StateRootsIterator<'a, E, U>> {
|
||||||
|
// The `self.clone()` here is wasteful.
|
||||||
|
Some(StateRootsIterator::owned(store, self.clone(), self.slot))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct StateRootsIterator<'a, T: EthSpec, U> {
|
pub struct StateRootsIterator<'a, T: EthSpec, U> {
|
||||||
store: Arc<U>,
|
store: Arc<U>,
|
||||||
|
Loading…
Reference in New Issue
Block a user