Add first attempts at HTTP bootstrap

This commit is contained in:
Paul Hauner 2019-08-14 10:36:55 +10:00
parent 82e8aafb01
commit 4f98a3985f
No known key found for this signature in database
GPG Key ID: 303E4494BB28068C
8 changed files with 162 additions and 0 deletions

View File

@ -27,3 +27,4 @@ clap = "2.32.0"
dirs = "1.0.3" dirs = "1.0.3"
exit-future = "0.1.3" exit-future = "0.1.3"
futures = "0.1.25" futures = "0.1.25"
reqwest = "0.9"

View File

@ -1,4 +1,5 @@
use crate::error::Result; use crate::error::Result;
use crate::local_bootstrap::BootstrapParams;
use crate::{config::GenesisState, ClientConfig}; use crate::{config::GenesisState, ClientConfig};
use beacon_chain::{ use beacon_chain::{
lmd_ghost::{LmdGhost, ThreadSafeReducedTree}, lmd_ghost::{LmdGhost, ThreadSafeReducedTree},
@ -6,6 +7,7 @@ use beacon_chain::{
store::Store, store::Store,
BeaconChain, BeaconChainTypes, BeaconChain, BeaconChainTypes,
}; };
use reqwest::Url;
use slog::{crit, info, Logger}; use slog::{crit, info, Logger};
use slot_clock::SlotClock; use slot_clock::SlotClock;
use std::fs::File; use std::fs::File;
@ -74,6 +76,15 @@ where
serde_yaml::from_reader(file) serde_yaml::from_reader(file)
.map_err(|e| format!("Unable to parse YAML genesis state file: {:?}", e))? .map_err(|e| format!("Unable to parse YAML genesis state file: {:?}", e))?
} }
GenesisState::HttpBootstrap { server } => {
let url: Url =
Url::parse(&server).map_err(|e| format!("Invalid bootstrap server url: {}", e))?;
let params = BootstrapParams::from_http_api(url)
.map_err(|e| format!("Failed to bootstrap from HTTP server: {:?}", e))?;
params.genesis_state
}
}; };
let mut genesis_block = BeaconBlock::empty(&spec); let mut genesis_block = BeaconBlock::empty(&spec);

View File

@ -48,6 +48,8 @@ pub enum GenesisState {
}, },
/// Load a YAML-encoded genesis state from a file. /// Load a YAML-encoded genesis state from a file.
Yaml { file: PathBuf }, Yaml { file: PathBuf },
/// Use a HTTP server (running our REST-API) to load genesis and finalized states and blocks.
HttpBootstrap { server: String },
} }
impl Default for Config { impl Default for Config {

View File

@ -2,6 +2,7 @@ extern crate slog;
mod beacon_chain_types; mod beacon_chain_types;
mod config; mod config;
mod local_bootstrap;
pub mod error; pub mod error;
pub mod notifier; pub mod notifier;

View File

@ -0,0 +1,93 @@
use reqwest::{Error as HttpError, Url};
use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Slot};
#[derive(Debug)]
pub enum Error {
UrlCannotBeBase,
HttpError(HttpError),
}
impl From<HttpError> for Error {
fn from(e: HttpError) -> Error {
Error::HttpError(e)
}
}
pub struct BootstrapParams<T: EthSpec> {
pub finalized_block: BeaconBlock<T>,
pub finalized_state: BeaconState<T>,
pub genesis_block: BeaconBlock<T>,
pub genesis_state: BeaconState<T>,
}
impl<T: EthSpec> BootstrapParams<T> {
pub fn from_http_api(url: Url) -> Result<Self, Error> {
let slots_per_epoch = get_slots_per_epoch(url.clone())?;
let genesis_slot = Slot::new(0);
let finalized_slot = get_finalized_slot(url.clone(), slots_per_epoch.as_u64())?;
Ok(Self {
finalized_block: get_block(url.clone(), finalized_slot)?,
finalized_state: get_state(url.clone(), finalized_slot)?,
genesis_block: get_block(url.clone(), genesis_slot)?,
genesis_state: get_state(url.clone(), genesis_slot)?,
})
}
}
fn get_slots_per_epoch(mut url: Url) -> Result<Slot, Error> {
url.path_segments_mut()
.map(|mut url| {
url.push("spec").push("slots_per_epoch");
})
.map_err(|_| Error::UrlCannotBeBase)?;
reqwest::get(url)?
.error_for_status()?
.json()
.map_err(Into::into)
}
fn get_finalized_slot(mut url: Url, slots_per_epoch: u64) -> Result<Slot, Error> {
url.path_segments_mut()
.map(|mut url| {
url.push("beacon").push("latest_finalized_checkpoint");
})
.map_err(|_| Error::UrlCannotBeBase)?;
let checkpoint: Checkpoint = reqwest::get(url)?.error_for_status()?.json()?;
Ok(checkpoint.epoch.start_slot(slots_per_epoch))
}
fn get_state<T: EthSpec>(mut url: Url, slot: Slot) -> Result<BeaconState<T>, Error> {
url.path_segments_mut()
.map(|mut url| {
url.push("beacon").push("state");
})
.map_err(|_| Error::UrlCannotBeBase)?;
url.query_pairs_mut()
.append_pair("slot", &format!("{}", slot.as_u64()));
reqwest::get(url)?
.error_for_status()?
.json()
.map_err(Into::into)
}
fn get_block<T: EthSpec>(mut url: Url, slot: Slot) -> Result<BeaconBlock<T>, Error> {
url.path_segments_mut()
.map(|mut url| {
url.push("beacon").push("block");
})
.map_err(|_| Error::UrlCannotBeBase)?;
url.query_pairs_mut()
.append_pair("slot", &format!("{}", slot.as_u64()));
reqwest::get(url)?
.error_for_status()?
.json()
.map_err(Into::into)
}

View File

@ -58,3 +58,24 @@ pub fn get_state_root<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiR
Ok(success_response(Body::from(json))) Ok(success_response(Body::from(json)))
} }
/// HTTP handler to return the highest finalized slot.
pub fn get_latest_finalized_checkpoint<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 checkpoint = beacon_chain
.head()
.beacon_state
.finalized_checkpoint
.clone();
let json: String = serde_json::to_string(&checkpoint)
.map_err(|e| ApiError::ServerError(format!("Unable to serialize checkpoint: {:?}", e)))?;
Ok(success_response(Body::from(json)))
}

