Add validator changes from validator-to-rest

This commit is contained in:
Paul Hauner
2019-11-12 16:50:12 +11:00
parent f229bbba1c
commit 41805611d0
5 changed files with 86 additions and 76 deletions

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "rest_api" name = "rest_api"
version = "0.1.0" version = "0.1.0"
authors = ["Luke Anderson <luke@lukeanderson.com.au>"] authors = ["Luke Anderson <luke@sigmaprime.io>"]
edition = "2018" 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
@@ -12,28 +12,27 @@ network = { path = "../network" }
eth2-libp2p = { path = "../eth2-libp2p" } eth2-libp2p = { path = "../eth2-libp2p" }
store = { path = "../store" } store = { path = "../store" }
version = { path = "../version" } version = { path = "../version" }
serde = { version = "1.0.102", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.41" serde_json = "^1.0"
serde_yaml = "0.8.11" serde_yaml = "0.8"
slog = "2.5.2" slog = "^2.2.3"
slog-term = "2.4.2" slog-term = "^2.4.0"
slog-async = "2.3.0" slog-async = "^2.3.0"
eth2_ssz = "0.1.2" eth2_ssz = { path = "../../eth2/utils/ssz" }
eth2_ssz_derive = "0.1.0" eth2_ssz_derive = { path = "../../eth2/utils/ssz_derive" }
state_processing = { path = "../../eth2/state_processing" } state_processing = { path = "../../eth2/state_processing" }
types = { path = "../../eth2/types" } types = { path = "../../eth2/types" }
clap = "2.33.0" clap = "2.32.0"
http = "0.1.19" http = "^0.1.17"
prometheus = { version = "0.7.0", features = ["process"] } prometheus = { version = "^0.6", features = ["process"] }
hyper = "0.12.35" hyper = "0.12.35"
exit-future = "0.1.4" exit-future = "0.1.3"
tokio = "0.1.22" tokio = "0.1.17"
url = "2.1.0" url = "2.0"
lazy_static = "1.4.0" lazy_static = "1.3.0"
eth2_config = { path = "../../eth2/utils/eth2_config" } eth2_config = { path = "../../eth2/utils/eth2_config" }
lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" } lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" }
slot_clock = { path = "../../eth2/utils/slot_clock" } slot_clock = { path = "../../eth2/utils/slot_clock" }
hex = "0.3" hex = "0.3.2"
parking_lot = "0.9.0" parking_lot = "0.9"
futures = "0.1.29" futures = "0.1.25"

View File

