Changes to rest_api (#480)
* Add half-finished rest api changes * Add basic, messy changes to rest api * Fix expect() in ApiRequest * Remove ApiRequest, add route for beacon state * Tidy rest api, add get state from root * Add api method for getting state roots by slot * Add test for URL helper * Simplify state_at_slot fn * Add tests for rest api helper fns * Add extra tests for parse root * Fix clippy lints * Fix compile error in rest api * Update test to new ethereum-types
This commit is contained in:
		
							parent
							
								
									468015f9bb
								
							
						
					
					
						commit
						989e2727d7
					
				| @ -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
 | ||||
|     /// is the state as it was when the head block was received, which could be some slots prior to
 | ||||
|     /// now.
 | ||||
|     pub fn head(&self) -> RwLockReadGuard<CheckPoint<T::EthSpec>> { | ||||
|     pub fn head<'a>(&'a self) -> RwLockReadGuard<'a, CheckPoint<T::EthSpec>> { | ||||
|         self.canonical_head.read() | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -26,7 +26,7 @@ pub struct Config { | ||||
|     pub network: network::NetworkConfig, | ||||
|     pub rpc: rpc::RPCConfig, | ||||
|     pub http: HttpServerConfig, | ||||
|     pub rest_api: rest_api::APIConfig, | ||||
|     pub rest_api: rest_api::ApiConfig, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| @ -60,7 +60,7 @@ impl Default for Config { | ||||
|             network: NetworkConfig::new(), | ||||
|             rpc: rpc::RPCConfig::default(), | ||||
|             http: HttpServerConfig::default(), | ||||
|             rest_api: rest_api::APIConfig::default(), | ||||
|             rest_api: rest_api::ApiConfig::default(), | ||||
|             spec_constants: TESTNET_SPEC_CONSTANTS.into(), | ||||
|             genesis_state: GenesisState::RecentGenesis { | ||||
|                 validator_count: TESTNET_VALIDATOR_COUNT, | ||||
|  | ||||
| @ -7,16 +7,19 @@ edition = "2018" | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
| [dependencies] | ||||
| beacon_chain = { path = "../beacon_chain" } | ||||
| store = { path = "../store" } | ||||
| version = { path = "../version" } | ||||
| serde = { version = "1.0", features = ["derive"] } | ||||
| serde_json = "^1.0" | ||||
| slog = "^2.2.3" | ||||
| slog-term = "^2.4.0" | ||||
| slog-async = "^2.3.0" | ||||
| state_processing = { path = "../../eth2/state_processing" } | ||||
| types = { path = "../../eth2/types" } | ||||
| clap = "2.32.0" | ||||
| http = "^0.1.17" | ||||
| hyper = "0.12.32" | ||||
| hyper-router = "^0.5" | ||||
| futures = "0.1" | ||||
| exit-future = "0.1.3" | ||||
| 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 hyper; | ||||
| #[macro_use] | ||||
| mod macros; | ||||
| mod beacon_node; | ||||
| pub mod config; | ||||
| mod beacon; | ||||
| mod config; | ||||
| mod helpers; | ||||
| mod node; | ||||
| mod url_query; | ||||
| 
 | ||||
| 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 std::sync::Arc; | ||||
| use tokio::runtime::TaskExecutor; | ||||
| use url_query::UrlQuery; | ||||
| 
 | ||||
| use crate::beacon_node::BeaconNodeServiceInstance; | ||||
| use hyper::rt::Future; | ||||
| use hyper::service::{service_fn, Service}; | ||||
| use hyper::{Body, Request, Response, Server, StatusCode}; | ||||
| use hyper_router::{RouterBuilder, RouterService}; | ||||
| 
 | ||||
| pub enum APIError { | ||||
|     MethodNotAllowed { desc: String }, | ||||
|     ServerError { desc: String }, | ||||
|     NotImplemented { desc: String }, | ||||
| #[derive(PartialEq, Debug)] | ||||
| pub enum ApiError { | ||||
|     MethodNotAllowed(String), | ||||
|     ServerError(String), | ||||
|     NotImplemented(String), | ||||
|     InvalidQueryParams(String), | ||||
|     NotFound(String), | ||||
|     ImATeapot(String), // Just in case.
 | ||||
| } | ||||
| 
 | ||||
| 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> { | ||||
|         let status_code: (StatusCode, String) = match self { | ||||
|             APIError::MethodNotAllowed { desc } => (StatusCode::METHOD_NOT_ALLOWED, desc), | ||||
|             APIError::ServerError { desc } => (StatusCode::INTERNAL_SERVER_ERROR, desc), | ||||
|             APIError::NotImplemented { desc } => (StatusCode::NOT_IMPLEMENTED, desc), | ||||
|             ApiError::MethodNotAllowed(desc) => (StatusCode::METHOD_NOT_ALLOWED, desc), | ||||
|             ApiError::ServerError(desc) => (StatusCode::INTERNAL_SERVER_ERROR, 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() | ||||
|             .status(status_code.0) | ||||
| @ -40,17 +45,31 @@ impl Into<Response<Body>> for APIError { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub trait APIService { | ||||
|     fn add_routes(&mut self, router_builder: RouterBuilder) -> Result<RouterBuilder, hyper::Error>; | ||||
| impl From<store::Error> for ApiError { | ||||
|     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>( | ||||
|     config: &APIConfig, | ||||
|     config: &ApiConfig, | ||||
|     executor: &TaskExecutor, | ||||
|     beacon_chain: Arc<BeaconChain<T>>, | ||||
|     log: &slog::Logger, | ||||
| ) -> 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
 | ||||
|     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_bc = beacon_chain.clone(); | ||||
| 
 | ||||
|     // Create the service closure
 | ||||
|     let service = move || { | ||||
|         //TODO: This router must be moved out of this closure, so it isn't rebuilt for every connection.
 | ||||
|         let mut router = build_router_service::<T>(); | ||||
| 
 | ||||
|         // Clone our stateful objects, for use in handler closure
 | ||||
|         let service_log = server_log.clone(); | ||||
|         let service_bc = server_bc.clone(); | ||||
|         let log = server_log.clone(); | ||||
|         let beacon_chain = server_bc.clone(); | ||||
| 
 | ||||
|         // 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() | ||||
|                 .insert::<slog::Logger>(service_log.clone()); | ||||
|             req.extensions_mut() | ||||
|                 .insert::<Arc<BeaconChain<T>>>(service_bc.clone()); | ||||
|             router.call(req) | ||||
|                 .insert::<Arc<BeaconChain<T>>>(beacon_chain.clone()); | ||||
| 
 | ||||
|             let path = req.uri().path().to_string(); | ||||
| 
 | ||||
|             // 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) | ||||
|         .serve(service) | ||||
|         .with_graceful_shutdown(server_exit) | ||||
|         .map_err(move |e| { | ||||
|             warn!( | ||||
|                 log, | ||||
|                 log_clone, | ||||
|                 "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); | ||||
| 
 | ||||
|     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> { | ||||
|     Response::builder() | ||||
|         .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()); | ||||
|     } | ||||
| } | ||||
| @ -210,7 +210,6 @@ fn main() { | ||||
|             Arg::with_name("debug-level") | ||||
|                 .long("debug-level") | ||||
|                 .value_name("LEVEL") | ||||
|                 .short("s") | ||||
|                 .help("The title of the spec constants for chain config.") | ||||
|                 .takes_value(true) | ||||
|                 .possible_values(&["info", "debug", "trace", "warn", "error", "crit"]) | ||||
|  | ||||
| @ -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)] | ||||
| pub struct StateRootsIterator<'a, T: EthSpec, U> { | ||||
|     store: Arc<U>, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user