diff --git a/beacon_node/rest_api/src/error.rs b/beacon_node/rest_api/src/error.rs new file mode 100644 index 000000000..bae07ea0b --- /dev/null +++ b/beacon_node/rest_api/src/error.rs @@ -0,0 +1,61 @@ +use hyper::{Body, Method, Request, Response, Server, StatusCode}; +use std::error::Error as StdError; + +type Cause = Box; + +pub struct ApiError { + kind: ApiErrorKind, + cause: Option, +} + +#[derive(PartialEq, Debug)] +pub enum ApiErrorKind { + 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) + .header("content-type", "text/plain") + .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)) + } +} + +impl std::error::Error for ApiError { + fn cause(&self) -> Option<&Error> {} +} diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index adab0c3bb..56ed8c7bb 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -4,6 +4,7 @@ extern crate network as client_network; mod beacon; mod config; +mod error; mod helpers; mod metrics; mod network; @@ -32,52 +33,143 @@ use url_query::UrlQuery; pub use beacon::{BlockResponse, HeadResponse, StateResponse}; pub use config::Config as ApiConfig; +use eth2_libp2p::rpc::RequestId; +use serde::ser::StdError; -#[derive(PartialEq, Debug)] -pub enum ApiError { - MethodNotAllowed(String), - ServerError(String), - NotImplemented(String), - InvalidQueryParams(String), - NotFound(String), - ImATeapot(String), // Just in case. +type BoxFut = Box, Error = ApiError> + Send>; + +pub struct ApiService { + log: slog::Logger, + beacon_chain: Arc>, + db_path: DBPath, + network_service: Arc>, + network_channel: Arc>>, + eth2_config: Arc, } -pub type ApiResult = Result, ApiError>; +impl Service for ApiService { + type ReqBody = Body; + type ResBody = Body; + type Error = ApiError; + type Future = BoxFut; -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), + fn call(&mut self, mut req: Request) -> Self::Future { + metrics::inc_counter(&metrics::REQUEST_COUNT); + let timer = metrics::start_timer(&metrics::REQUEST_RESPONSE_TIME); + + req.extensions_mut() + .insert::(self.log.clone()); + req.extensions_mut() + .insert::>>(self.beacon_chain.clone()); + req.extensions_mut().insert::(self.db_path.clone()); + req.extensions_mut() + .insert::>>(self.network_service.clone()); + req.extensions_mut() + .insert::>>>( + self.network_channel.clone(), + ); + req.extensions_mut() + .insert::>(self.eth2_config.clone()); + + let path = req.uri().path().to_string(); + + // Route the request to the correct handler. + let result = match (req.method(), path.as_ref()) { + // Methods for Client + (&Method::GET, "/node/version") => node::get_version(req), + /* + (&Method::GET, "/node/genesis_time") => node::get_genesis_time::(req), + (&Method::GET, "/node/syncing") => helpers::implementation_pending_response(req), + + // Methods for Network + (&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) + } + + // Methods for Beacon Node + (&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/blocks") => helpers::implementation_pending_response(req), + (&Method::GET, "/beacon/fork") => beacon::get_fork::(req), + (&Method::GET, "/beacon/attestations") => { + helpers::implementation_pending_response(req) + } + (&Method::GET, "/beacon/attestations/pending") => { + helpers::implementation_pending_response(req) + } + + (&Method::GET, "/beacon/validators") => beacon::get_validators::(req), + (&Method::GET, "/beacon/validators/indicies") => { + helpers::implementation_pending_response(req) + } + (&Method::GET, "/beacon/validators/pubkeys") => { + helpers::implementation_pending_response(req) + } + + // Methods for Validator + (&Method::GET, "/beacon/validator/duties") => { + validator::get_validator_duties::(req) + } + (&Method::GET, "/beacon/validator/block") => { + validator::get_new_beacon_block::(req) + } + (&Method::POST, "/beacon/validator/block") => { + validator::publish_beacon_block::(req) + } + (&Method::GET, "/beacon/validator/attestation") => { + validator::get_new_attestation::(req) + } + (&Method::POST, "/beacon/validator/attestation") => { + helpers::implementation_pending_response(req) + } + + (&Method::GET, "/beacon/state") => beacon::get_state::(req), + (&Method::GET, "/beacon/state_root") => beacon::get_state_root::(req), + (&Method::GET, "/beacon/state/current_finalized_checkpoint") => { + beacon::get_current_finalized_checkpoint::(req) + } + (&Method::GET, "/beacon/state/genesis") => beacon::get_genesis_state::(req), + //TODO: Add aggreggate/filtered state lookups here, e.g. /beacon/validators/balances + + // Methods for bootstrap and checking configuration + (&Method::GET, "/spec") => spec::get_spec::(req), + (&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::(req), + (&Method::GET, "/spec/deposit_contract") => { + helpers::implementation_pending_response(req) + } + (&Method::GET, "/spec/eth2_config") => spec::get_eth2_config::(req), + + (&Method::GET, "/metrics") => metrics::get_prometheus::(req), + + */ + _ => Err(ApiError::NotFound( + "Request path and/or method not found.".to_owned(), + )), }; - Response::builder() - .status(status_code.0) - .header("content-type", "text/plain") - .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)) - } -} + let response = match result { + // Return the `hyper::Response`. + Ok(response) => { + metrics::inc_counter(&metrics::SUCCESS_COUNT); + slog::debug!(self.log, "Request successful: {:?}", path); + Box::new(response) + } + // Map the `ApiError` into `hyper::Response`. + Err(e) => { + slog::debug!(self.log, "Request failure: {:?}", path); + Box::new(e.into()) + } + }; -impl From for ApiError { - fn from(e: types::BeaconStateError) -> ApiError { - ApiError::ServerError(format!("BeaconState error: {:?}", e)) - } -} + metrics::stop_timer(timer); -impl From for ApiError { - fn from(e: state_processing::per_slot_processing::Error) -> ApiError { - ApiError::ServerError(format!("PerSlotProcessing error: {:?}", e)) + Box::new(futures::future::ok(response)) } } @@ -112,128 +204,13 @@ pub fn start_server( let server_bc = beacon_chain.clone(); let eth2_config = Arc::new(eth2_config); - 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(); - let network_chan = network_chan.clone(); - let eth2_config = eth2_config.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()); - req.extensions_mut() - .insert::>(network_chan.clone()); - req.extensions_mut() - .insert::>(eth2_config.clone()); - - let path = req.uri().path().to_string(); - - // Route the request to the correct handler. - let result = match (req.method(), path.as_ref()) { - // Methods for Client - (&Method::GET, "/node/version") => node::get_version(req), - (&Method::GET, "/node/genesis_time") => node::get_genesis_time::(req), - (&Method::GET, "/node/syncing") => helpers::implementation_pending_response(req), - - // Methods for Network - (&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) - } - - // Methods for Beacon Node - (&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/blocks") => helpers::implementation_pending_response(req), - (&Method::GET, "/beacon/fork") => beacon::get_fork::(req), - (&Method::GET, "/beacon/attestations") => { - helpers::implementation_pending_response(req) - } - (&Method::GET, "/beacon/attestations/pending") => { - helpers::implementation_pending_response(req) - } - - (&Method::GET, "/beacon/validators") => beacon::get_validators::(req), - (&Method::GET, "/beacon/validators/indicies") => { - helpers::implementation_pending_response(req) - } - (&Method::GET, "/beacon/validators/pubkeys") => { - helpers::implementation_pending_response(req) - } - - // Methods for Validator - (&Method::GET, "/beacon/validator/duties") => { - validator::get_validator_duties::(req) - } - (&Method::GET, "/beacon/validator/block") => { - validator::get_new_beacon_block::(req) - } - (&Method::POST, "/beacon/validator/block") => { - validator::publish_beacon_block::(req) - } - (&Method::GET, "/beacon/validator/attestation") => { - validator::get_new_attestation::(req) - } - (&Method::POST, "/beacon/validator/attestation") => { - helpers::implementation_pending_response(req) - } - - (&Method::GET, "/beacon/state") => beacon::get_state::(req), - (&Method::GET, "/beacon/state_root") => beacon::get_state_root::(req), - (&Method::GET, "/beacon/state/current_finalized_checkpoint") => { - beacon::get_current_finalized_checkpoint::(req) - } - (&Method::GET, "/beacon/state/genesis") => beacon::get_genesis_state::(req), - //TODO: Add aggreggate/filtered state lookups here, e.g. /beacon/validators/balances - - // Methods for bootstrap and checking configuration - (&Method::GET, "/spec") => spec::get_spec::(req), - (&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::(req), - (&Method::GET, "/spec/deposit_contract") => { - helpers::implementation_pending_response(req) - } - (&Method::GET, "/spec/eth2_config") => spec::get_eth2_config::(req), - - (&Method::GET, "/metrics") => metrics::get_prometheus::(req), - - _ => Err(ApiError::NotFound( - "Request path and/or method not found.".to_owned(), - )), - }; - - 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 service = move || ApiService { + log: server_log.clone(), + beacon_chain: server_bc.clone(), + db_path: db_path.clone(), + network_service: network_service.clone(), + network_channel: Arc::new(RwLock::new(network_chan.clone())), + eth2_config: eth2_config.clone(), }; let log_clone = log.clone(); @@ -242,16 +219,16 @@ pub fn start_server( .with_graceful_shutdown(server_exit) .map_err(move |e| { warn!( - log_clone, - "API failed to start, Unable to bind"; "address" => format!("{:?}", e) + 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, + log, + "REST API started"; + "address" => format!("{}", config.listen_address), + "port" => config.port, ); executor.spawn(server);