View File

@ -4,6 +4,7 @@ mod beacon;
mod config; mod config;
mod helpers; mod helpers;
mod node; mod node;
mod spec;
mod url_query; mod url_query;
use beacon_chain::{BeaconChain, BeaconChainTypes}; use beacon_chain::{BeaconChain, BeaconChainTypes};
@ -101,10 +102,15 @@ pub fn start_server<T: BeaconChainTypes + Clone + 'static>(
// Route the request to the correct handler. // Route the request to the correct handler.
let result = match (req.method(), path.as_ref()) { let result = match (req.method(), path.as_ref()) {
(&Method::GET, "/beacon/latest_finalized_checkpoint") => {
beacon::get_latest_finalized_checkpoint::<T>(req)
}
(&Method::GET, "/beacon/state") => beacon::get_state::<T>(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_root") => beacon::get_state_root::<T>(req),
(&Method::GET, "/node/version") => node::get_version(req), (&Method::GET, "/node/version") => node::get_version(req),
(&Method::GET, "/node/genesis_time") => node::get_genesis_time::<T>(req), (&Method::GET, "/node/genesis_time") => node::get_genesis_time::<T>(req),
(&Method::GET, "/spec") => spec::get_spec::<T>(req),
(&Method::GET, "/spec/slots_per_epoch") => spec::get_slots_per_epoch::<T>(req),
_ => Err(ApiError::MethodNotAllowed(path.clone())), _ => Err(ApiError::MethodNotAllowed(path.clone())),
}; };

View File

@ -0,0 +1,27 @@
use super::{success_response, ApiResult};
use crate::ApiError;
use beacon_chain::{BeaconChain, BeaconChainTypes};
use hyper::{Body, Request};
use std::sync::Arc;
use types::EthSpec;
/// HTTP handler to return the full spec object.
pub fn get_spec<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 json: String = serde_json::to_string(&beacon_chain.spec)
.map_err(|e| ApiError::ServerError(format!("Unable to serialize spec: {:?}", e)))?;
Ok(success_response(Body::from(json)))
}
/// HTTP handler to return the full spec object.
pub fn get_slots_per_epoch<T: BeaconChainTypes + 'static>(_req: Request<Body>) -> ApiResult {
let json: String = serde_json::to_string(&T::EthSpec::slots_per_epoch())
.map_err(|e| ApiError::ServerError(format!("Unable to serialize epoch: {:?}", e)))?;
Ok(success_response(Body::from(json)))
}