Merge latest interop

This commit is contained in:
Age Manning 2019-09-05 03:03:45 +10:00
commit 2a1d6587a7
No known key found for this signature in database
GPG Key ID: 05EED64B79E06A93
37 changed files with 1019 additions and 473 deletions

View File

@ -18,6 +18,7 @@ rayon = "1.0"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_yaml = "0.8" serde_yaml = "0.8"
serde_json = "^1.0"
slog = { version = "^2.2.3" , features = ["max_level_trace"] } slog = { version = "^2.2.3" , features = ["max_level_trace"] }
sloggers = { version = "^0.3" } sloggers = { version = "^0.3" }
slot_clock = { path = "../../eth2/utils/slot_clock" } slot_clock = { path = "../../eth2/utils/slot_clock" }

View File

@ -1,5 +1,6 @@
use crate::checkpoint::CheckPoint; use crate::checkpoint::CheckPoint;
use crate::errors::{BeaconChainError as Error, BlockProductionError}; use crate::errors::{BeaconChainError as Error, BlockProductionError};
use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend};
use crate::fork_choice::{Error as ForkChoiceError, ForkChoice}; use crate::fork_choice::{Error as ForkChoiceError, ForkChoice};
use crate::iter::{ReverseBlockRootIterator, ReverseStateRootIterator}; use crate::iter::{ReverseBlockRootIterator, ReverseStateRootIterator};
use crate::metrics; use crate::metrics;
@ -45,11 +46,14 @@ pub enum BlockProcessingOutcome {
block_slot: Slot, block_slot: Slot,
}, },
/// The block state_root does not match the generated state. /// The block state_root does not match the generated state.
StateRootMismatch, StateRootMismatch { block: Hash256, local: Hash256 },
/// The block was a genesis block, these blocks cannot be re-imported. /// The block was a genesis block, these blocks cannot be re-imported.
GenesisBlock, GenesisBlock,
/// The slot is finalized, no need to import. /// The slot is finalized, no need to import.
FinalizedSlot, WouldRevertFinalizedSlot {
block_slot: Slot,
finalized_slot: Slot,
},
/// Block is already known, no need to re-import. /// Block is already known, no need to re-import.
BlockIsAlreadyKnown, BlockIsAlreadyKnown,
/// The block could not be applied to the state, it is invalid. /// The block could not be applied to the state, it is invalid.
@ -76,25 +80,31 @@ pub enum AttestationProcessingOutcome {
Invalid(AttestationValidationError), Invalid(AttestationValidationError),
} }
pub enum StateCow<'a, T: EthSpec> { /// Effectively a `Cow<BeaconState>`, however when it is `Borrowed` it holds a `RwLockReadGuard` (a
/// read-lock on some read/write-locked state).
///
/// Only has a small subset of the functionality of a `std::borrow::Cow`.
pub enum BeaconStateCow<'a, T: EthSpec> {
Borrowed(RwLockReadGuard<'a, CheckPoint<T>>), Borrowed(RwLockReadGuard<'a, CheckPoint<T>>),
Owned(BeaconState<T>), Owned(BeaconState<T>),
} }
impl<'a, T: EthSpec> AsRef<BeaconState<T>> for StateCow<'a, T> { impl<'a, T: EthSpec> BeaconStateCow<'a, T> {
fn as_ref(&self) -> &BeaconState<T> { pub fn maybe_as_mut_ref(&mut self) -> Option<&mut BeaconState<T>> {
match self { match self {
StateCow::Borrowed(checkpoint) => &checkpoint.beacon_state, BeaconStateCow::Borrowed(_) => None,
StateCow::Owned(state) => &state, BeaconStateCow::Owned(ref mut state) => Some(state),
} }
} }
} }
impl<'a, T: EthSpec> StateCow<'a, T> { impl<'a, T: EthSpec> std::ops::Deref for BeaconStateCow<'a, T> {
pub fn as_mut_ref(&mut self) -> Option<&mut BeaconState<T>> { type Target = BeaconState<T>;
fn deref(&self) -> &BeaconState<T> {
match self { match self {
StateCow::Borrowed(_) => None, BeaconStateCow::Borrowed(checkpoint) => &checkpoint.beacon_state,
StateCow::Owned(ref mut state) => Some(state), BeaconStateCow::Owned(state) => &state,
} }
} }
} }
@ -103,6 +113,7 @@ pub trait BeaconChainTypes: Send + Sync + 'static {
type Store: store::Store; type Store: store::Store;
type SlotClock: slot_clock::SlotClock; type SlotClock: slot_clock::SlotClock;
type LmdGhost: LmdGhost<Self::Store, Self::EthSpec>; type LmdGhost: LmdGhost<Self::Store, Self::EthSpec>;
type Eth1Chain: Eth1ChainBackend<Self::EthSpec>;
type EthSpec: types::EthSpec; type EthSpec: types::EthSpec;
} }
@ -117,6 +128,8 @@ pub struct BeaconChain<T: BeaconChainTypes> {
/// Stores all operations (e.g., `Attestation`, `Deposit`, etc) that are candidates for /// Stores all operations (e.g., `Attestation`, `Deposit`, etc) that are candidates for
/// inclusion in a block. /// inclusion in a block.
pub op_pool: OperationPool<T::EthSpec>, pub op_pool: OperationPool<T::EthSpec>,
/// Provides information from the Ethereum 1 (PoW) chain.
pub eth1_chain: Eth1Chain<T>,
/// Stores a "snapshot" of the chain at the time the head-of-the-chain block was received. /// Stores a "snapshot" of the chain at the time the head-of-the-chain block was received.
canonical_head: RwLock<CheckPoint<T::EthSpec>>, canonical_head: RwLock<CheckPoint<T::EthSpec>>,
/// The root of the genesis block. /// The root of the genesis block.
@ -132,6 +145,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Instantiate a new Beacon Chain, from genesis. /// Instantiate a new Beacon Chain, from genesis.
pub fn from_genesis( pub fn from_genesis(
store: Arc<T::Store>, store: Arc<T::Store>,
eth1_backend: T::Eth1Chain,
mut genesis_state: BeaconState<T::EthSpec>, mut genesis_state: BeaconState<T::EthSpec>,
mut genesis_block: BeaconBlock<T::EthSpec>, mut genesis_block: BeaconBlock<T::EthSpec>,
spec: ChainSpec, spec: ChainSpec,
@ -176,6 +190,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
spec, spec,
slot_clock, slot_clock,
op_pool: OperationPool::new(), op_pool: OperationPool::new(),
eth1_chain: Eth1Chain::new(eth1_backend),
canonical_head, canonical_head,
genesis_block_root, genesis_block_root,
fork_choice: ForkChoice::new(store.clone(), &genesis_block, genesis_block_root), fork_choice: ForkChoice::new(store.clone(), &genesis_block, genesis_block_root),
@ -187,6 +202,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Attempt to load an existing instance from the given `store`. /// Attempt to load an existing instance from the given `store`.
pub fn from_store( pub fn from_store(
store: Arc<T::Store>, store: Arc<T::Store>,
eth1_backend: T::Eth1Chain,
spec: ChainSpec, spec: ChainSpec,
log: Logger, log: Logger,
) -> Result<Option<BeaconChain<T>>, Error> { ) -> Result<Option<BeaconChain<T>>, Error> {
@ -223,6 +239,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
slot_clock, slot_clock,
fork_choice: ForkChoice::new(store.clone(), last_finalized_block, last_finalized_root), fork_choice: ForkChoice::new(store.clone(), last_finalized_block, last_finalized_root),
op_pool, op_pool,
eth1_chain: Eth1Chain::new(eth1_backend),
canonical_head: RwLock::new(p.canonical_head), canonical_head: RwLock::new(p.canonical_head),
genesis_block_root: p.genesis_block_root, genesis_block_root: p.genesis_block_root,
store, store,
@ -373,11 +390,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// ///
/// Returns `None` when the state is not found in the database or there is an error skipping /// Returns `None` when the state is not found in the database or there is an error skipping
/// to a future state. /// to a future state.
pub fn state_at_slot(&self, slot: Slot) -> Result<StateCow<T::EthSpec>, Error> { pub fn state_at_slot(&self, slot: Slot) -> Result<BeaconStateCow<T::EthSpec>, Error> {
let head_state = &self.head().beacon_state; let head_state = &self.head().beacon_state;
if slot == head_state.slot { if slot == head_state.slot {
Ok(StateCow::Borrowed(self.head())) Ok(BeaconStateCow::Borrowed(self.head()))
} else if slot > head_state.slot { } else if slot > head_state.slot {
let head_state_slot = head_state.slot; let head_state_slot = head_state.slot;
let mut state = head_state.clone(); let mut state = head_state.clone();
@ -397,7 +414,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
} }
}; };
} }
Ok(StateCow::Owned(state)) Ok(BeaconStateCow::Owned(state))
} else { } else {
let state_root = self let state_root = self
.rev_iter_state_roots() .rev_iter_state_roots()
@ -405,7 +422,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.map(|(root, _slot)| root) .map(|(root, _slot)| root)
.ok_or_else(|| Error::NoStateForSlot(slot))?; .ok_or_else(|| Error::NoStateForSlot(slot))?;
Ok(StateCow::Owned( Ok(BeaconStateCow::Owned(
self.store self.store
.get(&state_root)? .get(&state_root)?
.ok_or_else(|| Error::NoStateForSlot(slot))?, .ok_or_else(|| Error::NoStateForSlot(slot))?,
@ -421,7 +438,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// ///
/// Returns `None` when there is an error skipping to a future state or the slot clock cannot /// Returns `None` when there is an error skipping to a future state or the slot clock cannot
/// be read. /// be read.
pub fn state_now(&self) -> Result<StateCow<T::EthSpec>, Error> { pub fn state_now(&self) -> Result<BeaconStateCow<T::EthSpec>, Error> {
self.state_at_slot(self.slot()?) self.state_at_slot(self.slot()?)
} }
@ -473,25 +490,24 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let head_state = &self.head().beacon_state; let head_state = &self.head().beacon_state;
let mut state = if epoch(slot) == epoch(head_state.slot) { let mut state = if epoch(slot) == epoch(head_state.slot) {
StateCow::Borrowed(self.head()) BeaconStateCow::Borrowed(self.head())
} else { } else {
self.state_at_slot(slot)? self.state_at_slot(slot)?
}; };
if let Some(state) = state.as_mut_ref() { if let Some(state) = state.maybe_as_mut_ref() {
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?; state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
} }
if epoch(state.as_ref().slot) != epoch(slot) { if epoch(state.slot) != epoch(slot) {
return Err(Error::InvariantViolated(format!( return Err(Error::InvariantViolated(format!(
"Epochs in consistent in proposer lookup: state: {}, requested: {}", "Epochs in consistent in proposer lookup: state: {}, requested: {}",
epoch(state.as_ref().slot), epoch(state.slot),
epoch(slot) epoch(slot)
))); )));
} }
state state
.as_ref()
.get_beacon_proposer_index(slot, RelativeEpoch::Current, &self.spec) .get_beacon_proposer_index(slot, RelativeEpoch::Current, &self.spec)
.map_err(Into::into) .map_err(Into::into)
} }
@ -509,26 +525,25 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let head_state = &self.head().beacon_state; let head_state = &self.head().beacon_state;
let mut state = if epoch == as_epoch(head_state.slot) { let mut state = if epoch == as_epoch(head_state.slot) {
StateCow::Borrowed(self.head()) BeaconStateCow::Borrowed(self.head())
} else { } else {
self.state_at_slot(epoch.start_slot(T::EthSpec::slots_per_epoch()))? self.state_at_slot(epoch.start_slot(T::EthSpec::slots_per_epoch()))?
}; };
if let Some(state) = state.as_mut_ref() { if let Some(state) = state.maybe_as_mut_ref() {
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?; state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
} }
if as_epoch(state.as_ref().slot) != epoch { if as_epoch(state.slot) != epoch {
return Err(Error::InvariantViolated(format!( return Err(Error::InvariantViolated(format!(
"Epochs in consistent in attestation duties lookup: state: {}, requested: {}", "Epochs in consistent in attestation duties lookup: state: {}, requested: {}",
as_epoch(state.as_ref().slot), as_epoch(state.slot),
epoch epoch
))); )));
} }
if let Some(attestation_duty) = state if let Some(attestation_duty) =
.as_ref() state.get_attestation_duties(validator_index, RelativeEpoch::Current)?
.get_attestation_duties(validator_index, RelativeEpoch::Current)?
{ {
Ok(Some((attestation_duty.slot, attestation_duty.shard))) Ok(Some((attestation_duty.slot, attestation_duty.shard)))
} else { } else {
@ -549,12 +564,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let head_block_root = self.head().beacon_block_root; let head_block_root = self.head().beacon_block_root;
let head_block_slot = self.head().beacon_block.slot; let head_block_slot = self.head().beacon_block.slot;
self.produce_attestation_data_for_block( self.produce_attestation_data_for_block(shard, head_block_root, head_block_slot, &*state)
shard,
head_block_root,
head_block_slot,
state.as_ref(),
)
} }
/// Produce an `AttestationData` that attests to the chain denoted by `block_root` and `state`. /// Produce an `AttestationData` that attests to the chain denoted by `block_root` and `state`.
@ -876,7 +886,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
match self.state_now() { match self.state_now() {
Ok(state) => self Ok(state) => self
.op_pool .op_pool
.insert_voluntary_exit(exit, state.as_ref(), &self.spec), .insert_voluntary_exit(exit, &*state, &self.spec),
Err(e) => { Err(e) => {
error!( error!(
&self.log, &self.log,
@ -892,9 +902,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Accept some transfer and queue it for inclusion in an appropriate block. /// Accept some transfer and queue it for inclusion in an appropriate block.
pub fn process_transfer(&self, transfer: Transfer) -> Result<(), TransferValidationError> { pub fn process_transfer(&self, transfer: Transfer) -> Result<(), TransferValidationError> {
match self.state_now() { match self.state_now() {
Ok(state) => self Ok(state) => self.op_pool.insert_transfer(transfer, &*state, &self.spec),
.op_pool
.insert_transfer(transfer, state.as_ref(), &self.spec),
Err(e) => { Err(e) => {
error!( error!(
&self.log, &self.log,
@ -915,7 +923,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
match self.state_now() { match self.state_now() {
Ok(state) => { Ok(state) => {
self.op_pool self.op_pool
.insert_proposer_slashing(proposer_slashing, state.as_ref(), &self.spec) .insert_proposer_slashing(proposer_slashing, &*state, &self.spec)
} }
Err(e) => { Err(e) => {
error!( error!(
@ -937,7 +945,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
match self.state_now() { match self.state_now() {
Ok(state) => { Ok(state) => {
self.op_pool self.op_pool
.insert_attester_slashing(attester_slashing, state.as_ref(), &self.spec) .insert_attester_slashing(attester_slashing, &*state, &self.spec)
} }
Err(e) => { Err(e) => {
error!( error!(
@ -968,14 +976,17 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.epoch .epoch
.start_slot(T::EthSpec::slots_per_epoch()); .start_slot(T::EthSpec::slots_per_epoch());
if block.slot <= finalized_slot {
return Ok(BlockProcessingOutcome::FinalizedSlot);
}
if block.slot == 0 { if block.slot == 0 {
return Ok(BlockProcessingOutcome::GenesisBlock); return Ok(BlockProcessingOutcome::GenesisBlock);
} }
if block.slot <= finalized_slot {
return Ok(BlockProcessingOutcome::WouldRevertFinalizedSlot {
block_slot: block.slot,
finalized_slot: finalized_slot,
});
}
let block_root_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_BLOCK_ROOT); let block_root_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_BLOCK_ROOT);
let block_root = block.canonical_root(); let block_root = block.canonical_root();
@ -1073,7 +1084,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let state_root = state.canonical_root(); let state_root = state.canonical_root();
if block.state_root != state_root { if block.state_root != state_root {
return Ok(BlockProcessingOutcome::StateRootMismatch); return Ok(BlockProcessingOutcome::StateRootMismatch {
block: block.state_root,
local: state_root,
});
} }
metrics::stop_timer(state_root_timer); metrics::stop_timer(state_root_timer);
@ -1158,7 +1172,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.state_at_slot(slot - 1) .state_at_slot(slot - 1)
.map_err(|_| BlockProductionError::UnableToProduceAtSlot(slot))?; .map_err(|_| BlockProductionError::UnableToProduceAtSlot(slot))?;
self.produce_block_on_state(state.as_ref().clone(), slot, randao_reveal) self.produce_block_on_state(state.clone(), slot, randao_reveal)
} }
/// Produce a block for some `slot` upon the given `state`. /// Produce a block for some `slot` upon the given `state`.
@ -1207,16 +1221,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
body: BeaconBlockBody { body: BeaconBlockBody {
randao_reveal, randao_reveal,
// TODO: replace with real data. // TODO: replace with real data.
eth1_data: Eth1Data { eth1_data: self.eth1_chain.eth1_data_for_block_production(&state)?,
deposit_count: state.eth1_data.deposit_count,
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
},
graffiti, graffiti,
proposer_slashings: proposer_slashings.into(), proposer_slashings: proposer_slashings.into(),
attester_slashings: attester_slashings.into(), attester_slashings: attester_slashings.into(),
attestations: self.op_pool.get_attestations(&state, &self.spec).into(), attestations: self.op_pool.get_attestations(&state, &self.spec).into(),
deposits: self.op_pool.get_deposits(&state).into(), deposits: self.eth1_chain.deposits_for_block_inclusion(&state)?.into(),
voluntary_exits: self.op_pool.get_voluntary_exits(&state, &self.spec).into(), voluntary_exits: self.op_pool.get_voluntary_exits(&state, &self.spec).into(),
transfers: self.op_pool.get_transfers(&state, &self.spec).into(), transfers: self.op_pool.get_transfers(&state, &self.spec).into(),
}, },

View File

@ -4,16 +4,17 @@ use lighthouse_bootstrap::Bootstrapper;
use merkle_proof::MerkleTree; use merkle_proof::MerkleTree;
use rayon::prelude::*; use rayon::prelude::*;
use slog::Logger; use slog::Logger;
use ssz::Encode; use ssz::{Decode, Encode};
use state_processing::initialize_beacon_state_from_eth1; use state_processing::initialize_beacon_state_from_eth1;
use std::fs::File; use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
use tree_hash::{SignedRoot, TreeHash}; use tree_hash::{SignedRoot, TreeHash};
use types::{ use types::{
test_utils::generate_deterministic_keypairs, BeaconBlock, BeaconState, ChainSpec, Deposit, BeaconBlock, BeaconState, ChainSpec, Deposit, DepositData, Domain, EthSpec, Fork, Hash256,
DepositData, Domain, EthSpec, Fork, Hash256, PublicKey, Signature, Keypair, PublicKey, Signature,
}; };
enum BuildStrategy<T: BeaconChainTypes> { enum BuildStrategy<T: BeaconChainTypes> {
@ -32,21 +33,21 @@ pub struct BeaconChainBuilder<T: BeaconChainTypes> {
impl<T: BeaconChainTypes> BeaconChainBuilder<T> { impl<T: BeaconChainTypes> BeaconChainBuilder<T> {
pub fn recent_genesis( pub fn recent_genesis(
validator_count: usize, keypairs: &[Keypair],
minutes: u64, minutes: u64,
spec: ChainSpec, spec: ChainSpec,
log: Logger, log: Logger,
) -> Result<Self, String> { ) -> Result<Self, String> {
Self::quick_start(recent_genesis_time(minutes), validator_count, spec, log) Self::quick_start(recent_genesis_time(minutes), keypairs, spec, log)
} }
pub fn quick_start( pub fn quick_start(
genesis_time: u64, genesis_time: u64,
validator_count: usize, keypairs: &[Keypair],
spec: ChainSpec, spec: ChainSpec,
log: Logger, log: Logger,
) -> Result<Self, String> { ) -> Result<Self, String> {
let genesis_state = interop_genesis_state(validator_count, genesis_time, &spec)?; let genesis_state = interop_genesis_state(keypairs, genesis_time, &spec)?;
Ok(Self::from_genesis_state(genesis_state, spec, log)) Ok(Self::from_genesis_state(genesis_state, spec, log))
} }
@ -61,8 +62,32 @@ impl<T: BeaconChainTypes> BeaconChainBuilder<T> {
Ok(Self::from_genesis_state(genesis_state, spec, log)) Ok(Self::from_genesis_state(genesis_state, spec, log))
} }
pub fn ssz_state(file: &PathBuf, spec: ChainSpec, log: Logger) -> Result<Self, String> {
let mut file = File::open(file.clone())
.map_err(|e| format!("Unable to open SSZ genesis state file {:?}: {:?}", file, e))?;
let mut bytes = vec![];
file.read_to_end(&mut bytes)
.map_err(|e| format!("Failed to read SSZ file: {:?}", e))?;
let genesis_state = BeaconState::from_ssz_bytes(&bytes)
.map_err(|e| format!("Unable to parse SSZ genesis state file: {:?}", e))?;
Ok(Self::from_genesis_state(genesis_state, spec, log))
}
pub fn json_state(file: &PathBuf, spec: ChainSpec, log: Logger) -> Result<Self, String> {
let file = File::open(file.clone())
.map_err(|e| format!("Unable to open JSON genesis state file {:?}: {:?}", file, e))?;
let genesis_state = serde_json::from_reader(file)
.map_err(|e| format!("Unable to parse JSON genesis state file: {:?}", e))?;
Ok(Self::from_genesis_state(genesis_state, spec, log))
}
pub fn http_bootstrap(server: &str, spec: ChainSpec, log: Logger) -> Result<Self, String> { pub fn http_bootstrap(server: &str, spec: ChainSpec, log: Logger) -> Result<Self, String> {
let bootstrapper = Bootstrapper::from_server_string(server.to_string()) let bootstrapper = Bootstrapper::connect(server.to_string(), &log)
.map_err(|e| format!("Failed to initialize bootstrap client: {}", e))?; .map_err(|e| format!("Failed to initialize bootstrap client: {}", e))?;
let (genesis_state, genesis_block) = bootstrapper let (genesis_state, genesis_block) = bootstrapper
@ -102,16 +127,23 @@ impl<T: BeaconChainTypes> BeaconChainBuilder<T> {
} }
} }
pub fn build(self, store: Arc<T::Store>) -> Result<BeaconChain<T>, String> { pub fn build(
self,
store: Arc<T::Store>,
eth1_backend: T::Eth1Chain,
) -> Result<BeaconChain<T>, String> {
Ok(match self.build_strategy { Ok(match self.build_strategy {
BuildStrategy::LoadFromStore => BeaconChain::from_store(store, self.spec, self.log) BuildStrategy::LoadFromStore => {
BeaconChain::from_store(store, eth1_backend, self.spec, self.log)
.map_err(|e| format!("Error loading BeaconChain from database: {:?}", e))? .map_err(|e| format!("Error loading BeaconChain from database: {:?}", e))?
.ok_or_else(|| format!("Unable to find exising BeaconChain in database."))?, .ok_or_else(|| format!("Unable to find exising BeaconChain in database."))?
}
BuildStrategy::FromGenesis { BuildStrategy::FromGenesis {
genesis_block, genesis_block,
genesis_state, genesis_state,
} => BeaconChain::from_genesis( } => BeaconChain::from_genesis(
store, store,
eth1_backend,
genesis_state.as_ref().clone(), genesis_state.as_ref().clone(),
genesis_block.as_ref().clone(), genesis_block.as_ref().clone(),
self.spec, self.spec,
@ -135,12 +167,11 @@ fn genesis_block<T: EthSpec>(genesis_state: &BeaconState<T>, spec: &ChainSpec) -
/// Reference: /// Reference:
/// https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start /// https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start
fn interop_genesis_state<T: EthSpec>( fn interop_genesis_state<T: EthSpec>(
validator_count: usize, keypairs: &[Keypair],
genesis_time: u64, genesis_time: u64,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<BeaconState<T>, String> { ) -> Result<BeaconState<T>, String> {
let keypairs = generate_deterministic_keypairs(validator_count); let eth1_block_hash = Hash256::from_slice(&[0x42; 32]);
let eth1_block_hash = Hash256::from_slice(&[42; 32]);
let eth1_timestamp = 2_u64.pow(40); let eth1_timestamp = 2_u64.pow(40);
let amount = spec.max_effective_balance; let amount = spec.max_effective_balance;
@ -155,7 +186,7 @@ fn interop_genesis_state<T: EthSpec>(
.map(|keypair| { .map(|keypair| {
let mut data = DepositData { let mut data = DepositData {
withdrawal_credentials: withdrawal_credentials(&keypair.pk), withdrawal_credentials: withdrawal_credentials(&keypair.pk),
pubkey: keypair.pk.into(), pubkey: keypair.pk.clone().into(),
amount, amount,
signature: Signature::empty_signature().into(), signature: Signature::empty_signature().into(),
}; };
@ -212,6 +243,9 @@ fn interop_genesis_state<T: EthSpec>(
state.genesis_time = genesis_time; state.genesis_time = genesis_time;
// Invalid all the caches after all the manual state surgery.
state.drop_all_caches();
Ok(state) Ok(state)
} }
@ -237,7 +271,7 @@ fn recent_genesis_time(minutes: u64) -> u64 {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use types::{EthSpec, MinimalEthSpec}; use types::{test_utils::generate_deterministic_keypairs, EthSpec, MinimalEthSpec};
type TestEthSpec = MinimalEthSpec; type TestEthSpec = MinimalEthSpec;
@ -247,12 +281,14 @@ mod test {
let genesis_time = 42; let genesis_time = 42;
let spec = &TestEthSpec::default_spec(); let spec = &TestEthSpec::default_spec();
let state = interop_genesis_state::<TestEthSpec>(validator_count, genesis_time, spec) let keypairs = generate_deterministic_keypairs(validator_count);
let state = interop_genesis_state::<TestEthSpec>(&keypairs, genesis_time, spec)
.expect("should build state"); .expect("should build state");
assert_eq!( assert_eq!(
state.eth1_data.block_hash, state.eth1_data.block_hash,
Hash256::from_slice(&[42; 32]), Hash256::from_slice(&[0x42; 32]),
"eth1 block hash should be co-ordinated junk" "eth1 block hash should be co-ordinated junk"
); );

View File

@ -1,3 +1,4 @@
use crate::eth1_chain::Error as Eth1ChainError;
use crate::fork_choice::Error as ForkChoiceError; use crate::fork_choice::Error as ForkChoiceError;
use state_processing::per_block_processing::errors::AttestationValidationError; use state_processing::per_block_processing::errors::AttestationValidationError;
use state_processing::BlockProcessingError; use state_processing::BlockProcessingError;
@ -33,6 +34,7 @@ pub enum BeaconChainError {
MissingBeaconBlock(Hash256), MissingBeaconBlock(Hash256),
MissingBeaconState(Hash256), MissingBeaconState(Hash256),
SlotProcessingError(SlotProcessingError), SlotProcessingError(SlotProcessingError),
UnableToAdvanceState(String),
NoStateForAttestation { NoStateForAttestation {
beacon_block_root: Hash256, beacon_block_root: Hash256,
}, },
@ -42,6 +44,7 @@ pub enum BeaconChainError {
} }
easy_from_to!(SlotProcessingError, BeaconChainError); easy_from_to!(SlotProcessingError, BeaconChainError);
easy_from_to!(AttestationValidationError, BeaconChainError);
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum BlockProductionError { pub enum BlockProductionError {
@ -50,10 +53,11 @@ pub enum BlockProductionError {
UnableToProduceAtSlot(Slot), UnableToProduceAtSlot(Slot),
SlotProcessingError(SlotProcessingError), SlotProcessingError(SlotProcessingError),
BlockProcessingError(BlockProcessingError), BlockProcessingError(BlockProcessingError),
Eth1ChainError(Eth1ChainError),
BeaconStateError(BeaconStateError), BeaconStateError(BeaconStateError),
} }
easy_from_to!(BlockProcessingError, BlockProductionError); easy_from_to!(BlockProcessingError, BlockProductionError);
easy_from_to!(BeaconStateError, BlockProductionError); easy_from_to!(BeaconStateError, BlockProductionError);
easy_from_to!(SlotProcessingError, BlockProductionError); easy_from_to!(SlotProcessingError, BlockProductionError);
easy_from_to!(AttestationValidationError, BeaconChainError); easy_from_to!(Eth1ChainError, BlockProductionError);

View File

@ -0,0 +1,110 @@
use crate::BeaconChainTypes;
use eth2_hashing::hash;
use std::marker::PhantomData;
use types::{BeaconState, Deposit, Eth1Data, EthSpec, Hash256};
type Result<T> = std::result::Result<T, Error>;
/// Holds an `Eth1ChainBackend` and serves requests from the `BeaconChain`.
pub struct Eth1Chain<T: BeaconChainTypes> {
backend: T::Eth1Chain,
}
impl<T: BeaconChainTypes> Eth1Chain<T> {
pub fn new(backend: T::Eth1Chain) -> Self {
Self { backend }
}
/// Returns the `Eth1Data` that should be included in a block being produced for the given
/// `state`.
pub fn eth1_data_for_block_production(
&self,
state: &BeaconState<T::EthSpec>,
) -> Result<Eth1Data> {
self.backend.eth1_data(state)
}
/// Returns a list of `Deposits` that may be included in a block.
///
/// Including all of the returned `Deposits` in a block should _not_ cause it to become
/// invalid.
pub fn deposits_for_block_inclusion(
&self,
state: &BeaconState<T::EthSpec>,
) -> Result<Vec<Deposit>> {
let deposits = self.backend.queued_deposits(state)?;
// TODO: truncate deposits if required.
Ok(deposits)
}
}
#[derive(Debug, PartialEq)]
pub enum Error {
/// Unable to return an Eth1Data for the given epoch.
EpochUnavailable,
/// An error from the backend service (e.g., the web3 data fetcher).
BackendError(String),
}
pub trait Eth1ChainBackend<T: EthSpec>: Sized + Send + Sync {
fn new(server: String) -> Result<Self>;
/// Returns the `Eth1Data` that should be included in a block being produced for the given
/// `state`.
fn eth1_data(&self, beacon_state: &BeaconState<T>) -> Result<Eth1Data>;
/// Returns all `Deposits` between `state.eth1_deposit_index` and
/// `state.eth1_data.deposit_count`.
///
/// # Note:
///
/// It is possible that not all returned `Deposits` can be included in a block. E.g., there may
/// be more than `MAX_DEPOSIT_COUNT` or the churn may be too high.
fn queued_deposits(&self, beacon_state: &BeaconState<T>) -> Result<Vec<Deposit>>;
}
pub struct InteropEth1ChainBackend<T: EthSpec> {
_phantom: PhantomData<T>,
}
impl<T: EthSpec> Eth1ChainBackend<T> for InteropEth1ChainBackend<T> {
fn new(_server: String) -> Result<Self> {
Ok(Self::default())
}
fn eth1_data(&self, state: &BeaconState<T>) -> Result<Eth1Data> {
let current_epoch = state.current_epoch();
let slots_per_voting_period = T::slots_per_eth1_voting_period() as u64;
let current_voting_period: u64 = current_epoch.as_u64() / slots_per_voting_period;
let deposit_root = hash(&int_to_bytes32(current_voting_period));
let block_hash = hash(&deposit_root);
Ok(Eth1Data {
deposit_root: Hash256::from_slice(&deposit_root),
deposit_count: state.eth1_deposit_index,
block_hash: Hash256::from_slice(&block_hash),
})
}
fn queued_deposits(&self, _: &BeaconState<T>) -> Result<Vec<Deposit>> {
Ok(vec![])
}
}
impl<T: EthSpec> Default for InteropEth1ChainBackend<T> {
fn default() -> Self {
Self {
_phantom: PhantomData,
}
}
}
/// Returns `int` as little-endian bytes with a length of 32.
fn int_to_bytes32(int: u64) -> Vec<u8> {
let mut vec = int.to_le_bytes().to_vec();
vec.resize(32, 0);
vec
}

View File

@ -6,6 +6,7 @@ mod beacon_chain;
mod beacon_chain_builder; mod beacon_chain_builder;
mod checkpoint; mod checkpoint;
mod errors; mod errors;
mod eth1_chain;
mod fork_choice; mod fork_choice;
mod iter; mod iter;
mod metrics; mod metrics;
@ -18,6 +19,7 @@ pub use self::beacon_chain::{
pub use self::checkpoint::CheckPoint; pub use self::checkpoint::CheckPoint;
pub use self::errors::{BeaconChainError, BlockProductionError}; pub use self::errors::{BeaconChainError, BlockProductionError};
pub use beacon_chain_builder::BeaconChainBuilder; pub use beacon_chain_builder::BeaconChainBuilder;
pub use eth1_chain::{Eth1ChainBackend, InteropEth1ChainBackend};
pub use lmd_ghost; pub use lmd_ghost;
pub use metrics::scrape_for_metrics; pub use metrics::scrape_for_metrics;
pub use parking_lot; pub use parking_lot;

View File

@ -1,22 +1,28 @@
use crate::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome}; use crate::{
AttestationProcessingOutcome, BeaconChain, BeaconChainBuilder, BeaconChainTypes,
BlockProcessingOutcome, InteropEth1ChainBackend,
};
use lmd_ghost::LmdGhost; use lmd_ghost::LmdGhost;
use rayon::prelude::*; use rayon::prelude::*;
use sloggers::{null::NullLoggerBuilder, Build}; use sloggers::{terminal::TerminalLoggerBuilder, types::Severity, Build};
use slot_clock::TestingSlotClock; use slot_clock::TestingSlotClock;
use state_processing::per_slot_processing; use state_processing::per_slot_processing;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc; use std::sync::Arc;
use store::MemoryStore; use store::MemoryStore;
use store::Store;
use tree_hash::{SignedRoot, TreeHash}; use tree_hash::{SignedRoot, TreeHash};
use types::{ use types::{
test_utils::TestingBeaconStateBuilder, AggregateSignature, Attestation, AggregateSignature, Attestation, AttestationDataAndCustodyBit, BeaconBlock, BeaconState,
AttestationDataAndCustodyBit, BeaconBlock, BeaconState, BitList, ChainSpec, Domain, EthSpec, BitList, ChainSpec, Domain, EthSpec, Hash256, Keypair, RelativeEpoch, SecretKey, Signature,
Hash256, Keypair, RelativeEpoch, SecretKey, Signature, Slot, Slot,
}; };
pub use types::test_utils::generate_deterministic_keypairs;
pub use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY}; pub use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY};
pub const HARNESS_GENESIS_TIME: u64 = 1567552690; // 4th September 2019
/// Indicates how the `BeaconChainHarness` should produce blocks. /// Indicates how the `BeaconChainHarness` should produce blocks.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum BlockStrategy { pub enum BlockStrategy {
@ -60,6 +66,7 @@ where
type Store = MemoryStore; type Store = MemoryStore;
type SlotClock = TestingSlotClock; type SlotClock = TestingSlotClock;
type LmdGhost = L; type LmdGhost = L;
type Eth1Chain = InteropEth1ChainBackend<E>;
type EthSpec = E; type EthSpec = E;
} }
@ -83,40 +90,21 @@ where
E: EthSpec, E: EthSpec,
{ {
/// Instantiate a new harness with `validator_count` initial validators. /// Instantiate a new harness with `validator_count` initial validators.
pub fn new(validator_count: usize) -> Self { pub fn new(keypairs: Vec<Keypair>) -> Self {
let state_builder = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(
validator_count,
&E::default_spec(),
);
let (genesis_state, keypairs) = state_builder.build();
Self::from_state_and_keypairs(genesis_state, keypairs)
}
/// Instantiate a new harness with an initial validator for each key supplied.
pub fn from_keypairs(keypairs: Vec<Keypair>) -> Self {
let state_builder = TestingBeaconStateBuilder::from_keypairs(keypairs, &E::default_spec());
let (genesis_state, keypairs) = state_builder.build();
Self::from_state_and_keypairs(genesis_state, keypairs)
}
/// Instantiate a new harness with the given genesis state and a keypair for each of the
/// initial validators in the given state.
pub fn from_state_and_keypairs(genesis_state: BeaconState<E>, keypairs: Vec<Keypair>) -> Self {
let spec = E::default_spec(); let spec = E::default_spec();
let log = TerminalLoggerBuilder::new()
.level(Severity::Warning)
.build()
.expect("logger should build");
let store = Arc::new(MemoryStore::open()); let store = Arc::new(MemoryStore::open());
let mut genesis_block = BeaconBlock::empty(&spec);
genesis_block.state_root = Hash256::from_slice(&genesis_state.tree_hash_root());
let builder = NullLoggerBuilder;
let log = builder.build().expect("logger should build");
let chain = let chain =
BeaconChain::from_genesis(store, genesis_state, genesis_block, spec.clone(), log) BeaconChainBuilder::quick_start(HARNESS_GENESIS_TIME, &keypairs, spec.clone(), log)
.expect("Terminate if beacon chain generation fails"); .unwrap_or_else(|e| panic!("Failed to create beacon chain builder: {}", e))
.build(store.clone(), InteropEth1ChainBackend::default())
.unwrap_or_else(|e| panic!("Failed to build beacon chain: {}", e));
Self { Self {
chain, chain,
@ -156,7 +144,10 @@ where
BlockStrategy::ForkCanonicalChainAt { previous_slot, .. } => previous_slot, BlockStrategy::ForkCanonicalChainAt { previous_slot, .. } => previous_slot,
}; };
self.get_state_at_slot(state_slot) self.chain
.state_at_slot(state_slot)
.expect("should find state for slot")
.clone()
}; };
// Determine the first slot where a block should be built. // Determine the first slot where a block should be built.
@ -194,21 +185,6 @@ where
head_block_root.expect("did not produce any blocks") head_block_root.expect("did not produce any blocks")
} }
fn get_state_at_slot(&self, state_slot: Slot) -> BeaconState<E> {
let state_root = self
.chain
.rev_iter_state_roots()
.find(|(_hash, slot)| *slot == state_slot)
.map(|(hash, _slot)| hash)
.expect("could not find state root");
self.chain
.store
.get(&state_root)
.expect("should read db")
.expect("should find state root")
}
/// Returns a newly created block, signed by the proposer for the given slot. /// Returns a newly created block, signed by the proposer for the given slot.
fn build_block( fn build_block(
&self, &self,
@ -282,9 +258,14 @@ where
) )
.into_iter() .into_iter()
.for_each(|attestation| { .for_each(|attestation| {
self.chain match self
.chain
.process_attestation(attestation) .process_attestation(attestation)
.expect("should process attestation"); .expect("should not error during attestation processing")
{
AttestationProcessingOutcome::Processed => (),
other => panic!("did not successfully process attestation: {:?}", other),
}
}); });
} }

View File

@ -3,11 +3,14 @@
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
use beacon_chain::test_utils::{ use beacon_chain::AttestationProcessingOutcome;
use beacon_chain::{
test_utils::{
AttestationStrategy, BeaconChainHarness, BlockStrategy, CommonTypes, PersistedBeaconChain, AttestationStrategy, BeaconChainHarness, BlockStrategy, CommonTypes, PersistedBeaconChain,
BEACON_CHAIN_DB_KEY, BEACON_CHAIN_DB_KEY,
},
BlockProcessingOutcome,
}; };
use beacon_chain::AttestationProcessingOutcome;
use lmd_ghost::ThreadSafeReducedTree; use lmd_ghost::ThreadSafeReducedTree;
use rand::Rng; use rand::Rng;
use store::{MemoryStore, Store}; use store::{MemoryStore, Store};
@ -25,7 +28,7 @@ lazy_static! {
type TestForkChoice = ThreadSafeReducedTree<MemoryStore, MinimalEthSpec>; type TestForkChoice = ThreadSafeReducedTree<MemoryStore, MinimalEthSpec>;
fn get_harness(validator_count: usize) -> BeaconChainHarness<TestForkChoice, MinimalEthSpec> { fn get_harness(validator_count: usize) -> BeaconChainHarness<TestForkChoice, MinimalEthSpec> {
let harness = BeaconChainHarness::from_keypairs(KEYPAIRS[0..validator_count].to_vec()); let harness = BeaconChainHarness::new(KEYPAIRS[0..validator_count].to_vec());
harness.advance_slot(); harness.advance_slot();
@ -461,3 +464,48 @@ fn free_attestations_added_to_fork_choice_all_updated() {
} }
} }
} }
fn run_skip_slot_test(skip_slots: u64) {
let num_validators = 8;
let harness_a = get_harness(num_validators);
let harness_b = get_harness(num_validators);
for _ in 0..skip_slots {
harness_a.advance_slot();
harness_b.advance_slot();
}
harness_a.extend_chain(
1,
BlockStrategy::OnCanonicalHead,
// No attestation required for test.
AttestationStrategy::SomeValidators(vec![]),
);
assert_eq!(
harness_a.chain.head().beacon_block.slot,
Slot::new(skip_slots + 1)
);
assert_eq!(harness_b.chain.head().beacon_block.slot, Slot::new(0));
assert_eq!(
harness_b
.chain
.process_block(harness_a.chain.head().beacon_block.clone()),
Ok(BlockProcessingOutcome::Processed {
block_root: harness_a.chain.head().beacon_block_root
})
);
assert_eq!(
harness_b.chain.head().beacon_block.slot,
Slot::new(skip_slots + 1)
);
}
#[test]
fn produces_and_processes_with_genesis_skip_slots() {
for i in 0..MinimalEthSpec::slots_per_epoch() * 4 {
run_skip_slot_test(i)
}
}

View File

@ -23,6 +23,7 @@ pub struct Config {
/// files. It can only be configured via the CLI. /// files. It can only be configured via the CLI.
#[serde(skip)] #[serde(skip)]
pub beacon_chain_start_method: BeaconChainStartMethod, pub beacon_chain_start_method: BeaconChainStartMethod,
pub eth1_backend_method: Eth1BackendMethod,
pub network: network::NetworkConfig, pub network: network::NetworkConfig,
pub rpc: rpc::RPCConfig, pub rpc: rpc::RPCConfig,
pub rest_api: rest_api::ApiConfig, pub rest_api: rest_api::ApiConfig,
@ -54,6 +55,10 @@ pub enum BeaconChainStartMethod {
}, },
/// Create a new beacon chain by loading a YAML-encoded genesis state from a file. /// Create a new beacon chain by loading a YAML-encoded genesis state from a file.
Yaml { file: PathBuf }, Yaml { file: PathBuf },
/// Create a new beacon chain by loading a SSZ-encoded genesis state from a file.
Ssz { file: PathBuf },
/// Create a new beacon chain by loading a JSON-encoded genesis state from a file.
Json { file: PathBuf },
/// Create a new beacon chain by using a HTTP server (running our REST-API) to load genesis and /// Create a new beacon chain by using a HTTP server (running our REST-API) to load genesis and
/// finalized states and blocks. /// finalized states and blocks.
HttpBootstrap { server: String, port: Option<u16> }, HttpBootstrap { server: String, port: Option<u16> },
@ -65,6 +70,22 @@ impl Default for BeaconChainStartMethod {
} }
} }
/// Defines which Eth1 backend the client should use.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Eth1BackendMethod {
/// Use the mocked eth1 backend used in interop testing
Interop,
/// Use a web3 connection to a running Eth1 node.
Web3 { server: String },
}
impl Default for Eth1BackendMethod {
fn default() -> Self {
Eth1BackendMethod::Interop
}
}
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -77,6 +98,7 @@ impl Default for Config {
rest_api: <_>::default(), rest_api: <_>::default(),
spec_constants: TESTNET_SPEC_CONSTANTS.into(), spec_constants: TESTNET_SPEC_CONSTANTS.into(),
beacon_chain_start_method: <_>::default(), beacon_chain_start_method: <_>::default(),
eth1_backend_method: <_>::default(),
} }
} }
} }

View File

@ -6,8 +6,8 @@ pub mod error;
pub mod notifier; pub mod notifier;
use beacon_chain::{ use beacon_chain::{
lmd_ghost::ThreadSafeReducedTree, slot_clock::SystemTimeSlotClock, store::Store, BeaconChain, lmd_ghost::ThreadSafeReducedTree, slot_clock::SystemTimeSlotClock, store::Store,
BeaconChainBuilder, test_utils::generate_deterministic_keypairs, BeaconChain, BeaconChainBuilder,
}; };
use exit_future::Signal; use exit_future::Signal;
use futures::{future::Future, Stream}; use futures::{future::Future, Stream};
@ -21,14 +21,14 @@ use tokio::runtime::TaskExecutor;
use tokio::timer::Interval; use tokio::timer::Interval;
use types::EthSpec; use types::EthSpec;
pub use beacon_chain::BeaconChainTypes; pub use beacon_chain::{BeaconChainTypes, Eth1ChainBackend, InteropEth1ChainBackend};
pub use config::{BeaconChainStartMethod, Config as ClientConfig}; pub use config::{BeaconChainStartMethod, Config as ClientConfig, Eth1BackendMethod};
pub use eth2_config::Eth2Config; pub use eth2_config::Eth2Config;
#[derive(Clone)] #[derive(Clone)]
pub struct ClientType<S: Store, E: EthSpec> { pub struct ClientType<S: Store, E: EthSpec> {
_phantom_t: PhantomData<S>, _phantom_s: PhantomData<S>,
_phantom_u: PhantomData<E>, _phantom_e: PhantomData<E>,
} }
impl<S, E> BeaconChainTypes for ClientType<S, E> impl<S, E> BeaconChainTypes for ClientType<S, E>
@ -39,6 +39,7 @@ where
type Store = S; type Store = S;
type SlotClock = SystemTimeSlotClock; type SlotClock = SystemTimeSlotClock;
type LmdGhost = ThreadSafeReducedTree<S, E>; type LmdGhost = ThreadSafeReducedTree<S, E>;
type Eth1Chain = InteropEth1ChainBackend<E>;
type EthSpec = E; type EthSpec = E;
} }
@ -105,7 +106,7 @@ where
"method" => "recent" "method" => "recent"
); );
BeaconChainBuilder::recent_genesis( BeaconChainBuilder::recent_genesis(
*validator_count, &generate_deterministic_keypairs(*validator_count),
*minutes, *minutes,
spec.clone(), spec.clone(),
log.clone(), log.clone(),
@ -124,7 +125,7 @@ where
); );
BeaconChainBuilder::quick_start( BeaconChainBuilder::quick_start(
*genesis_time, *genesis_time,
*validator_count, &generate_deterministic_keypairs(*validator_count),
spec.clone(), spec.clone(),
log.clone(), log.clone(),
)? )?
@ -138,6 +139,24 @@ where
); );
BeaconChainBuilder::yaml_state(file, spec.clone(), log.clone())? BeaconChainBuilder::yaml_state(file, spec.clone(), log.clone())?
} }
BeaconChainStartMethod::Ssz { file } => {
info!(
log,
"Starting beacon chain";
"file" => format!("{:?}", file),
"method" => "ssz"
);
BeaconChainBuilder::ssz_state(file, spec.clone(), log.clone())?
}
BeaconChainStartMethod::Json { file } => {
info!(
log,
"Starting beacon chain";
"file" => format!("{:?}", file),
"method" => "json"
);
BeaconChainBuilder::json_state(file, spec.clone(), log.clone())?
}
BeaconChainStartMethod::HttpBootstrap { server, port } => { BeaconChainStartMethod::HttpBootstrap { server, port } => {
info!( info!(
log, log,
@ -150,9 +169,11 @@ where
} }
}; };
let eth1_backend = T::Eth1Chain::new(String::new()).map_err(|e| format!("{:?}", e))?;
let beacon_chain: Arc<BeaconChain<T>> = Arc::new( let beacon_chain: Arc<BeaconChain<T>> = Arc::new(
beacon_chain_builder beacon_chain_builder
.build(store) .build(store, eth1_backend)
.map_err(error::Error::from)?, .map_err(error::Error::from)?,
); );

View File

@ -914,7 +914,7 @@ fn process_blocks<T: BeaconChainTypes>(
BlockProcessingOutcome::ParentUnknown { parent } => { BlockProcessingOutcome::ParentUnknown { parent } => {
// blocks should be sequential and all parents should exist // blocks should be sequential and all parents should exist
trace!( trace!(
log, "ParentBlockUnknown"; log, "Parent block is unknown";
"parent_root" => format!("{}", parent), "parent_root" => format!("{}", parent),
"baby_block_slot" => block.slot, "baby_block_slot" => block.slot,
); );
@ -923,6 +923,13 @@ fn process_blocks<T: BeaconChainTypes>(
block.slot block.slot
)); ));
} }
BlockProcessingOutcome::BlockIsAlreadyKnown => {
// this block is already known to us, move to the next
debug!(
log, "Imported a block that is already known";
"parent_root" => format!("{}", parent),
"baby_block_slot" => block.slot,
);
BlockProcessingOutcome::FutureSlot { BlockProcessingOutcome::FutureSlot {
present_slot, present_slot,
block_slot, block_slot,
@ -930,7 +937,7 @@ fn process_blocks<T: BeaconChainTypes>(
if present_slot + FUTURE_SLOT_TOLERANCE >= block_slot { if present_slot + FUTURE_SLOT_TOLERANCE >= block_slot {
// The block is too far in the future, drop it. // The block is too far in the future, drop it.
trace!( trace!(
log, "FutureBlock"; self.log, "Block is ahead of our slot clock";
"msg" => "block for future slot rejected, check your time", "msg" => "block for future slot rejected, check your time",
"present_slot" => present_slot, "present_slot" => present_slot,
"block_slot" => block_slot, "block_slot" => block_slot,
@ -943,14 +950,26 @@ fn process_blocks<T: BeaconChainTypes>(
} else { } else {
// The block is in the future, but not too far. // The block is in the future, but not too far.
trace!( trace!(
log, "QueuedFutureBlock"; self.log, "Block is slightly ahead of our slot clock, ignoring.";
"msg" => "queuing future block, check your time",
"present_slot" => present_slot, "present_slot" => present_slot,
"block_slot" => block_slot, "block_slot" => block_slot,
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE, "FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
); );
} }
} }
BlockProcessingOutcome::WouldRevertFinalizedSlot { .. } => {
trace!(
self.log, "Finalized or earlier block processed";
"outcome" => format!("{:?}", outcome),
);
// block reached our finalized slot or was earlier, move to the next block
}
BlockProcessingOutcome::GenesisBlock => {
trace!(
self.log, "Genesis block was processed";
"outcome" => format!("{:?}", outcome),
);
}
BlockProcessingOutcome::FinalizedSlot => { BlockProcessingOutcome::FinalizedSlot => {
trace!( trace!(
log, "Finalized or earlier block processed"; log, "Finalized or earlier block processed";
@ -959,8 +978,8 @@ fn process_blocks<T: BeaconChainTypes>(
// block reached our finalized slot or was earlier, move to the next block // block reached our finalized slot or was earlier, move to the next block
} }
_ => { _ => {
trace!( warn!(
log, "InvalidBlock"; log, "Invalid block received";
"msg" => "peer sent invalid block", "msg" => "peer sent invalid block",
"outcome" => format!("{:?}", outcome), "outcome" => format!("{:?}", outcome),
); );
@ -968,7 +987,7 @@ fn process_blocks<T: BeaconChainTypes>(
} }
} }
} else { } else {
trace!( warn!(
log, "BlockProcessingFailure"; log, "BlockProcessingFailure";
"msg" => "unexpected condition in processing block.", "msg" => "unexpected condition in processing block.",
"outcome" => format!("{:?}", processing_result) "outcome" => format!("{:?}", processing_result)

View File

@ -385,7 +385,7 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
"peer" => format!("{:?}", peer_id), "peer" => format!("{:?}", peer_id),
"msg" => "Failed to return all requested hashes", "msg" => "Failed to return all requested hashes",
"start_slot" => req.start_slot, "start_slot" => req.start_slot,
"current_slot" => self.chain.best_slot(), "current_slot" => format!("{:?}", self.chain.slot()),
"requested" => req.count, "requested" => req.count,
"returned" => blocks.len(), "returned" => blocks.len(),
); );
@ -523,6 +523,12 @@ impl NetworkContext {
} }
pub fn disconnect(&mut self, peer_id: PeerId, reason: GoodbyeReason) { pub fn disconnect(&mut self, peer_id: PeerId, reason: GoodbyeReason) {
warn!(
&self.log,
"Disconnecting peer (RPC)";
"reason" => format!("{:?}", reason),
"peer_id" => format!("{:?}", peer_id),
);
self.send_rpc_request(None, peer_id, RPCRequest::Goodbye(reason)) self.send_rpc_request(None, peer_id, RPCRequest::Goodbye(reason))
// TODO: disconnect peers. // TODO: disconnect peers.
} }

View File

@ -14,9 +14,12 @@ store = { path = "../store" }
version = { path = "../version" } version = { path = "../version" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "^1.0" serde_json = "^1.0"
serde_yaml = "0.8"
slog = "^2.2.3" slog = "^2.2.3"
slog-term = "^2.4.0" slog-term = "^2.4.0"
slog-async = "^2.3.0" slog-async = "^2.3.0"
eth2_ssz = { path = "../../eth2/utils/ssz" }
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.32.0" clap = "2.32.0"

View File

@ -1,8 +1,9 @@
use super::{success_response, ApiResult}; use super::{success_response, ApiResult, ResponseBuilder};
use crate::{helpers::*, ApiError, UrlQuery}; use crate::{helpers::*, ApiError, UrlQuery};
use beacon_chain::{BeaconChain, BeaconChainTypes}; use beacon_chain::{BeaconChain, BeaconChainTypes};
use hyper::{Body, Request}; use hyper::{Body, Request};
use serde::Serialize; use serde::Serialize;
use ssz_derive::Encode;
use std::sync::Arc; use std::sync::Arc;
use store::Store; use store::Store;
use types::{BeaconBlock, BeaconState, EthSpec, Hash256, Slot}; use types::{BeaconBlock, BeaconState, EthSpec, Hash256, Slot};
@ -33,7 +34,7 @@ pub fn get_head<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult
Ok(success_response(Body::from(json))) Ok(success_response(Body::from(json)))
} }
#[derive(Serialize)] #[derive(Serialize, Encode)]
#[serde(bound = "T: EthSpec")] #[serde(bound = "T: EthSpec")]
pub struct BlockResponse<T: EthSpec> { pub struct BlockResponse<T: EthSpec> {
pub root: Hash256, pub root: Hash256,
@ -77,11 +78,7 @@ pub fn get_block<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult
beacon_block: block, beacon_block: block,
}; };
let json: String = serde_json::to_string(&response).map_err(|e| { ResponseBuilder::new(&req).body(&response)
ApiError::ServerError(format!("Unable to serialize BlockResponse: {:?}", e))
})?;
Ok(success_response(Body::from(json)))
} }
/// HTTP handler to return a `BeaconBlock` root at a given `slot`. /// HTTP handler to return a `BeaconBlock` root at a given `slot`.
@ -104,7 +101,22 @@ pub fn get_block_root<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiR
Ok(success_response(Body::from(json))) Ok(success_response(Body::from(json)))
} }
#[derive(Serialize)] /// HTTP handler to return a `BeaconState` at a given `root` or `slot`.
///
/// Will not return a state if the request slot is in the future. Will return states higher than
/// the current head by skipping slots.
pub fn get_genesis_state<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 (_root, state) = state_at_slot(&beacon_chain, Slot::new(0))?;
ResponseBuilder::new(&req).body(&state)
}
#[derive(Serialize, Encode)]
#[serde(bound = "T: EthSpec")] #[serde(bound = "T: EthSpec")]
pub struct StateResponse<T: EthSpec> { pub struct StateResponse<T: EthSpec> {
pub root: Hash256, pub root: Hash256,
@ -144,11 +156,7 @@ pub fn get_state<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult
beacon_state: state, beacon_state: state,
}; };
let json: String = serde_json::to_string(&response).map_err(|e| { ResponseBuilder::new(&req).body(&response)
ApiError::ServerError(format!("Unable to serialize StateResponse: {:?}", e))
})?;
Ok(success_response(Body::from(json)))
} }
/// HTTP handler to return a `BeaconState` root at a given `slot`. /// HTTP handler to return a `BeaconState` root at a given `slot`.

View File

@ -8,6 +8,7 @@ mod helpers;
mod metrics; mod metrics;
mod network; mod network;
mod node; mod node;
mod response_builder;
mod spec; mod spec;
mod url_query; mod url_query;
mod validator; mod validator;
@ -18,6 +19,7 @@ use eth2_config::Eth2Config;
use hyper::rt::Future; use hyper::rt::Future;
use hyper::service::service_fn_ok; use hyper::service::service_fn_ok;
use hyper::{Body, Method, Response, Server, StatusCode}; use hyper::{Body, Method, Response, Server, StatusCode};
use response_builder::ResponseBuilder;
use slog::{info, o, warn}; use slog::{info, o, warn};
use std::ops::Deref; use std::ops::Deref;
use std::path::PathBuf; use std::path::PathBuf;
@ -145,6 +147,7 @@ pub fn start_server<T: BeaconChainTypes>(
beacon::get_latest_finalized_checkpoint::<T>(req) 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/genesis") => beacon::get_genesis_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),
//TODO: Add aggreggate/filtered state lookups here, e.g. /beacon/validators/balances //TODO: Add aggreggate/filtered state lookups here, e.g. /beacon/validators/balances

View File

@ -0,0 +1,50 @@
use super::{ApiError, ApiResult};
use http::header;
use hyper::{Body, Request, Response, StatusCode};
use serde::Serialize;
use ssz::Encode;
pub enum Encoding {
JSON,
SSZ,
YAML,
}
pub struct ResponseBuilder {
encoding: Encoding,
}
impl ResponseBuilder {
pub fn new(req: &Request<Body>) -> Self {
let encoding = match req.headers().get(header::CONTENT_TYPE) {
Some(h) if h == "application/ssz" => Encoding::SSZ,
Some(h) if h == "application/yaml" => Encoding::YAML,
_ => Encoding::JSON,
};
Self { encoding }
}
pub fn body<T: Serialize + Encode>(self, item: &T) -> ApiResult {
let body: Body = match self.encoding {
Encoding::JSON => Body::from(serde_json::to_string(&item).map_err(|e| {
ApiError::ServerError(format!(
"Unable to serialize response body as JSON: {:?}",
e
))
})?),
Encoding::SSZ => Body::from(item.as_ssz_bytes()),
Encoding::YAML => Body::from(serde_yaml::to_string(&item).map_err(|e| {
ApiError::ServerError(format!(
"Unable to serialize response body as YAML: {:?}",
e
))
})?),
};
Response::builder()
.status(StatusCode::OK)
.body(Body::from(body))
.map_err(|e| ApiError::ServerError(format!("Failed to build response: {:?}", e)))
}
}

View File

@ -32,7 +32,7 @@ impl<T: BeaconChainTypes> ValidatorService for ValidatorServiceInstance<T> {
let slot = epoch.start_slot(T::EthSpec::slots_per_epoch()); let slot = epoch.start_slot(T::EthSpec::slots_per_epoch());
let mut state = if let Ok(state) = self.chain.state_at_slot(slot) { let mut state = if let Ok(state) = self.chain.state_at_slot(slot) {
state.as_ref().clone() state.clone()
} else { } else {
let log_clone = self.log.clone(); let log_clone = self.log.clone();
let f = sink let f = sink

View File

@ -1,5 +1,5 @@
use clap::ArgMatches; use clap::ArgMatches;
use client::{BeaconChainStartMethod, ClientConfig, Eth2Config}; use client::{BeaconChainStartMethod, ClientConfig, Eth1BackendMethod, Eth2Config};
use eth2_config::{read_from_file, write_to_file}; use eth2_config::{read_from_file, write_to_file};
use lighthouse_bootstrap::Bootstrapper; use lighthouse_bootstrap::Bootstrapper;
use rand::{distributions::Alphanumeric, Rng}; use rand::{distributions::Alphanumeric, Rng};
@ -25,6 +25,14 @@ type Config = (ClientConfig, Eth2Config);
pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result<Config> { pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result<Config> {
let mut builder = ConfigBuilder::new(cli_args, log)?; let mut builder = ConfigBuilder::new(cli_args, log)?;
if let Some(server) = cli_args.value_of("eth1-server") {
builder.set_eth1_backend_method(Eth1BackendMethod::Web3 {
server: server.into(),
})
} else {
builder.set_eth1_backend_method(Eth1BackendMethod::Interop)
}
match cli_args.subcommand() { match cli_args.subcommand() {
("testnet", Some(sub_cmd_args)) => { ("testnet", Some(sub_cmd_args)) => {
process_testnet_subcommand(&mut builder, sub_cmd_args, log)? process_testnet_subcommand(&mut builder, sub_cmd_args, log)?
@ -70,11 +78,15 @@ fn process_testnet_subcommand(
builder.set_random_datadir()?; builder.set_random_datadir()?;
} }
if cli_args.is_present("force") {
builder.clean_datadir()?;
}
let is_bootstrap = cli_args.subcommand_name() == Some("bootstrap"); let is_bootstrap = cli_args.subcommand_name() == Some("bootstrap");
if let Some(path_string) = cli_args.value_of("eth2-config") { if let Some(path_string) = cli_args.value_of("eth2-config") {
if is_bootstrap { if is_bootstrap {
return Err("Cannot supply --eth2-config when using bootsrap".to_string()); return Err("Cannot supply --eth2-config when using bootstrap".to_string());
} }
let path = path_string let path = path_string
@ -85,6 +97,18 @@ fn process_testnet_subcommand(
builder.update_spec_from_subcommand(&cli_args)?; builder.update_spec_from_subcommand(&cli_args)?;
} }
if let Some(slot_time) = cli_args.value_of("slot-time") {
if is_bootstrap {
return Err("Cannot supply --slot-time flag whilst using bootstrap.".into());
}
let slot_time = slot_time
.parse::<u64>()
.map_err(|e| format!("Unable to parse slot-time: {:?}", e))?;
builder.set_slot_time(slot_time);
}
if let Some(path_string) = cli_args.value_of("client-config") { if let Some(path_string) = cli_args.value_of("client-config") {
let path = path_string let path = path_string
.parse::<PathBuf>() .parse::<PathBuf>()
@ -92,10 +116,6 @@ fn process_testnet_subcommand(
builder.load_client_config(path)?; builder.load_client_config(path)?;
} }
if cli_args.is_present("force") {
builder.clean_datadir()?;
}
info!( info!(
log, log,
"Creating new datadir"; "Creating new datadir";
@ -117,6 +137,7 @@ fn process_testnet_subcommand(
.and_then(|s| s.parse::<u16>().ok()); .and_then(|s| s.parse::<u16>().ok());
builder.import_bootstrap_libp2p_address(server, port)?; builder.import_bootstrap_libp2p_address(server, port)?;
builder.import_bootstrap_enr_address(server)?;
builder.import_bootstrap_eth2_config(server)?; builder.import_bootstrap_eth2_config(server)?;
builder.set_beacon_chain_start_method(BeaconChainStartMethod::HttpBootstrap { builder.set_beacon_chain_start_method(BeaconChainStartMethod::HttpBootstrap {
@ -160,6 +181,32 @@ fn process_testnet_subcommand(
genesis_time, genesis_time,
}) })
} }
("file", Some(cli_args)) => {
let file = cli_args
.value_of("file")
.ok_or_else(|| "No filename specified")?
.parse::<PathBuf>()
.map_err(|e| format!("Unable to parse filename: {:?}", e))?;
let format = cli_args
.value_of("format")
.ok_or_else(|| "No file format specified")?;
let start_method = match format {
"yaml" => BeaconChainStartMethod::Yaml { file },
"ssz" => BeaconChainStartMethod::Ssz { file },
"json" => BeaconChainStartMethod::Json { file },
other => return Err(format!("Unknown genesis file format: {}", other)),
};
builder.set_beacon_chain_start_method(start_method)
}
(cmd, Some(_)) => {
return Err(format!(
"Invalid valid method specified: {}. See 'testnet --help'.",
cmd
))
}
_ => return Err("No testnet method specified. See 'testnet --help'.".into()), _ => return Err("No testnet method specified. See 'testnet --help'.".into()),
}; };
@ -250,7 +297,12 @@ impl<'a> ConfigBuilder<'a> {
self.client_config.beacon_chain_start_method = method; self.client_config.beacon_chain_start_method = method;
} }
/// Import the libp2p address for `server` into the list of bootnodes in `self`. /// Sets the method for starting the beacon chain.
pub fn set_eth1_backend_method(&mut self, method: Eth1BackendMethod) {
self.client_config.eth1_backend_method = method;
}
/// Import the libp2p address for `server` into the list of libp2p nodes to connect with.
/// ///
/// If `port` is `Some`, it is used as the port for the `Multiaddr`. If `port` is `None`, /// If `port` is `Some`, it is used as the port for the `Multiaddr`. If `port` is `None`,
/// attempts to connect to the `server` via HTTP and retrieve it's libp2p listen port. /// attempts to connect to the `server` via HTTP and retrieve it's libp2p listen port.
@ -259,7 +311,7 @@ impl<'a> ConfigBuilder<'a> {
server: &str, server: &str,
port: Option<u16>, port: Option<u16>,
) -> Result<()> { ) -> Result<()> {
let bootstrapper = Bootstrapper::from_server_string(server.to_string())?; let bootstrapper = Bootstrapper::connect(server.to_string(), &self.log)?;
if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr(port) { if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr(port) {
info!( info!(
@ -282,6 +334,28 @@ impl<'a> ConfigBuilder<'a> {
Ok(()) Ok(())
} }
/// Import the enr address for `server` into the list of initial enrs (boot nodes).
pub fn import_bootstrap_enr_address(&mut self, server: &str) -> Result<()> {
let bootstrapper = Bootstrapper::connect(server.to_string(), &self.log)?;
if let Ok(enr) = bootstrapper.enr() {
info!(
self.log,
"Loaded bootstrapper libp2p address";
"enr" => format!("{:?}", enr)
);
self.client_config.network.boot_nodes.push(enr);
} else {
warn!(
self.log,
"Unable to estimate a bootstrapper enr address, this node may not find any peers."
);
};
Ok(())
}
/// Set the config data_dir to be an random directory. /// Set the config data_dir to be an random directory.
/// ///
/// Useful for easily spinning up ephemeral testnets. /// Useful for easily spinning up ephemeral testnets.
@ -296,7 +370,7 @@ impl<'a> ConfigBuilder<'a> {
/// Imports an `Eth2Config` from `server`, returning an error if this fails. /// Imports an `Eth2Config` from `server`, returning an error if this fails.
pub fn import_bootstrap_eth2_config(&mut self, server: &str) -> Result<()> { pub fn import_bootstrap_eth2_config(&mut self, server: &str) -> Result<()> {
let bootstrapper = Bootstrapper::from_server_string(server.to_string())?; let bootstrapper = Bootstrapper::connect(server.to_string(), &self.log)?;
self.update_eth2_config(bootstrapper.eth2_config()?); self.update_eth2_config(bootstrapper.eth2_config()?);
@ -307,6 +381,10 @@ impl<'a> ConfigBuilder<'a> {
self.eth2_config = eth2_config; self.eth2_config = eth2_config;
} }
fn set_slot_time(&mut self, milliseconds_per_slot: u64) {
self.eth2_config.spec.milliseconds_per_slot = milliseconds_per_slot;
}
/// Reads the subcommand and tries to update `self.eth2_config` based up on the `--spec` flag. /// Reads the subcommand and tries to update `self.eth2_config` based up on the `--spec` flag.
/// ///
/// Returns an error if the `--spec` flag is not present in the given `cli_args`. /// Returns an error if the `--spec` flag is not present in the given `cli_args`.

View File

@ -163,6 +163,16 @@ fn main() {
.takes_value(true), .takes_value(true),
) )
/*
* Eth1 Integration
*/
.arg(
Arg::with_name("eth1-server")
.long("eth1-server")
.value_name("SERVER")
.help("Specifies the server for a web3 connection to the Eth1 chain.")
.takes_value(true)
)
/* /*
* Database parameters. * Database parameters.
*/ */
@ -235,6 +245,13 @@ fn main() {
backup directory.") backup directory.")
.conflicts_with("random-datadir") .conflicts_with("random-datadir")
) )
.arg(
Arg::with_name("slot-time")
.long("slot-time")
.short("t")
.value_name("MILLISECONDS")
.help("Defines the slot time when creating a new testnet.")
)
/* /*
* `boostrap` * `boostrap`
* *
@ -246,6 +263,7 @@ fn main() {
.arg(Arg::with_name("server") .arg(Arg::with_name("server")
.value_name("HTTP_SERVER") .value_name("HTTP_SERVER")
.required(true) .required(true)
.default_value("http://localhost:5052")
.help("A HTTP server, with a http:// prefix")) .help("A HTTP server, with a http:// prefix"))
.arg(Arg::with_name("libp2p-port") .arg(Arg::with_name("libp2p-port")
.short("p") .short("p")
@ -298,9 +316,14 @@ fn main() {
* *
* Start a new node, using a genesis state loaded from a YAML file * Start a new node, using a genesis state loaded from a YAML file
*/ */
.subcommand(SubCommand::with_name("yaml") .subcommand(SubCommand::with_name("file")
.about("Creates a new datadir where the genesis state is read from YAML. Will fail to parse \ .about("Creates a new datadir where the genesis state is read from YAML. May fail to parse \
a YAML state that was generated to a different spec than that specified by --spec.") a file that was generated to a different spec than that specified by --spec.")
.arg(Arg::with_name("format")
.value_name("FORMAT")
.required(true)
.possible_values(&["yaml", "ssz", "json"])
.help("The encoding of the state in the file."))
.arg(Arg::with_name("file") .arg(Arg::with_name("file")
.value_name("YAML_FILE") .value_name("YAML_FILE")
.required(true) .required(true)

View File

@ -1,4 +1,7 @@
use client::{error, notifier, BeaconChainTypes, Client, ClientConfig, ClientType, Eth2Config}; use client::{
error, notifier, BeaconChainTypes, Client, ClientConfig, ClientType, Eth1BackendMethod,
Eth2Config,
};
use futures::sync::oneshot; use futures::sync::oneshot;
use futures::Future; use futures::Future;
use slog::{error, info}; use slog::{error, info};
@ -47,55 +50,30 @@ pub fn run_beacon_node(
"spec_constants" => &spec_constants, "spec_constants" => &spec_constants,
); );
macro_rules! run_client {
($store: ty, $eth_spec: ty) => {
run::<ClientType<$store, $eth_spec>>(
&db_path,
client_config,
eth2_config,
executor,
runtime,
log,
)
};
}
if let Eth1BackendMethod::Web3 { .. } = client_config.eth1_backend_method {
return Err("Starting from web3 backend is not supported for interop.".into());
}
match (db_type.as_str(), spec_constants.as_str()) { match (db_type.as_str(), spec_constants.as_str()) {
("disk", "minimal") => run::<ClientType<DiskStore, MinimalEthSpec>>( ("disk", "minimal") => run_client!(DiskStore, MinimalEthSpec),
&db_path, ("disk", "mainnet") => run_client!(DiskStore, MainnetEthSpec),
client_config, ("disk", "interop") => run_client!(DiskStore, InteropEthSpec),
eth2_config, ("memory", "minimal") => run_client!(MemoryStore, MinimalEthSpec),
executor, ("memory", "mainnet") => run_client!(MemoryStore, MainnetEthSpec),
runtime, ("memory", "interop") => run_client!(MemoryStore, InteropEthSpec),
log,
),
("memory", "minimal") => run::<ClientType<MemoryStore, MinimalEthSpec>>(
&db_path,
client_config,
eth2_config,
executor,
runtime,
log,
),
("disk", "mainnet") => run::<ClientType<DiskStore, MainnetEthSpec>>(
&db_path,
client_config,
eth2_config,
executor,
runtime,
log,
),
("memory", "mainnet") => run::<ClientType<MemoryStore, MainnetEthSpec>>(
&db_path,
client_config,
eth2_config,
executor,
runtime,
log,
),
("disk", "interop") => run::<ClientType<DiskStore, InteropEthSpec>>(
&db_path,
client_config,
eth2_config,
executor,
runtime,
log,
),
("memory", "interop") => run::<ClientType<MemoryStore, InteropEthSpec>>(
&db_path,
client_config,
eth2_config,
executor,
runtime,
log,
),
(db_type, spec) => { (db_type, spec) => {
error!(log, "Unknown runtime configuration"; "spec_constants" => spec, "db_type" => db_type); error!(log, "Unknown runtime configuration"; "spec_constants" => spec, "db_type" => db_type);
Err("Unknown specification and/or db_type.".into()) Err("Unknown specification and/or db_type.".into())

View File

@ -2,7 +2,9 @@
* [Introduction](./intro.md) * [Introduction](./intro.md)
* [Development Environment](./setup.md) * [Development Environment](./setup.md)
* [Testnets](./testnets.md)
* [Simple Local Testnet](./simple-testnet.md) * [Simple Local Testnet](./simple-testnet.md)
* [Interop](./interop.md) * [Interop](./interop.md)
* [Interop Tips & Tricks](./interop-tips.md) * [Environment](./interop-environment.md)
* [CLI Overview](./interop-cli.md)
* [Scenarios](./interop-scenarios.md)
* [Cheat-sheet](./interop-cheat-sheet.md)

View File

@ -0,0 +1,139 @@
# Interop Cheat-sheet
This document contains a list of tips and tricks that may be useful during
interop testing.
- When starting a beacon node:
- [Specify a boot node by multiaddr](#boot-node-multiaddr)
- [Specify a boot node by ENR](#boot-node-enr)
- [Avoid port clashes when starting multiple nodes](#port-bump)
- [Specify a custom slot time](#slot-time)
- Using the beacon node HTTP API:
- [Curl a node's ENR](#http-enr)
- [Curl a node's connected peers](#http-peer-ids)
- [Curl a node's local peer id](#http-peer-id)
- [Curl a node's listening multiaddrs](#http-listen-addresses)
- [Curl a node's beacon chain head](#http-head)
- [Curl a node's finalized checkpoint](#http-finalized)
## Category: CLI
The `--help` command provides detail on the CLI interface. Here are some
interop-specific CLI commands.
<a name="boot-node-multiaddr"></a>
### Specify a boot node by multiaddr
You can specify a static list of multiaddrs when booting Lighthouse using
the `--libp2p-addresses` command.
#### Example:
```
$ ./beacon_node --libp2p-addresses /ip4/192.168.0.1/tcp/9000
```
<a name="boot-node-enr"></a>
### Specify a boot node by ENR
You can specify a static list of Discv5 addresses when booting Lighthouse using
the `--boot-nodes` command.
#### Example:
```
$ ./beacon_node --boot-nodes -IW4QB2Hi8TPuEzQ41Cdf1r2AUU1FFVFDBJdJyOkWk2qXpZfFZQy2YnJIyoT_5fnbtrXUouoskmydZl4pIg90clIkYUDgmlwhH8AAAGDdGNwgiMog3VkcIIjKIlzZWNwMjU2azGhAjg0-DsTkQynhJCRnLLttBK1RS78lmUkLa-wgzAi-Ob5
```
<a name="port-bump"></a>
### Avoid port clashes when starting nodes
Starting a second Lighthouse node on the same machine will fail due to TCP/UDP
port collisions. Use the `-b` (`--port-bump`) flag to increase all listening
ports by some `n`.
#### Example:
Increase all ports by `10` (using multiples of `10` is recommended).
```
$ ./beacon_node -b 10
```
<a name="slot-time"></a>
### Start a testnet with a custom slot time
Lighthouse can run at quite low slot times when there are few validators (e.g.,
`500 ms` slot times should be fine for 8 validators).
#### Example
The `-t` (`--slot-time`) flag specifies the milliseconds per slot.
```
$ ./beacon_node testnet -t 500 recent 8
```
> Note: `bootstrap` loads the slot time via HTTP and therefore conflicts with
> this flag.
## Category: HTTP API
Examples assume there is a Lighthouse node exposing a HTTP API on
`localhost:5052`. Responses are JSON.
<a name="http-enr"></a>
### Get the node's ENR
```
$ curl localhost:5052/network/enr
"-IW4QFyf1VlY5pZs0xZuvKMRZ9_cdl9WMCDAAJXZiZiuGcfRYoU40VPrYDLQj5prneJIz3zcbTjHp9BbThc-yiymJO8HgmlwhH8AAAGDdGNwgiMog3VkcIIjKIlzZWNwMjU2azGhAjg0-DsTkQynhJCRnLLttBK1RS78lmUkLa-wgzAi-Ob5"%
```
<a name="http-peer-ids"></a>
### Get a list of connected peer ids
```
$ curl localhost:5052/network/peers
["QmeMFRTWfo3KbVG7dEBXGhyRMa29yfmnJBXW84rKuGEhuL"]%
```
<a name="http-peer-id"></a>
### Get the node's peer id
```
curl localhost:5052/network/peer_id
"QmRD1qs2AqNNRdBcGHUGpUGkpih5cmdL32mhh22Sy79xsJ"%
```
<a name="http-listen-addresses"></a>
### Get the list of listening libp2p addresses
Lists all the libp2p multiaddrs that the node is listening on.
```
curl localhost:5052/network/listen_addresses
["/ip4/127.0.0.1/tcp/9000","/ip4/192.168.1.121/tcp/9000","/ip4/172.17.0.1/tcp/9000","/ip4/172.42.0.1/tcp/9000","/ip6/::1/tcp/9000","/ip6/fdd3:c293:1bc::203/tcp/9000","/ip6/fdd3:c293:1bc:0:9aa9:b2ea:c610:44db/tcp/9000"]%
```
<a name="http-head"></a>
### Get the node's beacon chain head
```
curl localhost:5052/beacon/head
{"slot":0,"block_root":"0x827bf71805540aa13f6d8c7d18b41b287b2094a4d7a28cbb8deb061dbf5df4f5","state_root":"0x90a78d73294bc9c7519a64e1912161be0e823eb472012ff54204e15a4d717fa5"}%
```
<a name="http-finalized"></a>
### Get the node's finalized checkpoint
```
curl localhost:5052/beacon/latest_finalized_checkpoint
{"epoch":0,"root":"0x0000000000000000000000000000000000000000000000000000000000000000"}%
```

29
book/src/interop-cli.md Normal file
View File

@ -0,0 +1,29 @@
# Interop CLI Overview
The Lighthouse CLI has two primary tasks:
- **Resuming** an existing database with `$ ./beacon_node`.
- **Creating** a new testnet database using `$ ./beacon_node testnet`.
_See [Scenarios](./interop-scenarios.md) for methods we've anticipated will be
used interop._
## Creating a new database
There are several methods for creating a new beacon node database:
- `quick`: using the `(validator_client, genesis_time)` tuple.
- `recent`: as above but `genesis_time` is set to the start of some recent time
window.
- `file`: loads the genesis file from disk in one of multiple formats.
- `bootstrap`: a Lighthouse-specific method where we connect to a running node
and download it's specification and genesis state via the HTTP API.
See `$ ./beacon_node testnet --help` for more detail.
## Resuming from an existing database
Once a database has been created, it can be resumed by running `$ ./beacon_node`.
Presently, this command will fail if no existing database is found. You must
use the `$ ./beacon_node testnet` command to create a new database.

View File

@ -0,0 +1,30 @@
# Interop Environment
All that is required for inter-op is a built and tested [development
environment](./setup.md).
## Repositories
You will only require the [sigp/lighthouse](http://github.com/sigp/lighthouse)
library.
To allow for faster build/test iterations we will use the
[`interop`](https://github.com/sigp/lighthouse/tree/interop) branch of
[sigp/lighthouse](https://github.com/sigp/lighthouse/tree/interop) for
September 2019 interop. **Please use ensure you `git checkout interop` after
cloning the repo.**
## File System
When lighthouse boots, it will create the following
directories:
- `~/.lighthouse`: database and configuration for the beacon node.
- `~/.lighthouse-validator`: database and configuration for the validator
client.
After building the binaries with `cargo build --release --all`, there will be a
`target/release` directory in the root of the Lighthouse repository. This is
where the `beacon_node` and `validator_client` binaries are located.
You do not need to create any of these directories manually.

View File

@ -0,0 +1,98 @@
# Interop Scenarios
Here we demonstrate some expected interop scenarios.
All scenarios assume a working [development environment](./setup.md) and
commands are based in the `target/release` directory (this is the build dir for
`cargo`).
Additional functions can be found in the [interop
cheat-sheet](./interop-cheat-sheet.md).
### Table of contents
- [Starting from a`validator_count, genesis_time` tuple](#quick-start)
- [Starting a node from a genesis state file](#state-file)
- [Starting a validator client](#val-client)
- [Exporting a genesis state file](#export) from a running Lighthouse
node
<a name="quick-start"></a>
### Start beacon node given a validator count and genesis_time
To start a brand-new beacon node (with no history) use:
```
$ ./beacon_node testnet -f quick 8 1567222226
```
> Notes:
>
> - This method conforms the ["Quick-start
genesis"](https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start#quick-start-genesis)
method in the `ethereum/eth2.0-pm` repository.
> - The `-f` flag ignores any existing database or configuration, backing them
> up before re-initializing.
> - `8` is the validator count and `1567222226` is the genesis time.
> - See `$ ./beacon_node testnet quick --help` for more configuration options.
<a name="state-file"></a>
### Start Beacon Node given a genesis state file
A genesis state can be read from file using the `testnet file` subcommand.
There are three supported formats:
- `ssz` (default)
- `json`
- `yaml`
Start a new node using `/tmp/genesis.ssz` as the genesis state:
```
$ ./beacon_node testnet --spec minimal -f file ssz /tmp/genesis.ssz
```
> Notes:
>
> - The `-f` flag ignores any existing database or configuration, backing them
> up before re-initializing.
> - See `$ ./beacon_node testnet file --help` for more configuration options.
> - The `--spec` flag is required to allow SSZ parsing of fixed-length lists.
<a name="val-client"></a>
### Start an auto-configured validator client
To start a brand-new validator client (with no history) use:
```
$ ./validator_client testnet -b insecure 0 8
```
> Notes:
>
> - The `-b` flag means the validator client will "bootstrap" specs and config
> from the beacon node.
> - The `insecure` command dictates that the [interop keypairs](https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start#pubkeyprivkey-generation)
> will be used.
> - The `0 8` indicates that this validator client should manage 8 validators,
> starting at validator 0 (the first deposited validator).
> - The validator client will try to connect to the beacon node at `localhost`.
> See `--help` to configure that address and other features.
> - The validator client will operate very unsafely in `testnet` mode, happily
> swapping between chains and creating double-votes.
<a name="export"></a>
### Exporting a genesis file
Genesis states can downloaded from a running Lighthouse node via the HTTP API. Three content-types are supported:
- `application/json`
- `application/yaml`
- `application/ssz`
Using `curl`, a genesis state can be downloaded to `/tmp/genesis.ssz`:
```
$ curl --header "Content-Type: application/ssz" "localhost:5052/beacon/state/genesis" -o /tmp/genesis.ssz
```

View File

@ -1,104 +1 @@
# Interop Tips & Tricks # Interop Tips & Tricks
This document contains a list of tips and tricks that may be useful during
interop testing.
## Command-line Interface
The `--help` command provides detail on the CLI interface. Here are some
interop-specific CLI commands.
### Specify a boot node by multiaddr
You can specify a static list of multiaddrs when booting Lighthouse using
the `--libp2p-addresses` command.
#### Example:
Runs an 8 validator quick-start chain, peering with `/ip4/192.168.0.1/tcp/9000` on boot.
```
$ ./beacon_node --libp2p-addresses /ip4/192.168.0.1/tcp/9000 testnet -f quick 8 1567222226
```
### Specify a boot node by ENR
You can specify a static list of Discv5 addresses when booting Lighthouse using
the `--boot-nodes` command.
#### Example:
Runs an 8 validator quick-start chain, peering with `-IW4QB2...` on boot.
```
$ ./beacon_node --boot-nodes -IW4QB2Hi8TPuEzQ41Cdf1r2AUU1FFVFDBJdJyOkWk2qXpZfFZQy2YnJIyoT_5fnbtrXUouoskmydZl4pIg90clIkYUDgmlwhH8AAAGDdGNwgiMog3VkcIIjKIlzZWNwMjU2azGhAjg0-DsTkQynhJCRnLLttBK1RS78lmUkLa-wgzAi-Ob5 testnet -f quick 8 1567222226
```
### Avoid port clashes when starting nodes
Starting a second Lighthouse node on the same machine will fail due to TCP/UDP
port collisions. Use the `-b` (`--port-bump`) flag to increase all listening
ports by some `n`.
#### Example:
Increase all ports by `10` (using multiples of `10` is recommended).
```
$ ./beacon_node -b 10 testnet -f quick 8 1567222226
```
## HTTP API
Examples assume there is a Lighthouse node exposing a HTTP API on
`localhost:5052`. Responses are JSON.
### Get the node's ENR
```
$ curl localhost:5052/network/enr
"-IW4QFyf1VlY5pZs0xZuvKMRZ9_cdl9WMCDAAJXZiZiuGcfRYoU40VPrYDLQj5prneJIz3zcbTjHp9BbThc-yiymJO8HgmlwhH8AAAGDdGNwgiMog3VkcIIjKIlzZWNwMjU2azGhAjg0-DsTkQynhJCRnLLttBK1RS78lmUkLa-wgzAi-Ob5"%
```
### Get a list of connected peer ids
```
$ curl localhost:5052/network/peers
["QmeMFRTWfo3KbVG7dEBXGhyRMa29yfmnJBXW84rKuGEhuL"]%
```
### Get the node's peer id
```
curl localhost:5052/network/peer_id
"QmRD1qs2AqNNRdBcGHUGpUGkpih5cmdL32mhh22Sy79xsJ"%
```
### Get the list of listening libp2p addresses
Lists all the libp2p multiaddrs that the node is listening on.
```
curl localhost:5052/network/listen_addresses
["/ip4/127.0.0.1/tcp/9000","/ip4/192.168.1.121/tcp/9000","/ip4/172.17.0.1/tcp/9000","/ip4/172.42.0.1/tcp/9000","/ip6/::1/tcp/9000","/ip6/fdd3:c293:1bc::203/tcp/9000","/ip6/fdd3:c293:1bc:0:9aa9:b2ea:c610:44db/tcp/9000"]%
```
### Get the node's beacon chain head
```
curl localhost:5052/beacon/head
{"slot":0,"block_root":"0x827bf71805540aa13f6d8c7d18b41b287b2094a4d7a28cbb8deb061dbf5df4f5","state_root":"0x90a78d73294bc9c7519a64e1912161be0e823eb472012ff54204e15a4d717fa5"}%
```
### Get the node's finalized checkpoint
```
curl localhost:5052/beacon/latest_finalized_checkpoint
{"epoch":0,"root":"0x0000000000000000000000000000000000000000000000000000000000000000"}%
```

View File

@ -3,84 +3,9 @@
This guide is intended for other Ethereum 2.0 client developers performing This guide is intended for other Ethereum 2.0 client developers performing
inter-operability testing with Lighthouse. inter-operability testing with Lighthouse.
To allow for faster iteration cycles without the "merging to master" overhead, ## Chapters
we will use the [`interop`](https://github.com/sigp/lighthouse/tree/interop)
branch of [sigp/lighthouse](https://github.com/sigp/lighthouse/tree/interop)
for September 2019 interop. **Please use ensure you `git checkout interop`
after cloning the repo.**
## Environment - Read about the required [development environment](./interop-environment.md).
- Get an [overview](./interop-cli.md) of the Lighthouse CLI.
All that is required for inter-op is a built and tested [development - See how we expect to handle some [interop scenarios](./interop-scenarios.md).
environment](setup). When lighthouse boots, it will create the following - See the [interop cheat-sheet](./interop-cheat-sheet.md) for useful CLI tips.
directories:
- `~/.lighthouse`: database and configuration for the beacon node.
- `~/.lighthouse-validator`: database and configuration for the validator
client.
After building the binaries with `cargo build --release --all`, there will be a
`target/release` directory in the root of the Lighthouse repository. This is
where the `beacon_node` and `validator_client` binaries are located.
## Interop Procedure
The following scenarios are documented:
- [Starting a "quick-start" beacon node](#quick-start-beacon-node) from a
`(validator_count, genesis)` tuple.
- [Starting a validator client](#validator-client) with `n` interop keypairs.
- [Starting a node from a genesis state file](#starting-from-a-genesis-file).
- [Exporting a genesis state file](#exporting-a-genesis-file) from a running Lighthouse
node.
First, setup a Lighthouse development environment and navigate to the
`target/release` directory (this is where the binaries are located).
#### Quick-start Beacon Node
To start the node (each time creating a fresh database and configuration in
`~/.lighthouse`), use:
```
$ ./beacon_node testnet -f quick 8 1567222226
```
> Notes:
>
> - This method conforms the ["Quick-start
genesis"](https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start#quick-start-genesis)
method in the `ethereum/eth2.0-pm` repository.
> - The `-f` flag ignores any existing database or configuration, backing them
> up before re-initializing.
> - `8` is the validator count and `1567222226` is the genesis time.
> - See `$ ./beacon_node testnet quick --help` for more configuration options.
#### Validator Client
Start the validator client with:
```
$ ./validator_client testnet -b insecure 0 8
```
> Notes:
>
> - The `-b` flag means the validator client will "bootstrap" specs and config
> from the beacon node.
> - The `insecure` command dictates that the [interop
> keypairs](https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start#pubkeyprivkey-generation)
> will be used.
> - The `0 8` indicates that this validator client should manage 8 validators,
> starting at validator 0 (the first deposited validator).
> - The validator client will try to connect to the beacon node at `localhost`.
> See `--help` to configure that address and other features.
> - The validator client will operate very unsafely in `testnet` mode, happily
> swapping between chains and creating double-votes.
#### Starting from a genesis file
**TODO**
#### Exporting a genesis file
**TODO**

View File

@ -17,31 +17,11 @@ Foundation, Consensys and other individuals and organisations.
## Developer Resources ## Developer Resources
Documentation is provided for **researchers and developers** working on Documentation is presently targeted at **researchers and developers**. It
Ethereum 2.0 and assumes prior knowledge on the topic. assumes significant prior knowledge of Ethereum 2.0.
- Get started with [development environment setup](setup.html). Topics:
- [Run a simple testnet](simple-testnet.html) in Only Three CLI Commands™.
- Read about our interop workflow.
- API?
## Release - Get started with [development environment setup](./setup.md).
- See the [interop docs](./interop.md).
Ethereum 2.0 is not fully specified or implemented and as such, Lighthouse is - [Run a simple testnet](./simple-testnet.md) in Only Three CLI Commands™.
still **under development**.
We are on-track to provide a public, multi-client testnet in late-2019 and an
initial production-grade blockchain in 2020.
## Features
Lighthouse has been in development since mid-2018 and has an extensive feature
set:
- Libp2p networking stack, featuring Discovery v5.
- Optimized `BeaconChain` state machine, up-to-date and
passing all tests.
- RESTful HTTP API.
- Documented and feature-rich CLI interface.
- Capable of running small, local testnets with 250ms slot times.
- Detailed metrics exposed in the Prometheus format.

View File

@ -66,7 +66,7 @@ In a new terminal terminal, run:
``` ```
$ ./beacon_node -b 10 testnet -r bootstrap http://localhost:5052 $ ./beacon_node -b 10 testnet -r bootstrap
``` ```
> Notes: > Notes:
@ -74,7 +74,8 @@ $ ./beacon_node -b 10 testnet -r bootstrap http://localhost:5052
> - The `-b` (or `--port-bump`) increases all the listening TCP/UDP ports of > - The `-b` (or `--port-bump`) increases all the listening TCP/UDP ports of
> the new node to `10` higher. Your first node's HTTP server was at TCP > the new node to `10` higher. Your first node's HTTP server was at TCP
> `5052` but this one will be at `5062`. > `5052` but this one will be at `5062`.
> - The `-r` flag creates a new data directory in your home with a random > - The `-r` flag creates a new data directory with a random string appended
> string appended, to avoid conflicting with any other running node. > (avoids data directory collisions between nodes).
> - The HTTP address is the API of the first node. The new node will download > - The default bootstrap HTTP address is `http://localhost:5052`. The new node
> configuration via HTTP before starting sync via libp2p. > will download configuration via HTTP before starting sync via libp2p.
> - See `$ ./beacon_node testnet bootstrap --help` for more configuration.

View File

@ -4,7 +4,8 @@
extern crate lazy_static; extern crate lazy_static;
use beacon_chain::test_utils::{ use beacon_chain::test_utils::{
AttestationStrategy, BeaconChainHarness as BaseBeaconChainHarness, BlockStrategy, generate_deterministic_keypairs, AttestationStrategy,
BeaconChainHarness as BaseBeaconChainHarness, BlockStrategy,
}; };
use lmd_ghost::{LmdGhost, ThreadSafeReducedTree as BaseThreadSafeReducedTree}; use lmd_ghost::{LmdGhost, ThreadSafeReducedTree as BaseThreadSafeReducedTree};
use rand::{prelude::*, rngs::StdRng}; use rand::{prelude::*, rngs::StdRng};
@ -51,7 +52,7 @@ struct ForkedHarness {
impl ForkedHarness { impl ForkedHarness {
/// A new standard instance of with constant parameters. /// A new standard instance of with constant parameters.
pub fn new() -> Self { pub fn new() -> Self {
let harness = BeaconChainHarness::new(VALIDATOR_COUNT); let harness = BeaconChainHarness::new(generate_deterministic_keypairs(VALIDATOR_COUNT));
// Move past the zero slot. // Move past the zero slot.
harness.advance_slot(); harness.advance_slot();

View File

@ -31,3 +31,4 @@ tree_hash_derive = "0.2"
[dev-dependencies] [dev-dependencies]
env_logger = "0.6.0" env_logger = "0.6.0"
serde_json = "^1.0"

View File

@ -120,6 +120,13 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq {
fn epochs_per_historical_vector() -> usize { fn epochs_per_historical_vector() -> usize {
Self::EpochsPerHistoricalVector::to_usize() Self::EpochsPerHistoricalVector::to_usize()
} }
/// Returns the `SLOTS_PER_ETH1_VOTING_PERIOD` constant for this specification.
///
/// Spec v0.8.1
fn slots_per_eth1_voting_period() -> usize {
Self::EpochsPerHistoricalVector::to_usize()
}
} }
/// Macro to inherit some type values from another EthSpec. /// Macro to inherit some type values from another EthSpec.

View File

@ -9,7 +9,7 @@ fn default_values() {
let cache = CommitteeCache::default(); let cache = CommitteeCache::default();
assert_eq!(cache.is_initialized_at(Epoch::new(0)), false); assert_eq!(cache.is_initialized_at(Epoch::new(0)), false);
assert_eq!(cache.active_validator_indices(), &[]); assert!(&cache.active_validator_indices().is_empty());
assert_eq!(cache.get_crosslink_committee_for_shard(0), None); assert_eq!(cache.get_crosslink_committee_for_shard(0), None);
assert_eq!(cache.get_attestation_duties(0), None); assert_eq!(cache.get_attestation_duties(0), None);
assert_eq!(cache.active_validator_count(), 0); assert_eq!(cache.active_validator_count(), 0);

View File

@ -39,15 +39,15 @@ impl TestingProposerSlashingBuilder {
..header_1.clone() ..header_1.clone()
}; };
let epoch = slot.epoch(T::slots_per_epoch());
header_1.signature = { header_1.signature = {
let message = header_1.signed_root(); let message = header_1.signed_root();
let epoch = slot.epoch(T::slots_per_epoch());
signer(proposer_index, &message[..], epoch, Domain::BeaconProposer) signer(proposer_index, &message[..], epoch, Domain::BeaconProposer)
}; };
header_2.signature = { header_2.signature = {
let message = header_2.signed_root(); let message = header_2.signed_root();
let epoch = slot.epoch(T::slots_per_epoch());
signer(proposer_index, &message[..], epoch, Domain::BeaconProposer) signer(proposer_index, &message[..], epoch, Domain::BeaconProposer)
}; };

View File

@ -13,3 +13,4 @@ reqwest = "0.9"
url = "1.2" url = "1.2"
types = { path = "../../types" } types = { path = "../../types" }
serde = "1.0" serde = "1.0"
slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_trace"] }

View File

@ -5,11 +5,16 @@ use eth2_libp2p::{
}; };
use reqwest::{Error as HttpError, Url}; use reqwest::{Error as HttpError, Url};
use serde::Deserialize; use serde::Deserialize;
use slog::{error, Logger};
use std::borrow::Cow; use std::borrow::Cow;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::time::Duration;
use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Hash256, Slot}; use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Hash256, Slot};
use url::Host; use url::Host;
pub const RETRY_SLEEP_MILLIS: u64 = 100;
pub const RETRY_WARN_INTERVAL: u64 = 30;
#[derive(Debug)] #[derive(Debug)]
enum Error { enum Error {
InvalidUrl, InvalidUrl,
@ -31,11 +36,35 @@ pub struct Bootstrapper {
} }
impl Bootstrapper { impl Bootstrapper {
/// Parses the given `server` as a URL, instantiating `Self`. /// Parses the given `server` as a URL, instantiating `Self` and blocking until a connection
pub fn from_server_string(server: String) -> Result<Self, String> { /// can be made with the server.
Ok(Self { ///
/// Never times out.
pub fn connect(server: String, log: &Logger) -> Result<Self, String> {
let bootstrapper = Self {
url: Url::parse(&server).map_err(|e| format!("Invalid bootstrap server url: {}", e))?, url: Url::parse(&server).map_err(|e| format!("Invalid bootstrap server url: {}", e))?,
}) };
let mut retry_count = 0;
loop {
match bootstrapper.enr() {
Ok(_) => break,
Err(_) => {
if retry_count % RETRY_WARN_INTERVAL == 0 {
error!(
log,
"Failed to contact bootstrap server";
"retry_count" => retry_count,
"retry_delay_millis" => RETRY_SLEEP_MILLIS,
);
}
retry_count += 1;
std::thread::sleep(Duration::from_millis(RETRY_SLEEP_MILLIS));
}
}
}
Ok(bootstrapper)
} }
/// Build a multiaddr using the HTTP server URL that is not guaranteed to be correct. /// Build a multiaddr using the HTTP server URL that is not guaranteed to be correct.

View File

@ -247,10 +247,13 @@ fn process_testnet_subcommand(
) -> Result<(ClientConfig, Eth2Config)> { ) -> Result<(ClientConfig, Eth2Config)> {
let eth2_config = if cli_args.is_present("bootstrap") { let eth2_config = if cli_args.is_present("bootstrap") {
info!(log, "Connecting to bootstrap server"); info!(log, "Connecting to bootstrap server");
let bootstrapper = Bootstrapper::from_server_string(format!( let bootstrapper = Bootstrapper::connect(
format!(
"http://{}:{}", "http://{}:{}",
client_config.server, client_config.server_http_port client_config.server, client_config.server_http_port
))?; ),
&log,
)?;
let eth2_config = bootstrapper.eth2_config()?; let eth2_config = bootstrapper.eth2_config()?;