mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-06 18:21:45 +00:00
Refactor beacon chain start code
This commit is contained in:
@@ -27,5 +27,3 @@ clap = "2.32.0"
|
||||
dirs = "1.0.3"
|
||||
exit-future = "0.1.3"
|
||||
futures = "0.1.25"
|
||||
reqwest = "0.9"
|
||||
url = "1.2"
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
use crate::bootstrapper::Bootstrapper;
|
||||
use crate::error::Result;
|
||||
use crate::{config::BeaconChainStartMethod, ClientConfig};
|
||||
use beacon_chain::{
|
||||
lmd_ghost::{LmdGhost, ThreadSafeReducedTree},
|
||||
slot_clock::SystemTimeSlotClock,
|
||||
store::Store,
|
||||
BeaconChain, BeaconChainTypes,
|
||||
};
|
||||
use slog::{crit, info, Logger};
|
||||
use slot_clock::SlotClock;
|
||||
use std::fs::File;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
use tree_hash::TreeHash;
|
||||
use types::{
|
||||
test_utils::TestingBeaconStateBuilder, BeaconBlock, BeaconState, ChainSpec, EthSpec, Hash256,
|
||||
};
|
||||
|
||||
/// Provides a new, initialized `BeaconChain`
|
||||
pub trait InitialiseBeaconChain<T: BeaconChainTypes> {
|
||||
fn initialise_beacon_chain(
|
||||
store: Arc<T::Store>,
|
||||
config: &ClientConfig,
|
||||
spec: ChainSpec,
|
||||
log: Logger,
|
||||
) -> Result<BeaconChain<T>> {
|
||||
maybe_load_from_store_for_testnet::<_, T::Store, T::EthSpec>(store, config, spec, log)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientType<S: Store, E: EthSpec> {
|
||||
_phantom_t: PhantomData<S>,
|
||||
_phantom_u: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<S, E> BeaconChainTypes for ClientType<S, E>
|
||||
where
|
||||
S: Store + 'static,
|
||||
E: EthSpec,
|
||||
{
|
||||
type Store = S;
|
||||
type SlotClock = SystemTimeSlotClock;
|
||||
type LmdGhost = ThreadSafeReducedTree<S, E>;
|
||||
type EthSpec = E;
|
||||
}
|
||||
impl<T: Store, E: EthSpec, X: BeaconChainTypes> InitialiseBeaconChain<X> for ClientType<T, E> {}
|
||||
|
||||
/// Loads a `BeaconChain` from `store`, if it exists. Otherwise, create a new chain from genesis.
|
||||
fn maybe_load_from_store_for_testnet<T, U: Store, V: EthSpec>(
|
||||
store: Arc<U>,
|
||||
config: &ClientConfig,
|
||||
spec: ChainSpec,
|
||||
log: Logger,
|
||||
) -> Result<BeaconChain<T>>
|
||||
where
|
||||
T: BeaconChainTypes<Store = U, EthSpec = V>,
|
||||
T::LmdGhost: LmdGhost<U, V>,
|
||||
{
|
||||
let genesis_state = match &config.beacon_chain_start_method {
|
||||
BeaconChainStartMethod::Resume => unimplemented!("No resume code yet"),
|
||||
BeaconChainStartMethod::Mainnet => {
|
||||
crit!(log, "No mainnet beacon chain startup specification.");
|
||||
return Err("Mainnet is not yet specified. We're working on it.".into());
|
||||
}
|
||||
BeaconChainStartMethod::RecentGenesis { validator_count } => {
|
||||
generate_testnet_genesis_state(*validator_count, recent_genesis_time(), &spec)
|
||||
}
|
||||
BeaconChainStartMethod::Generated {
|
||||
validator_count,
|
||||
genesis_time,
|
||||
} => generate_testnet_genesis_state(*validator_count, *genesis_time, &spec),
|
||||
BeaconChainStartMethod::Yaml { file } => {
|
||||
let file = File::open(file).map_err(|e| {
|
||||
format!("Unable to open YAML genesis state file {:?}: {:?}", file, e)
|
||||
})?;
|
||||
|
||||
serde_yaml::from_reader(file)
|
||||
.map_err(|e| format!("Unable to parse YAML genesis state file: {:?}", e))?
|
||||
}
|
||||
BeaconChainStartMethod::HttpBootstrap { server, .. } => {
|
||||
let bootstrapper = Bootstrapper::from_server_string(server.to_string())
|
||||
.map_err(|e| format!("Failed to initialize bootstrap client: {}", e))?;
|
||||
|
||||
let (state, _block) = bootstrapper
|
||||
.genesis()
|
||||
.map_err(|e| format!("Failed to bootstrap genesis state: {}", e))?;
|
||||
|
||||
state
|
||||
}
|
||||
};
|
||||
|
||||
let mut genesis_block = BeaconBlock::empty(&spec);
|
||||
genesis_block.state_root = Hash256::from_slice(&genesis_state.tree_hash_root());
|
||||
let genesis_block_root = genesis_block.canonical_root();
|
||||
|
||||
// Slot clock
|
||||
let slot_clock = T::SlotClock::new(
|
||||
spec.genesis_slot,
|
||||
genesis_state.genesis_time,
|
||||
spec.seconds_per_slot,
|
||||
);
|
||||
|
||||
// Try load an existing `BeaconChain` from the store. If unable, create a new one.
|
||||
if let Ok(Some(beacon_chain)) =
|
||||
BeaconChain::from_store(store.clone(), spec.clone(), log.clone())
|
||||
{
|
||||
// Here we check to ensure that the `BeaconChain` loaded from store has the expected
|
||||
// genesis block.
|
||||
//
|
||||
// Without this check, it's possible that there will be an existing DB with a `BeaconChain`
|
||||
// that has different parameters than provided to this executable.
|
||||
if beacon_chain.genesis_block_root == genesis_block_root {
|
||||
info!(
|
||||
log,
|
||||
"Loaded BeaconChain from store";
|
||||
"slot" => beacon_chain.head().beacon_state.slot,
|
||||
"best_slot" => beacon_chain.best_slot(),
|
||||
);
|
||||
|
||||
Ok(beacon_chain)
|
||||
} else {
|
||||
crit!(
|
||||
log,
|
||||
"The BeaconChain loaded from disk has an incorrect genesis root. \
|
||||
This may be caused by an old database in located in datadir."
|
||||
);
|
||||
Err("Incorrect genesis root".into())
|
||||
}
|
||||
} else {
|
||||
BeaconChain::from_genesis(
|
||||
store,
|
||||
slot_clock,
|
||||
genesis_state,
|
||||
genesis_block,
|
||||
spec,
|
||||
log.clone(),
|
||||
)
|
||||
.map_err(|e| format!("Failed to initialize new beacon chain: {:?}", e).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_testnet_genesis_state<E: EthSpec>(
|
||||
validator_count: usize,
|
||||
genesis_time: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> BeaconState<E> {
|
||||
let (mut genesis_state, _keypairs) =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, spec)
|
||||
.build();
|
||||
|
||||
genesis_state.genesis_time = genesis_time;
|
||||
|
||||
genesis_state
|
||||
}
|
||||
|
||||
/// Returns the system time, mod 30 minutes.
|
||||
///
|
||||
/// Used for easily creating testnets.
|
||||
fn recent_genesis_time() -> u64 {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
let secs_after_last_period = now.checked_rem(30 * 60).unwrap_or(0);
|
||||
// genesis is now the last 30 minute block.
|
||||
now - secs_after_last_period
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
use eth2_libp2p::{
|
||||
multiaddr::{Multiaddr, Protocol},
|
||||
Enr,
|
||||
};
|
||||
use reqwest::{Error as HttpError, Url};
|
||||
use serde::Deserialize;
|
||||
use std::borrow::Cow;
|
||||
use std::net::Ipv4Addr;
|
||||
use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Hash256, Slot};
|
||||
use url::Host;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
InvalidUrl,
|
||||
HttpError(HttpError),
|
||||
}
|
||||
|
||||
impl From<HttpError> for Error {
|
||||
fn from(e: HttpError) -> Error {
|
||||
Error::HttpError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to load "bootstrap" information from the HTTP API of another Lighthouse beacon node.
|
||||
///
|
||||
/// Bootstrapping information includes things like genesis and finalized states and blocks, and
|
||||
/// libp2p connection details.
|
||||
pub struct Bootstrapper {
|
||||
url: Url,
|
||||
}
|
||||
|
||||
impl Bootstrapper {
|
||||
/// Parses the given `server` as a URL, instantiating `Self`.
|
||||
pub fn from_server_string(server: String) -> Result<Self, String> {
|
||||
Ok(Self {
|
||||
url: Url::parse(&server).map_err(|e| format!("Invalid bootstrap server url: {}", e))?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Build a multiaddr using the HTTP server URL that is not guaranteed to be correct.
|
||||
///
|
||||
/// The address is created by querying the HTTP server for its listening libp2p addresses.
|
||||
/// Then, we find the first TCP port in those addresses and combine the port with the URL of
|
||||
/// the server.
|
||||
///
|
||||
/// For example, the server `http://192.168.0.1` might end up with a `best_effort_multiaddr` of
|
||||
/// `/ipv4/192.168.0.1/tcp/9000` if the server advertises a listening address of
|
||||
/// `/ipv4/172.0.0.1/tcp/9000`.
|
||||
pub fn best_effort_multiaddr(&self, port: Option<u16>) -> Option<Multiaddr> {
|
||||
let tcp_port = if let Some(port) = port {
|
||||
port
|
||||
} else {
|
||||
self.listen_port().ok()?
|
||||
};
|
||||
|
||||
let mut multiaddr = Multiaddr::with_capacity(2);
|
||||
|
||||
match self.url.host()? {
|
||||
Host::Ipv4(addr) => multiaddr.push(Protocol::Ip4(addr)),
|
||||
Host::Domain(s) => multiaddr.push(Protocol::Dns4(Cow::Borrowed(s))),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
multiaddr.push(Protocol::Tcp(tcp_port));
|
||||
|
||||
Some(multiaddr)
|
||||
}
|
||||
|
||||
/// Returns the IPv4 address of the server URL, unless it contains a FQDN.
|
||||
pub fn server_ipv4_addr(&self) -> Option<Ipv4Addr> {
|
||||
match self.url.host()? {
|
||||
Host::Ipv4(addr) => Some(addr),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the servers ENR address.
|
||||
pub fn enr(&self) -> Result<Enr, String> {
|
||||
get_enr(self.url.clone()).map_err(|e| format!("Unable to get ENR: {:?}", e))
|
||||
}
|
||||
|
||||
/// Returns the servers listening libp2p addresses.
|
||||
pub fn listen_port(&self) -> Result<u16, String> {
|
||||
get_listen_port(self.url.clone()).map_err(|e| format!("Unable to get listen port: {:?}", e))
|
||||
}
|
||||
|
||||
/// Returns the genesis block and state.
|
||||
pub fn genesis<T: EthSpec>(&self) -> Result<(BeaconState<T>, BeaconBlock<T>), String> {
|
||||
let genesis_slot = Slot::new(0);
|
||||
|
||||
let block = get_block(self.url.clone(), genesis_slot)
|
||||
.map_err(|e| format!("Unable to get genesis block: {:?}", e))?
|
||||
.beacon_block;
|
||||
let state = get_state(self.url.clone(), genesis_slot)
|
||||
.map_err(|e| format!("Unable to get genesis state: {:?}", e))?
|
||||
.beacon_state;
|
||||
|
||||
Ok((state, block))
|
||||
}
|
||||
|
||||
/// Returns the most recent finalized state and block.
|
||||
pub fn finalized<T: EthSpec>(&self) -> Result<(BeaconState<T>, BeaconBlock<T>), String> {
|
||||
let slots_per_epoch = get_slots_per_epoch(self.url.clone())
|
||||
.map_err(|e| format!("Unable to get slots per epoch: {:?}", e))?;
|
||||
let finalized_slot = get_finalized_slot(self.url.clone(), slots_per_epoch.as_u64())
|
||||
.map_err(|e| format!("Unable to get finalized slot: {:?}", e))?;
|
||||
|
||||
let block = get_block(self.url.clone(), finalized_slot)
|
||||
.map_err(|e| format!("Unable to get finalized block: {:?}", e))?
|
||||
.beacon_block;
|
||||
let state = get_state(self.url.clone(), finalized_slot)
|
||||
.map_err(|e| format!("Unable to get finalized state: {:?}", e))?
|
||||
.beacon_state;
|
||||
|
||||
Ok((state, block))
|
||||
}
|
||||
}
|
||||
|
||||
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::InvalidUrl)?;
|
||||
|
||||
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::InvalidUrl)?;
|
||||
|
||||
let checkpoint: Checkpoint = reqwest::get(url)?.error_for_status()?.json()?;
|
||||
|
||||
Ok(checkpoint.epoch.start_slot(slots_per_epoch))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct StateResponse<T: EthSpec> {
|
||||
pub root: Hash256,
|
||||
pub beacon_state: BeaconState<T>,
|
||||
}
|
||||
|
||||
fn get_state<T: EthSpec>(mut url: Url, slot: Slot) -> Result<StateResponse<T>, Error> {
|
||||
url.path_segments_mut()
|
||||
.map(|mut url| {
|
||||
url.push("beacon").push("state");
|
||||
})
|
||||
.map_err(|_| Error::InvalidUrl)?;
|
||||
|
||||
url.query_pairs_mut()
|
||||
.append_pair("slot", &format!("{}", slot.as_u64()));
|
||||
|
||||
reqwest::get(url)?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(bound = "T: EthSpec")]
|
||||
pub struct BlockResponse<T: EthSpec> {
|
||||
pub root: Hash256,
|
||||
pub beacon_block: BeaconBlock<T>,
|
||||
}
|
||||
|
||||
fn get_block<T: EthSpec>(mut url: Url, slot: Slot) -> Result<BlockResponse<T>, Error> {
|
||||
url.path_segments_mut()
|
||||
.map(|mut url| {
|
||||
url.push("beacon").push("block");
|
||||
})
|
||||
.map_err(|_| Error::InvalidUrl)?;
|
||||
|
||||
url.query_pairs_mut()
|
||||
.append_pair("slot", &format!("{}", slot.as_u64()));
|
||||
|
||||
reqwest::get(url)?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn get_enr(mut url: Url) -> Result<Enr, Error> {
|
||||
url.path_segments_mut()
|
||||
.map(|mut url| {
|
||||
url.push("network").push("enr");
|
||||
})
|
||||
.map_err(|_| Error::InvalidUrl)?;
|
||||
|
||||
reqwest::get(url)?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn get_listen_port(mut url: Url) -> Result<u16, Error> {
|
||||
url.path_segments_mut()
|
||||
.map(|mut url| {
|
||||
url.push("network").push("listen_port");
|
||||
})
|
||||
.map_err(|_| Error::InvalidUrl)?;
|
||||
|
||||
reqwest::get(url)?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
@@ -1,31 +1,47 @@
|
||||
extern crate slog;
|
||||
|
||||
mod beacon_chain_types;
|
||||
mod bootstrapper;
|
||||
mod config;
|
||||
|
||||
pub mod error;
|
||||
pub mod notifier;
|
||||
|
||||
use beacon_chain::BeaconChain;
|
||||
use beacon_chain::{
|
||||
lmd_ghost::ThreadSafeReducedTree, slot_clock::SystemTimeSlotClock, store::Store, BeaconChain,
|
||||
BeaconChainBuilder,
|
||||
};
|
||||
use exit_future::Signal;
|
||||
use futures::{future::Future, Stream};
|
||||
use network::Service as NetworkService;
|
||||
use slog::{error, info, o};
|
||||
use slog::{crit, error, info, o};
|
||||
use slot_clock::SlotClock;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::runtime::TaskExecutor;
|
||||
use tokio::timer::Interval;
|
||||
use types::EthSpec;
|
||||
|
||||
pub use beacon_chain::BeaconChainTypes;
|
||||
pub use beacon_chain_types::ClientType;
|
||||
pub use beacon_chain_types::InitialiseBeaconChain;
|
||||
pub use bootstrapper::Bootstrapper;
|
||||
pub use config::{BeaconChainStartMethod, Config as ClientConfig};
|
||||
pub use eth2_config::Eth2Config;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientType<S: Store, E: EthSpec> {
|
||||
_phantom_t: PhantomData<S>,
|
||||
_phantom_u: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<S, E> BeaconChainTypes for ClientType<S, E>
|
||||
where
|
||||
S: Store + 'static,
|
||||
E: EthSpec,
|
||||
{
|
||||
type Store = S;
|
||||
type SlotClock = SystemTimeSlotClock;
|
||||
type LmdGhost = ThreadSafeReducedTree<S, E>;
|
||||
type EthSpec = E;
|
||||
}
|
||||
|
||||
/// Main beacon node client service. This provides the connection and initialisation of the clients
|
||||
/// sub-services in multiple threads.
|
||||
pub struct Client<T: BeaconChainTypes> {
|
||||
@@ -49,7 +65,7 @@ pub struct Client<T: BeaconChainTypes> {
|
||||
|
||||
impl<T> Client<T>
|
||||
where
|
||||
T: BeaconChainTypes + InitialiseBeaconChain<T> + Clone,
|
||||
T: BeaconChainTypes + Clone,
|
||||
{
|
||||
/// Generate an instance of the client. Spawn and link all internal sub-processes.
|
||||
pub fn new(
|
||||
@@ -62,13 +78,41 @@ where
|
||||
let store = Arc::new(store);
|
||||
let seconds_per_slot = eth2_config.spec.seconds_per_slot;
|
||||
|
||||
// Load a `BeaconChain` from the store, or create a new one if it does not exist.
|
||||
let beacon_chain = Arc::new(T::initialise_beacon_chain(
|
||||
store,
|
||||
&client_config,
|
||||
eth2_config.spec.clone(),
|
||||
log.clone(),
|
||||
)?);
|
||||
let spec = ð2_config.spec.clone();
|
||||
|
||||
let beacon_chain_builder = match &client_config.beacon_chain_start_method {
|
||||
BeaconChainStartMethod::Resume => {
|
||||
BeaconChainBuilder::from_store(spec.clone(), log.clone())
|
||||
}
|
||||
BeaconChainStartMethod::Mainnet => {
|
||||
crit!(log, "No mainnet beacon chain startup specification.");
|
||||
return Err("Mainnet is not yet specified. We're working on it.".into());
|
||||
}
|
||||
BeaconChainStartMethod::RecentGenesis { validator_count } => {
|
||||
BeaconChainBuilder::recent_genesis(*validator_count, spec.clone(), log.clone())
|
||||
}
|
||||
BeaconChainStartMethod::Generated {
|
||||
validator_count,
|
||||
genesis_time,
|
||||
} => BeaconChainBuilder::quick_start(
|
||||
*genesis_time,
|
||||
*validator_count,
|
||||
spec.clone(),
|
||||
log.clone(),
|
||||
),
|
||||
BeaconChainStartMethod::Yaml { file } => {
|
||||
BeaconChainBuilder::yaml_state(file, spec.clone(), log.clone())?
|
||||
}
|
||||
BeaconChainStartMethod::HttpBootstrap { server, .. } => {
|
||||
BeaconChainBuilder::http_bootstrap(server, spec.clone(), log.clone())?
|
||||
}
|
||||
};
|
||||
|
||||
let beacon_chain: Arc<BeaconChain<T>> = Arc::new(
|
||||
beacon_chain_builder
|
||||
.build(store)
|
||||
.map_err(error::Error::from)?,
|
||||
);
|
||||
|
||||
if beacon_chain.read_slot_clock().is_none() {
|
||||
panic!("Cannot start client before genesis!")
|
||||
|
||||
Reference in New Issue
Block a user