WIP: Trying to restructure ApiService to be async.
This commit is contained in:
parent
476cbae577
commit
405a59e8b9
61
beacon_node/rest_api/src/error.rs
Normal file
61
beacon_node/rest_api/src/error.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use hyper::{Body, Method, Request, Response, Server, StatusCode};
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
|
type Cause = Box<dyn StdErr + Send + Sync>;
|
||||||
|
|
||||||
|
pub struct ApiError {
|
||||||
|
kind: ApiErrorKind,
|
||||||
|
cause: Option<Cause>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Response<Body>, 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::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<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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ApiError {
|
||||||
|
fn cause(&self) -> Option<&Error> {}
|
||||||
|
}
|
@ -4,6 +4,7 @@ extern crate network as client_network;
|
|||||||
|
|
||||||
mod beacon;
|
mod beacon;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod error;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod metrics;
|
mod metrics;
|
||||||
mod network;
|
mod network;
|
||||||
@ -32,52 +33,143 @@ use url_query::UrlQuery;
|
|||||||
|
|
||||||
pub use beacon::{BlockResponse, HeadResponse, StateResponse};
|
pub use beacon::{BlockResponse, HeadResponse, StateResponse};
|
||||||
pub use config::Config as ApiConfig;
|
pub use config::Config as ApiConfig;
|
||||||
|
use eth2_libp2p::rpc::RequestId;
|
||||||
|
use serde::ser::StdError;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
type BoxFut = Box<dyn Future<Item = Response<Body>, Error = ApiError> + Send>;
|
||||||
pub enum ApiError {
|
|
||||||
MethodNotAllowed(String),
|
pub struct ApiService<T: BeaconChainTypes + 'static> {
|
||||||
ServerError(String),
|
log: slog::Logger,
|
||||||
NotImplemented(String),
|
beacon_chain: Arc<BeaconChain<T>>,
|
||||||
InvalidQueryParams(String),
|
db_path: DBPath,
|
||||||
NotFound(String),
|
network_service: Arc<NetworkService<T>>,
|
||||||
ImATeapot(String), // Just in case.
|
network_channel: Arc<RwLock<mpsc::UnboundedSender<NetworkMessage>>>,
|
||||||
|
eth2_config: Arc<Eth2Config>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ApiResult = Result<Response<Body>, ApiError>;
|
impl<T: BeaconChainTypes> Service for ApiService<T> {
|
||||||
|
type ReqBody = Body;
|
||||||
|
type ResBody = Body;
|
||||||
|
type Error = ApiError;
|
||||||
|
type Future = BoxFut;
|
||||||
|
|
||||||
impl Into<Response<Body>> for ApiError {
|
fn call(&mut self, mut req: Request<Body>) -> Self::Future {
|
||||||
fn into(self) -> Response<Body> {
|
metrics::inc_counter(&metrics::REQUEST_COUNT);
|
||||||
let status_code: (StatusCode, String) = match self {
|
let timer = metrics::start_timer(&metrics::REQUEST_RESPONSE_TIME);
|
||||||
ApiError::MethodNotAllowed(desc) => (StatusCode::METHOD_NOT_ALLOWED, desc),
|
|
||||||
ApiError::ServerError(desc) => (StatusCode::INTERNAL_SERVER_ERROR, desc),
|
req.extensions_mut()
|
||||||
ApiError::NotImplemented(desc) => (StatusCode::NOT_IMPLEMENTED, desc),
|
.insert::<slog::Logger>(self.log.clone());
|
||||||
ApiError::InvalidQueryParams(desc) => (StatusCode::BAD_REQUEST, desc),
|
req.extensions_mut()
|
||||||
ApiError::NotFound(desc) => (StatusCode::NOT_FOUND, desc),
|
.insert::<Arc<BeaconChain<T>>>(self.beacon_chain.clone());
|
||||||
ApiError::ImATeapot(desc) => (StatusCode::IM_A_TEAPOT, desc),
|
req.extensions_mut().insert::<DBPath>(self.db_path.clone());
|
||||||
|
req.extensions_mut()
|
||||||
|
.insert::<Arc<NetworkService<T>>>(self.network_service.clone());
|
||||||
|
req.extensions_mut()
|
||||||
|
.insert::<Arc<RwLock<mpsc::UnboundedSender<NetworkMessage>>>>(
|
||||||
|
self.network_channel.clone(),
|
||||||
|
);
|
||||||
|
req.extensions_mut()
|
||||||
|
.insert::<Arc<Eth2Config>>(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::<T>(req),
|
||||||
|
(&Method::GET, "/node/syncing") => helpers::implementation_pending_response(req),
|
||||||
|
|
||||||
|
// Methods for Network
|
||||||
|
(&Method::GET, "/network/enr") => network::get_enr::<T>(req),
|
||||||
|
(&Method::GET, "/network/peer_count") => network::get_peer_count::<T>(req),
|
||||||
|
(&Method::GET, "/network/peer_id") => network::get_peer_id::<T>(req),
|
||||||
|
(&Method::GET, "/network/peers") => network::get_peer_list::<T>(req),
|
||||||
|
(&Method::GET, "/network/listen_port") => network::get_listen_port::<T>(req),
|
||||||
|
(&Method::GET, "/network/listen_addresses") => {
|
||||||
|
network::get_listen_addresses::<T>(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods for Beacon Node
|
||||||
|
(&Method::GET, "/beacon/head") => beacon::get_head::<T>(req),
|
||||||
|
(&Method::GET, "/beacon/block") => beacon::get_block::<T>(req),
|
||||||
|
(&Method::GET, "/beacon/block_root") => beacon::get_block_root::<T>(req),
|
||||||
|
(&Method::GET, "/beacon/blocks") => helpers::implementation_pending_response(req),
|
||||||
|
(&Method::GET, "/beacon/fork") => beacon::get_fork::<T>(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::<T>(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::<T>(req)
|
||||||
|
}
|
||||||
|
(&Method::GET, "/beacon/validator/block") => {
|
||||||
|
validator::get_new_beacon_block::<T>(req)
|
||||||
|
}
|
||||||
|
(&Method::POST, "/beacon/validator/block") => {
|
||||||
|
validator::publish_beacon_block::<T>(req)
|
||||||
|
}
|
||||||
|
(&Method::GET, "/beacon/validator/attestation") => {
|
||||||
|
validator::get_new_attestation::<T>(req)
|
||||||
|
}
|
||||||
|
(&Method::POST, "/beacon/validator/attestation") => {
|
||||||
|
helpers::implementation_pending_response(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
(&Method::GET, "/beacon/state") => beacon::get_state::<T>(req),
|
||||||
|
(&Method::GET, "/beacon/state_root") => beacon::get_state_root::<T>(req),
|
||||||
|
(&Method::GET, "/beacon/state/current_finalized_checkpoint") => {
|
||||||
|
beacon::get_current_finalized_checkpoint::<T>(req)
|
||||||
|
}
|
||||||
|
(&Method::GET, "/beacon/state/genesis") => beacon::get_genesis_state::<T>(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::<T>(req),
|
||||||
|
(&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::<T>(req),
|
||||||
|
(&Method::GET, "/spec/deposit_contract") => {
|
||||||
|
helpers::implementation_pending_response(req)
|
||||||
|
}
|
||||||
|
(&Method::GET, "/spec/eth2_config") => spec::get_eth2_config::<T>(req),
|
||||||
|
|
||||||
|
(&Method::GET, "/metrics") => metrics::get_prometheus::<T>(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<store::Error> for ApiError {
|
let response = match result {
|
||||||
fn from(e: store::Error) -> ApiError {
|
// Return the `hyper::Response`.
|
||||||
ApiError::ServerError(format!("Database error: {:?}", e))
|
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<types::BeaconStateError> for ApiError {
|
metrics::stop_timer(timer);
|
||||||
fn from(e: types::BeaconStateError) -> ApiError {
|
|
||||||
ApiError::ServerError(format!("BeaconState error: {:?}", e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<state_processing::per_slot_processing::Error> for ApiError {
|
Box::new(futures::future::ok(response))
|
||||||
fn from(e: state_processing::per_slot_processing::Error) -> ApiError {
|
|
||||||
ApiError::ServerError(format!("PerSlotProcessing error: {:?}", e))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,128 +204,13 @@ pub fn start_server<T: BeaconChainTypes>(
|
|||||||
let server_bc = beacon_chain.clone();
|
let server_bc = beacon_chain.clone();
|
||||||
let eth2_config = Arc::new(eth2_config);
|
let eth2_config = Arc::new(eth2_config);
|
||||||
|
|
||||||
let service = move || {
|
let service = move || ApiService {
|
||||||
let log = server_log.clone();
|
log: server_log.clone(),
|
||||||
let beacon_chain = server_bc.clone();
|
beacon_chain: server_bc.clone(),
|
||||||
let db_path = db_path.clone();
|
db_path: db_path.clone(),
|
||||||
let network_service = network_service.clone();
|
network_service: network_service.clone(),
|
||||||
let network_chan = network_chan.clone();
|
network_channel: Arc::new(RwLock::new(network_chan.clone())),
|
||||||
let eth2_config = eth2_config.clone();
|
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::<slog::Logger>(log.clone());
|
|
||||||
req.extensions_mut()
|
|
||||||
.insert::<Arc<BeaconChain<T>>>(beacon_chain.clone());
|
|
||||||
req.extensions_mut().insert::<DBPath>(db_path.clone());
|
|
||||||
req.extensions_mut()
|
|
||||||
.insert::<Arc<NetworkService<T>>>(network_service.clone());
|
|
||||||
req.extensions_mut()
|
|
||||||
.insert::<mpsc::UnboundedSender<NetworkMessage>>(network_chan.clone());
|
|
||||||
req.extensions_mut()
|
|
||||||
.insert::<Arc<Eth2Config>>(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::<T>(req),
|
|
||||||
(&Method::GET, "/node/syncing") => helpers::implementation_pending_response(req),
|
|
||||||
|
|
||||||
// Methods for Network
|
|
||||||
(&Method::GET, "/network/enr") => network::get_enr::<T>(req),
|
|
||||||
(&Method::GET, "/network/peer_count") => network::get_peer_count::<T>(req),
|
|
||||||
(&Method::GET, "/network/peer_id") => network::get_peer_id::<T>(req),
|
|
||||||
(&Method::GET, "/network/peers") => network::get_peer_list::<T>(req),
|
|
||||||
(&Method::GET, "/network/listen_port") => network::get_listen_port::<T>(req),
|
|
||||||
(&Method::GET, "/network/listen_addresses") => {
|
|
||||||
network::get_listen_addresses::<T>(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods for Beacon Node
|
|
||||||
(&Method::GET, "/beacon/head") => beacon::get_head::<T>(req),
|
|
||||||
(&Method::GET, "/beacon/block") => beacon::get_block::<T>(req),
|
|
||||||
(&Method::GET, "/beacon/block_root") => beacon::get_block_root::<T>(req),
|
|
||||||
(&Method::GET, "/beacon/blocks") => helpers::implementation_pending_response(req),
|
|
||||||
(&Method::GET, "/beacon/fork") => beacon::get_fork::<T>(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::<T>(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::<T>(req)
|
|
||||||
}
|
|
||||||
(&Method::GET, "/beacon/validator/block") => {
|
|
||||||
validator::get_new_beacon_block::<T>(req)
|
|
||||||
}
|
|
||||||
(&Method::POST, "/beacon/validator/block") => {
|
|
||||||
validator::publish_beacon_block::<T>(req)
|
|
||||||
}
|
|
||||||
(&Method::GET, "/beacon/validator/attestation") => {
|
|
||||||
validator::get_new_attestation::<T>(req)
|
|
||||||
}
|
|
||||||
(&Method::POST, "/beacon/validator/attestation") => {
|
|
||||||
helpers::implementation_pending_response(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
(&Method::GET, "/beacon/state") => beacon::get_state::<T>(req),
|
|
||||||
(&Method::GET, "/beacon/state_root") => beacon::get_state_root::<T>(req),
|
|
||||||
(&Method::GET, "/beacon/state/current_finalized_checkpoint") => {
|
|
||||||
beacon::get_current_finalized_checkpoint::<T>(req)
|
|
||||||
}
|
|
||||||
(&Method::GET, "/beacon/state/genesis") => beacon::get_genesis_state::<T>(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::<T>(req),
|
|
||||||
(&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::<T>(req),
|
|
||||||
(&Method::GET, "/spec/deposit_contract") => {
|
|
||||||
helpers::implementation_pending_response(req)
|
|
||||||
}
|
|
||||||
(&Method::GET, "/spec/eth2_config") => spec::get_eth2_config::<T>(req),
|
|
||||||
|
|
||||||
(&Method::GET, "/metrics") => metrics::get_prometheus::<T>(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 log_clone = log.clone();
|
let log_clone = log.clone();
|
||||||
@ -242,16 +219,16 @@ pub fn start_server<T: BeaconChainTypes>(
|
|||||||
.with_graceful_shutdown(server_exit)
|
.with_graceful_shutdown(server_exit)
|
||||||
.map_err(move |e| {
|
.map_err(move |e| {
|
||||||
warn!(
|
warn!(
|
||||||
log_clone,
|
log_clone,
|
||||||
"API failed to start, Unable to bind"; "address" => format!("{:?}", e)
|
"API failed to start, Unable to bind"; "address" => format!("{:?}", e)
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
log,
|
log,
|
||||||
"REST API started";
|
"REST API started";
|
||||||
"address" => format!("{}", config.listen_address),
|
"address" => format!("{}", config.listen_address),
|
||||||
"port" => config.port,
|
"port" => config.port,
|
||||||
);
|
);
|
||||||
|
|
||||||
executor.spawn(server);
|
executor.spawn(server);
|
||||||
|
Loading…
Reference in New Issue
Block a user