@@ -2,6 +2,37 @@ use clap::ArgMatches;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
/// Defines the encoding for the API
///
/// The validator client can speak to the beacon node in a number of encodings. Currently both JSON
/// and YAML are supported.
#[derive(Clone, Serialize, Deserialize, Copy)]
pub enum ApiEncodingFormat {
JSON,
YAML,
SSZ,
}
impl ApiEncodingFormat {
pub fn get_content_type(&self) -> &str {
match self {
ApiEncodingFormat::JSON => "application/json",
ApiEncodingFormat::YAML => "application/yaml",
ApiEncodingFormat::SSZ => "application/ssz",
}
}
}
impl From<&str> for ApiEncodingFormat {
fn from(f: &str) -> ApiEncodingFormat {
match f {
"application/yaml" => ApiEncodingFormat::YAML,
"application/ssz" => ApiEncodingFormat::SSZ,
_ => ApiEncodingFormat::JSON,
}
}
}
/// HTTP REST API Configuration /// HTTP REST API Configuration
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config { pub struct Config {

View File

@@ -5,9 +5,9 @@ extern crate lazy_static;
extern crate network as client_network; extern crate network as client_network;
mod beacon; mod beacon;
mod config; pub mod config;
mod error; mod error;
mod helpers; pub mod helpers;
mod metrics; mod metrics;
mod network; mod network;
mod node; mod node;
@@ -19,6 +19,7 @@ mod validator;
use beacon_chain::{BeaconChain, BeaconChainTypes}; use beacon_chain::{BeaconChain, BeaconChainTypes};
use client_network::NetworkMessage; use client_network::NetworkMessage;
use client_network::Service as NetworkService; use client_network::Service as NetworkService;
pub use config::ApiEncodingFormat;
use error::{ApiError, ApiResult}; use error::{ApiError, ApiResult};
use eth2_config::Eth2Config; use eth2_config::Eth2Config;
use futures::future::IntoFuture; use futures::future::IntoFuture;
@@ -26,8 +27,7 @@ use hyper::rt::Future;
use hyper::service::Service; use hyper::service::Service;
use hyper::{Body, Method, Request, Response, Server}; use hyper::{Body, Method, Request, Response, Server};
use parking_lot::RwLock; use parking_lot::RwLock;
use slog::{info, warn}; use slog::{info, o, warn};
use std::net::SocketAddr;
use std::ops::Deref; use std::ops::Deref;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
@@ -35,8 +35,10 @@ use tokio::runtime::TaskExecutor;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use url_query::UrlQuery; use url_query::UrlQuery;
pub use crate::helpers::parse_pubkey;
pub use beacon::{BlockResponse, HeadResponse, StateResponse}; pub use beacon::{BlockResponse, HeadResponse, StateResponse};
pub use config::Config; pub use config::Config as ApiConfig;
pub use validator::ValidatorDuty;
type BoxFut = Box<dyn Future<Item = Response<Body>, Error = ApiError> + Send>; type BoxFut = Box<dyn Future<Item = Response<Body>, Error = ApiError> + Send>;
@@ -197,14 +199,16 @@ impl<T: BeaconChainTypes> Service for ApiService<T> {
} }
pub fn start_server<T: BeaconChainTypes>( pub fn start_server<T: BeaconChainTypes>(
config: &Config, config: &ApiConfig,
executor: &TaskExecutor, executor: &TaskExecutor,
beacon_chain: Arc<BeaconChain<T>>, beacon_chain: Arc<BeaconChain<T>>,
network_info: NetworkInfo<T>, network_info: NetworkInfo<T>,
db_path: PathBuf, db_path: PathBuf,
eth2_config: Eth2Config, eth2_config: Eth2Config,
log: slog::Logger, log: &slog::Logger,
) -> Result<(exit_future::Signal, SocketAddr), hyper::Error> { ) -> Result<exit_future::Signal, hyper::Error> {
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();
@@ -236,11 +240,8 @@ pub fn start_server<T: BeaconChainTypes>(
}; };
let log_clone = log.clone(); let log_clone = log.clone();
let server = Server::bind(&bind_addr).serve(service); let server = Server::bind(&bind_addr)
.serve(service)
let actual_listen_addr = server.local_addr();
let server_future = server
.with_graceful_shutdown(server_exit) .with_graceful_shutdown(server_exit)
.map_err(move |e| { .map_err(move |e| {
warn!( warn!(
@@ -250,15 +251,15 @@ pub fn start_server<T: BeaconChainTypes>(
}); });
info!( info!(
log, log,
"REST API started"; "REST API started";
"address" => format!("{}", actual_listen_addr.ip()), "address" => format!("{}", config.listen_address),
"port" => actual_listen_addr.port(), "port" => config.port,
); );
executor.spawn(server_future); executor.spawn(server);
Ok((exit_signal, actual_listen_addr)) Ok(exit_signal)
} }
#[derive(Clone)] #[derive(Clone)]

View File

@@ -1,47 +1,36 @@
use super::{ApiError, ApiResult}; use super::{ApiError, ApiResult};
use crate::config::ApiEncodingFormat;
use http::header; use http::header;
use hyper::{Body, Request, Response, StatusCode}; use hyper::{Body, Request, Response, StatusCode};
use serde::Serialize; use serde::Serialize;
use ssz::Encode; use ssz::Encode;
pub enum Encoding {
JSON,
SSZ,
YAML,
TEXT,
}
pub struct ResponseBuilder { pub struct ResponseBuilder {
encoding: Encoding, encoding: ApiEncodingFormat,
} }
impl ResponseBuilder { impl ResponseBuilder {
pub fn new(req: &Request<Body>) -> Result<Self, ApiError> { pub fn new(req: &Request<Body>) -> Result<Self, ApiError> {
let content_header: String = req let accept_header: String = req
.headers() .headers()
.get(header::CONTENT_TYPE) .get(header::ACCEPT)
.map_or(Ok(""), |h| h.to_str()) .map_or(Ok(""), |h| h.to_str())
.map_err(|e| { .map_err(|e| {
ApiError::BadRequest(format!( ApiError::BadRequest(format!(
"The content-type header contains invalid characters: {:?}", "The Accept header contains invalid characters: {:?}",
e e
)) ))
}) })
.map(String::from)?; .map(String::from)?;
// JSON is our default encoding, unless something else is requested. // JSON is our default encoding, unless something else is requested.
let encoding = match content_header { let encoding = ApiEncodingFormat::from(accept_header.as_str());
ref h if h.starts_with("application/ssz") => Encoding::SSZ,
ref h if h.starts_with("application/yaml") => Encoding::YAML,
ref h if h.starts_with("text/") => Encoding::TEXT,
_ => Encoding::JSON,
};
Ok(Self { encoding }) Ok(Self { encoding })
} }
pub fn body<T: Serialize + Encode>(self, item: &T) -> ApiResult { pub fn body<T: Serialize + Encode>(self, item: &T) -> ApiResult {
match self.encoding { match self.encoding {
Encoding::SSZ => Response::builder() ApiEncodingFormat::SSZ => Response::builder()
.status(StatusCode::OK) .status(StatusCode::OK)
.header("content-type", "application/ssz") .header("content-type", "application/ssz")
.body(Body::from(item.as_ssz_bytes())) .body(Body::from(item.as_ssz_bytes()))
@@ -52,7 +41,7 @@ impl ResponseBuilder {
pub fn body_no_ssz<T: Serialize>(self, item: &T) -> ApiResult { pub fn body_no_ssz<T: Serialize>(self, item: &T) -> ApiResult {
let (body, content_type) = match self.encoding { let (body, content_type) = match self.encoding {
Encoding::JSON => ( ApiEncodingFormat::JSON => (
Body::from(serde_json::to_string(&item).map_err(|e| { Body::from(serde_json::to_string(&item).map_err(|e| {
ApiError::ServerError(format!( ApiError::ServerError(format!(
"Unable to serialize response body as JSON: {:?}", "Unable to serialize response body as JSON: {:?}",
@@ -61,12 +50,12 @@ impl ResponseBuilder {
})?), })?),
"application/json", "application/json",
), ),
Encoding::SSZ => { ApiEncodingFormat::SSZ => {
return Err(ApiError::UnsupportedType( return Err(ApiError::UnsupportedType(
"Response cannot be encoded as SSZ.".into(), "Response cannot be encoded as SSZ.".into(),
)); ));
} }
Encoding::YAML => ( ApiEncodingFormat::YAML => (
Body::from(serde_yaml::to_string(&item).map_err(|e| { Body::from(serde_yaml::to_string(&item).map_err(|e| {
ApiError::ServerError(format!( ApiError::ServerError(format!(
"Unable to serialize response body as YAML: {:?}", "Unable to serialize response body as YAML: {:?}",
@@ -75,11 +64,6 @@ impl ResponseBuilder {
})?), })?),
"application/yaml", "application/yaml",
), ),
Encoding::TEXT => {
return Err(ApiError::UnsupportedType(
"Response cannot be encoded as plain text.".into(),
));
}
}; };
Response::builder() Response::builder()

View File

@@ -5,7 +5,7 @@ use crate::helpers::{
use crate::response_builder::ResponseBuilder; use crate::response_builder::ResponseBuilder;
use crate::{ApiError, ApiResult, BoxFut, UrlQuery}; use crate::{ApiError, ApiResult, BoxFut, UrlQuery};
use beacon_chain::{AttestationProcessingOutcome, BeaconChainTypes, BlockProcessingOutcome}; use beacon_chain::{AttestationProcessingOutcome, BeaconChainTypes, BlockProcessingOutcome};
use bls::{AggregateSignature, PublicKey, Signature}; use bls::{AggregateSignature, PublicKey, Signature, BLS_PUBLIC_KEY_BYTE_SIZE};
use futures::future::Future; use futures::future::Future;
use futures::stream::Stream; use futures::stream::Stream;
use hyper::{Body, Request}; use hyper::{Body, Request};
@@ -22,7 +22,7 @@ use types::{Attestation, BeaconBlock, BitList, Epoch, RelativeEpoch, Shard, Slot
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ValidatorDuty { pub struct ValidatorDuty {
/// The validator's BLS public key, uniquely identifying them. _48-bytes, hex encoded with 0x prefix, case insensitive._ /// The validator's BLS public key, uniquely identifying them. _48-bytes, hex encoded with 0x prefix, case insensitive._
pub validator_pubkey: String, pub validator_pubkey: PublicKey,
/// The slot at which the validator must attest. /// The slot at which the validator must attest.
pub attestation_slot: Option<Slot>, pub attestation_slot: Option<Slot>,
/// The shard in which the validator must attest. /// The shard in which the validator must attest.
@@ -34,7 +34,8 @@ pub struct ValidatorDuty {
impl ValidatorDuty { impl ValidatorDuty {
pub fn new() -> ValidatorDuty { pub fn new() -> ValidatorDuty {
ValidatorDuty { ValidatorDuty {
validator_pubkey: "".to_string(), validator_pubkey: PublicKey::from_bytes(vec![0; BLS_PUBLIC_KEY_BYTE_SIZE].as_slice())
.expect("Should always be able to create a 'zero' BLS public key."),
attestation_slot: None, attestation_slot: None,
attestation_shard: None, attestation_shard: None,
block_proposal_slot: None, block_proposal_slot: None,
@@ -103,7 +104,7 @@ pub fn get_validator_duties<T: BeaconChainTypes + 'static>(req: Request<Body>) -
// Look up duties for each validator // Look up duties for each validator
for val_pk in validators { for val_pk in validators {
let mut duty = ValidatorDuty::new(); let mut duty = ValidatorDuty::new();
duty.validator_pubkey = val_pk.as_hex_string(); duty.validator_pubkey = val_pk.clone();
// Get the validator index // Get the validator index
// If it does not exist in the index, just add a null duty and move on. // If it does not exist in the index, just add a null duty and move on.
@@ -210,14 +211,8 @@ pub fn publish_beacon_block<T: BeaconChainTypes + 'static>(req: Request<Body>) -
Box::new(body Box::new(body
.concat2() .concat2()
.map_err(|e| ApiError::ServerError(format!("Unable to get request body: {:?}",e))) .map_err(|e| ApiError::ServerError(format!("Unable to get request body: {:?}",e)))
.map(|chunk| chunk.iter().cloned().collect::<Vec<u8>>())
.and_then(|chunks| { .and_then(|chunks| {
serde_json::from_slice(&chunks.as_slice()).map_err(|e| { serde_json::from_slice(&chunks).map_err(|e| ApiError::BadRequest(format!("Unable to parse JSON into BeaconBlock: {:?}",e)))
ApiError::BadRequest(format!(
"Unable to deserialize JSON into a BeaconBlock: {:?}",
e
))
})
}) })
.and_then(move |block: BeaconBlock<T::EthSpec>| { .and_then(move |block: BeaconBlock<T::EthSpec>| {
let slot = block.slot; let slot = block.slot;