#[macro_use] extern crate lazy_static; extern crate network as client_network; mod beacon; mod config; mod helpers; mod metrics; mod network; mod node; mod spec; mod url_query; use beacon_chain::{BeaconChain, BeaconChainTypes}; use client_network::Service as NetworkService; use hyper::rt::Future; use hyper::service::service_fn_ok; use hyper::{Body, Method, Response, Server, StatusCode}; use slog::{info, o, warn}; use std::ops::Deref; use std::path::PathBuf; use std::sync::Arc; use tokio::runtime::TaskExecutor; use url_query::UrlQuery; pub use beacon::{BlockResponse, HeadResponse, StateResponse}; pub use config::Config as ApiConfig; #[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, ApiError>; impl Into> for ApiError { fn into(self) -> Response { 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::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) .body(Body::from(status_code.1)) .expect("Response should always be created.") } } impl From for ApiError { fn from(e: store::Error) -> ApiError { ApiError::ServerError(format!("Database error: {:?}", e)) } } impl From for ApiError { fn from(e: types::BeaconStateError) -> ApiError { ApiError::ServerError(format!("BeaconState error: {:?}", e)) } } impl From for ApiError { fn from(e: state_processing::per_slot_processing::Error) -> ApiError { ApiError::ServerError(format!("PerSlotProcessing error: {:?}", e)) } } pub fn start_server( config: &ApiConfig, executor: &TaskExecutor, beacon_chain: Arc>, network_service: Arc>, db_path: PathBuf, log: &slog::Logger, ) -> Result { let log = log.new(o!("Service" => "Api")); // build a channel to kill the HTTP server let (exit_signal, exit) = exit_future::signal(); let exit_log = log.clone(); let server_exit = exit.and_then(move |_| { info!(exit_log, "API service shutdown"); Ok(()) }); let db_path = DBPath(db_path); // Get the address to bind to let bind_addr = (config.listen_address, config.port).into(); // Clone our stateful objects, for use in service closure. let server_log = log.clone(); let server_bc = beacon_chain.clone(); let service = move || { let log = server_log.clone(); let beacon_chain = server_bc.clone(); let db_path = db_path.clone(); let network_service = network_service.clone(); // Create a simple handler for the router, inject our stateful objects into the request. service_fn_ok(move |mut req| { metrics::inc_counter(&metrics::REQUEST_COUNT); let timer = metrics::start_timer(&metrics::REQUEST_RESPONSE_TIME); req.extensions_mut().insert::(log.clone()); req.extensions_mut() .insert::>>(beacon_chain.clone()); req.extensions_mut().insert::(db_path.clone()); req.extensions_mut() .insert::>>(network_service.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/head") => beacon::get_head::(req), (&Method::GET, "/beacon/block") => beacon::get_block::(req), (&Method::GET, "/beacon/block_root") => beacon::get_block_root::(req), (&Method::GET, "/beacon/latest_finalized_checkpoint") => { beacon::get_latest_finalized_checkpoint::(req) } (&Method::GET, "/beacon/state") => beacon::get_state::(req), (&Method::GET, "/beacon/state_root") => beacon::get_state_root::(req), (&Method::GET, "/metrics") => metrics::get_prometheus::(req), (&Method::GET, "/network/enr") => network::get_enr::(req), (&Method::GET, "/network/peer_count") => network::get_peer_count::(req), (&Method::GET, "/network/peer_id") => network::get_peer_id::(req), (&Method::GET, "/network/peers") => network::get_peer_list::(req), (&Method::GET, "/network/listen_port") => network::get_listen_port::(req), (&Method::GET, "/network/listen_addresses") => { network::get_listen_addresses::(req) } (&Method::GET, "/node/version") => node::get_version(req), (&Method::GET, "/node/genesis_time") => node::get_genesis_time::(req), (&Method::GET, "/spec") => spec::get_spec::(req), (&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::(req), _ => Err(ApiError::MethodNotAllowed(path.clone())), }; let response = match result { // Return the `hyper::Response`. Ok(response) => { metrics::inc_counter(&metrics::SUCCESS_COUNT); slog::debug!(log, "Request successful: {:?}", path); response } // Map the `ApiError` into `hyper::Response`. Err(e) => { slog::debug!(log, "Request failure: {:?}", path); e.into() } }; metrics::stop_timer(timer); response }) }; let log_clone = log.clone(); let server = Server::bind(&bind_addr) .serve(service) .with_graceful_shutdown(server_exit) .map_err(move |e| { warn!( 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 success_response(body: Body) -> Response { Response::builder() .status(StatusCode::OK) .body(body) .expect("We should always be able to make response from the success body.") } #[derive(Clone)] pub struct DBPath(PathBuf); impl Deref for DBPath { type Target = PathBuf; fn deref(&self) -> &Self::Target { &self.0 } }