From 4f98a3985fc1714799ac5897d002fa26ea74bb96 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 14 Aug 2019 10:36:55 +1000 Subject: [PATCH] Add first attempts at HTTP bootstrap --- beacon_node/client/Cargo.toml | 1 + beacon_node/client/src/beacon_chain_types.rs | 11 +++ beacon_node/client/src/config.rs | 2 + beacon_node/client/src/lib.rs | 1 + beacon_node/client/src/local_bootstrap.rs | 93 ++++++++++++++++++++ beacon_node/rest_api/src/beacon.rs | 21 +++++ beacon_node/rest_api/src/lib.rs | 6 ++ beacon_node/rest_api/src/spec.rs | 27 ++++++ 8 files changed, 162 insertions(+) create mode 100644 beacon_node/client/src/local_bootstrap.rs create mode 100644 beacon_node/rest_api/src/spec.rs diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 8c72fa4171..b0524b17d2 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -27,3 +27,4 @@ clap = "2.32.0" dirs = "1.0.3" exit-future = "0.1.3" futures = "0.1.25" +reqwest = "0.9" diff --git a/beacon_node/client/src/beacon_chain_types.rs b/beacon_node/client/src/beacon_chain_types.rs index 0b86c95838..a5b89b86a2 100644 --- a/beacon_node/client/src/beacon_chain_types.rs +++ b/beacon_node/client/src/beacon_chain_types.rs @@ -1,4 +1,5 @@ use crate::error::Result; +use crate::local_bootstrap::BootstrapParams; use crate::{config::GenesisState, ClientConfig}; use beacon_chain::{ lmd_ghost::{LmdGhost, ThreadSafeReducedTree}, @@ -6,6 +7,7 @@ use beacon_chain::{ store::Store, BeaconChain, BeaconChainTypes, }; +use reqwest::Url; use slog::{crit, info, Logger}; use slot_clock::SlotClock; use std::fs::File; @@ -74,6 +76,15 @@ where serde_yaml::from_reader(file) .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); diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index ee62b62815..2b410312b1 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -48,6 +48,8 @@ pub enum GenesisState { }, /// Load a YAML-encoded genesis state from a file. 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 { diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 65ba071fa1..7a9152ee0e 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -2,6 +2,7 @@ extern crate slog; mod beacon_chain_types; mod config; +mod local_bootstrap; pub mod error; pub mod notifier; diff --git a/beacon_node/client/src/local_bootstrap.rs b/beacon_node/client/src/local_bootstrap.rs new file mode 100644 index 0000000000..f38762b3be --- /dev/null +++ b/beacon_node/client/src/local_bootstrap.rs @@ -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 for Error { + fn from(e: HttpError) -> Error { + Error::HttpError(e) + } +} + +pub struct BootstrapParams { + pub finalized_block: BeaconBlock, + pub finalized_state: BeaconState, + pub genesis_block: BeaconBlock, + pub genesis_state: BeaconState, +} + +impl BootstrapParams { + pub fn from_http_api(url: Url) -> Result { + 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 { + 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 { + 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(mut url: Url, slot: Slot) -> Result, 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(mut url: Url, slot: Slot) -> Result, 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) +} diff --git a/beacon_node/rest_api/src/beacon.rs b/beacon_node/rest_api/src/beacon.rs index cef23abe81..8b089f542b 100644 --- a/beacon_node/rest_api/src/beacon.rs +++ b/beacon_node/rest_api/src/beacon.rs @@ -58,3 +58,24 @@ pub fn get_state_root(req: Request) -> ApiR Ok(success_response(Body::from(json))) } + +/// HTTP handler to return the highest finalized slot. +pub fn get_latest_finalized_checkpoint( + req: Request, +) -> ApiResult { + let beacon_chain = req + .extensions() + .get::>>() + .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))) +} diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index a94a8cdf4a..57c5482cd8 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -4,6 +4,7 @@ mod beacon; mod config; mod helpers; mod node; +mod spec; mod url_query; use beacon_chain::{BeaconChain, BeaconChainTypes}; @@ -101,10 +102,15 @@ pub fn start_server( // Route the request to the correct handler. let result = match (req.method(), path.as_ref()) { + (&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, "/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())), }; diff --git a/beacon_node/rest_api/src/spec.rs b/beacon_node/rest_api/src/spec.rs new file mode 100644 index 0000000000..d0c8e4368d --- /dev/null +++ b/beacon_node/rest_api/src/spec.rs @@ -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(req: Request) -> ApiResult { + let beacon_chain = req + .extensions() + .get::>>() + .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(_req: Request) -> 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))) +}