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_derive = "1.0"
serde_yaml = "0.8"
serde_json = "^1.0"
slog = { version = "^2.2.3" , features = ["max_level_trace"] }
sloggers = { version = "^0.3" }
slot_clock = { path = "../../eth2/utils/slot_clock" }

View File

@ -1,5 +1,6 @@
use crate::checkpoint::CheckPoint;
use crate::errors::{BeaconChainError as Error, BlockProductionError};
use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend};
use crate::fork_choice::{Error as ForkChoiceError, ForkChoice};
use crate::iter::{ReverseBlockRootIterator, ReverseStateRootIterator};
use crate::metrics;
@ -45,11 +46,14 @@ pub enum BlockProcessingOutcome {
block_slot: Slot,
},
/// 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.
GenesisBlock,
/// 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.
BlockIsAlreadyKnown,
/// The block could not be applied to the state, it is invalid.
@ -76,25 +80,31 @@ pub enum AttestationProcessingOutcome {
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>>),
Owned(BeaconState<T>),
}
impl<'a, T: EthSpec> AsRef<BeaconState<T>> for StateCow<'a, T> {
fn as_ref(&self) -> &BeaconState<T> {
impl<'a, T: EthSpec> BeaconStateCow<'a, T> {
pub fn maybe_as_mut_ref(&mut self) -> Option<&mut BeaconState<T>> {
match self {
StateCow::Borrowed(checkpoint) => &checkpoint.beacon_state,
StateCow::Owned(state) => &state,
BeaconStateCow::Borrowed(_) => None,
BeaconStateCow::Owned(ref mut state) => Some(state),
}
}
}
impl<'a, T: EthSpec> StateCow<'a, T> {
pub fn as_mut_ref(&mut self) -> Option<&mut BeaconState<T>> {
impl<'a, T: EthSpec> std::ops::Deref for BeaconStateCow<'a, T> {
type Target = BeaconState<T>;
fn deref(&self) -> &BeaconState<T> {
match self {
StateCow::Borrowed(_) => None,
StateCow::Owned(ref mut state) => Some(state),
BeaconStateCow::Borrowed(checkpoint) => &checkpoint.beacon_state,
BeaconStateCow::Owned(state) => &state,
}
}
}
@ -103,6 +113,7 @@ pub trait BeaconChainTypes: Send + Sync + 'static {
type Store: store::Store;
type SlotClock: slot_clock::SlotClock;
type LmdGhost: LmdGhost<Self::Store, Self::EthSpec>;
type Eth1Chain: Eth1ChainBackend<Self::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
/// inclusion in a block.
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.
canonical_head: RwLock<CheckPoint<T::EthSpec>>,
/// The root of the genesis block.
@ -132,6 +145,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Instantiate a new Beacon Chain, from genesis.
pub fn from_genesis(
store: Arc<T::Store>,
eth1_backend: T::Eth1Chain,
mut genesis_state: BeaconState<T::EthSpec>,
mut genesis_block: BeaconBlock<T::EthSpec>,
spec: ChainSpec,
@ -176,6 +190,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
spec,
slot_clock,
op_pool: OperationPool::new(),
eth1_chain: Eth1Chain::new(eth1_backend),
canonical_head,
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`.
pub fn from_store(
store: Arc<T::Store>,
eth1_backend: T::Eth1Chain,
spec: ChainSpec,
log: Logger,
) -> Result<Option<BeaconChain<T>>, Error> {
@ -223,6 +239,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
slot_clock,
fork_choice: ForkChoice::new(store.clone(), last_finalized_block, last_finalized_root),
op_pool,
eth1_chain: Eth1Chain::new(eth1_backend),
canonical_head: RwLock::new(p.canonical_head),
genesis_block_root: p.genesis_block_root,
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
/// 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;
if slot == head_state.slot {
Ok(StateCow::Borrowed(self.head()))
Ok(BeaconStateCow::Borrowed(self.head()))
} else if slot > head_state.slot {
let head_state_slot = head_state.slot;
let mut state = head_state.clone();
@ -397,7 +414,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
};
}
Ok(StateCow::Owned(state))
Ok(BeaconStateCow::Owned(state))
} else {
let state_root = self
.rev_iter_state_roots()
@ -405,7 +422,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.map(|(root, _slot)| root)
.ok_or_else(|| Error::NoStateForSlot(slot))?;
Ok(StateCow::Owned(
Ok(BeaconStateCow::Owned(
self.store
.get(&state_root)?
.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
/// 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()?)
}
@ -473,25 +490,24 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let head_state = &self.head().beacon_state;
let mut state = if epoch(slot) == epoch(head_state.slot) {
StateCow::Borrowed(self.head())
BeaconStateCow::Borrowed(self.head())
} else {
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)?;
}
if epoch(state.as_ref().slot) != epoch(slot) {
if epoch(state.slot) != epoch(slot) {
return Err(Error::InvariantViolated(format!(
"Epochs in consistent in proposer lookup: state: {}, requested: {}",
epoch(state.as_ref().slot),
epoch(state.slot),
epoch(slot)
)));
}
state
.as_ref()
.get_beacon_proposer_index(slot, RelativeEpoch::Current, &self.spec)
.map_err(Into::into)
}
@ -509,26 +525,25 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let head_state = &self.head().beacon_state;
let mut state = if epoch == as_epoch(head_state.slot) {
StateCow::Borrowed(self.head())
BeaconStateCow::Borrowed(self.head())
} else {
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)?;
}
if as_epoch(state.as_ref().slot) != epoch {
if as_epoch(state.slot) != epoch {
return Err(Error::InvariantViolated(format!(
"Epochs in consistent in attestation duties lookup: state: {}, requested: {}",
as_epoch(state.as_ref().slot),
as_epoch(state.slot),
epoch
)));
}
if let Some(attestation_duty) = state
.as_ref()
.get_attestation_duties(validator_index, RelativeEpoch::Current)?
if let Some(attestation_duty) =
state.get_attestation_duties(validator_index, RelativeEpoch::Current)?
{
Ok(Some((attestation_duty.slot, attestation_duty.shard)))
} else {
@ -549,12 +564,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let head_block_root = self.head().beacon_block_root;
let head_block_slot = self.head().beacon_block.slot;
self.produce_attestation_data_for_block(
shard,
head_block_root,
head_block_slot,
state.as_ref(),
)
self.produce_attestation_data_for_block(shard, head_block_root, head_block_slot, &*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() {
Ok(state) => self
.op_pool
.insert_voluntary_exit(exit, state.as_ref(), &self.spec),
.insert_voluntary_exit(exit, &*state, &self.spec),
Err(e) => {
error!(
&self.log,
@ -892,9 +902,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Accept some transfer and queue it for inclusion in an appropriate block.
pub fn process_transfer(&self, transfer: Transfer) -> Result<(), TransferValidationError> {
match self.state_now() {
Ok(state) => self
.op_pool
.insert_transfer(transfer, state.as_ref(), &self.spec),
Ok(state) => self.op_pool.insert_transfer(transfer, &*state, &self.spec),
Err(e) => {
error!(
&self.log,
@ -915,7 +923,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
match self.state_now() {
Ok(state) => {
self.op_pool
.insert_proposer_slashing(proposer_slashing, state.as_ref(), &self.spec)
.insert_proposer_slashing(proposer_slashing, &*state, &self.spec)
}
Err(e) => {
error!(
@ -937,7 +945,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
match self.state_now() {
Ok(state) => {
self.op_pool
.insert_attester_slashing(attester_slashing, state.as_ref(), &self.spec)
.insert_attester_slashing(attester_slashing, &*state, &self.spec)
}
Err(e) => {
error!(
@ -968,14 +976,17 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.epoch
.start_slot(T::EthSpec::slots_per_epoch());
if block.slot <= finalized_slot {
return Ok(BlockProcessingOutcome::FinalizedSlot);
}
if block.slot == 0 {
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 = block.canonical_root();
@ -1073,7 +1084,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let state_root = state.canonical_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);
@ -1158,7 +1172,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.state_at_slot(slot - 1)
.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`.
@ -1207,16 +1221,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
body: BeaconBlockBody {
randao_reveal,
// TODO: replace with real data.
eth1_data: Eth1Data {
deposit_count: state.eth1_data.deposit_count,
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
},
eth1_data: self.eth1_chain.eth1_data_for_block_production(&state)?,
graffiti,
proposer_slashings: proposer_slashings.into(),
attester_slashings: attester_slashings.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(),
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 rayon::prelude::*;
use slog::Logger;
use ssz::Encode;
use ssz::{Decode, Encode};
use state_processing::initialize_beacon_state_from_eth1;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::SystemTime;
use tree_hash::{SignedRoot, TreeHash};
use types::{
test_utils::generate_deterministic_keypairs, BeaconBlock, BeaconState, ChainSpec, Deposit,
DepositData, Domain, EthSpec, Fork, Hash256, PublicKey, Signature,
BeaconBlock, BeaconState, ChainSpec, Deposit, DepositData, Domain, EthSpec, Fork, Hash256,
Keypair, PublicKey, Signature,
};
enum BuildStrategy<T: BeaconChainTypes> {
@ -32,21 +33,21 @@ pub struct BeaconChainBuilder<T: BeaconChainTypes> {
impl<T: BeaconChainTypes> BeaconChainBuilder<T> {
pub fn recent_genesis(
validator_count: usize,
keypairs: &[Keypair],
minutes: u64,
spec: ChainSpec,
log: Logger,
) -> 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(
genesis_time: u64,
validator_count: usize,
keypairs: &[Keypair],
spec: ChainSpec,
log: Logger,
) -> 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))
}
@ -61,8 +62,32 @@ impl<T: BeaconChainTypes> BeaconChainBuilder<T> {
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> {
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))?;
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 {
BuildStrategy::LoadFromStore => BeaconChain::from_store(store, self.spec, self.log)
.map_err(|e| format!("Error loading BeaconChain from database: {:?}", e))?
.ok_or_else(|| format!("Unable to find exising BeaconChain in database."))?,
BuildStrategy::LoadFromStore => {
BeaconChain::from_store(store, eth1_backend, self.spec, self.log)
.map_err(|e| format!("Error loading BeaconChain from database: {:?}", e))?
.ok_or_else(|| format!("Unable to find exising BeaconChain in database."))?
}
BuildStrategy::FromGenesis {
genesis_block,
genesis_state,
} => BeaconChain::from_genesis(
store,
eth1_backend,
genesis_state.as_ref().clone(),
genesis_block.as_ref().clone(),
self.spec,
@ -135,12 +167,11 @@ fn genesis_block<T: EthSpec>(genesis_state: &BeaconState<T>, spec: &ChainSpec) -
/// Reference:
/// https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start
fn interop_genesis_state<T: EthSpec>(
validator_count: usize,
keypairs: &[Keypair],
genesis_time: u64,
spec: &ChainSpec,
) -> Result<BeaconState<T>, String> {
let keypairs = generate_deterministic_keypairs(validator_count);
let eth1_block_hash = Hash256::from_slice(&[42; 32]);
let eth1_block_hash = Hash256::from_slice(&[0x42; 32]);
let eth1_timestamp = 2_u64.pow(40);
let amount = spec.max_effective_balance;
@ -155,7 +186,7 @@ fn interop_genesis_state<T: EthSpec>(
.map(|keypair| {
let mut data = DepositData {
withdrawal_credentials: withdrawal_credentials(&keypair.pk),
pubkey: keypair.pk.into(),
pubkey: keypair.pk.clone().into(),
amount,
signature: Signature::empty_signature().into(),
};
@ -212,6 +243,9 @@ fn interop_genesis_state<T: EthSpec>(
state.genesis_time = genesis_time;
// Invalid all the caches after all the manual state surgery.
state.drop_all_caches();
Ok(state)
}
@ -237,7 +271,7 @@ fn recent_genesis_time(minutes: u64) -> u64 {
#[cfg(test)]
mod test {
use super::*;
use types::{EthSpec, MinimalEthSpec};
use types::{test_utils::generate_deterministic_keypairs, EthSpec, MinimalEthSpec};
type TestEthSpec = MinimalEthSpec;
@ -247,12 +281,14 @@ mod test {
let genesis_time = 42;
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");
assert_eq!(
state.eth1_data.block_hash,
Hash256::from_slice(&[42; 32]),
Hash256::from_slice(&[0x42; 32]),
"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 state_processing::per_block_processing::errors::AttestationValidationError;
use state_processing::BlockProcessingError;
@ -33,6 +34,7 @@ pub enum BeaconChainError {
MissingBeaconBlock(Hash256),
MissingBeaconState(Hash256),
SlotProcessingError(SlotProcessingError),
UnableToAdvanceState(String),
NoStateForAttestation {
beacon_block_root: Hash256,
},
@ -42,6 +44,7 @@ pub enum BeaconChainError {
}
easy_from_to!(SlotProcessingError, BeaconChainError);
easy_from_to!(AttestationValidationError, BeaconChainError);
#[derive(Debug, PartialEq)]
pub enum BlockProductionError {
@ -50,10 +53,11 @@ pub enum BlockProductionError {
UnableToProduceAtSlot(Slot),
SlotProcessingError(SlotProcessingError),
BlockProcessingError(BlockProcessingError),
Eth1ChainError(Eth1ChainError),
BeaconStateError(BeaconStateError),
}
easy_from_to!(BlockProcessingError, BlockProductionError);
easy_from_to!(BeaconStateError, 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 checkpoint;
mod errors;
mod eth1_chain;
mod fork_choice;
mod iter;
mod metrics;
@ -18,6 +19,7 @@ pub use self::beacon_chain::{
pub use self::checkpoint::CheckPoint;
pub use self::errors::{BeaconChainError, BlockProductionError};
pub use beacon_chain_builder::BeaconChainBuilder;
pub use eth1_chain::{Eth1ChainBackend, InteropEth1ChainBackend};
pub use lmd_ghost;
pub use metrics::scrape_for_metrics;
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 rayon::prelude::*;
use sloggers::{null::NullLoggerBuilder, Build};
use sloggers::{terminal::TerminalLoggerBuilder, types::Severity, Build};
use slot_clock::TestingSlotClock;
use state_processing::per_slot_processing;
use std::marker::PhantomData;
use std::sync::Arc;
use store::MemoryStore;
use store::Store;
use tree_hash::{SignedRoot, TreeHash};
use types::{
test_utils::TestingBeaconStateBuilder, AggregateSignature, Attestation,
AttestationDataAndCustodyBit, BeaconBlock, BeaconState, BitList, ChainSpec, Domain, EthSpec,
Hash256, Keypair, RelativeEpoch, SecretKey, Signature, Slot,
AggregateSignature, Attestation, AttestationDataAndCustodyBit, BeaconBlock, BeaconState,
BitList, ChainSpec, Domain, EthSpec, Hash256, Keypair, RelativeEpoch, SecretKey, Signature,
Slot,
};
pub use types::test_utils::generate_deterministic_keypairs;
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.
#[derive(Clone, Copy, Debug)]
pub enum BlockStrategy {
@ -60,6 +66,7 @@ where
type Store = MemoryStore;
type SlotClock = TestingSlotClock;
type LmdGhost = L;
type Eth1Chain = InteropEth1ChainBackend<E>;
type EthSpec = E;
}
@ -83,40 +90,21 @@ where
E: EthSpec,
{
/// Instantiate a new harness with `validator_count` initial validators.
pub fn new(validator_count: usize) -> 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 {
pub fn new(keypairs: Vec<Keypair>) -> Self {
let spec = E::default_spec();
let log = TerminalLoggerBuilder::new()
.level(Severity::Warning)
.build()
.expect("logger should build");
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 =
BeaconChain::from_genesis(store, genesis_state, genesis_block, spec.clone(), log)
.expect("Terminate if beacon chain generation fails");
BeaconChainBuilder::quick_start(HARNESS_GENESIS_TIME, &keypairs, spec.clone(), log)
.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 {
chain,
@ -156,7 +144,10 @@ where
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.
@ -194,21 +185,6 @@ where
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.
fn build_block(
&self,
@ -282,9 +258,14 @@ where
)
.into_iter()
.for_each(|attestation| {
self.chain
match self
.chain
.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]
extern crate lazy_static;
use beacon_chain::test_utils::{
AttestationStrategy, BeaconChainHarness, BlockStrategy, CommonTypes, PersistedBeaconChain,
BEACON_CHAIN_DB_KEY,
};
use beacon_chain::AttestationProcessingOutcome;
use beacon_chain::{
test_utils::{
AttestationStrategy, BeaconChainHarness, BlockStrategy, CommonTypes, PersistedBeaconChain,
BEACON_CHAIN_DB_KEY,
},
BlockProcessingOutcome,
};
use lmd_ghost::ThreadSafeReducedTree;
use rand::Rng;
use store::{MemoryStore, Store};
@ -25,7 +28,7 @@ lazy_static! {
type TestForkChoice = ThreadSafeReducedTree<MemoryStore, 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();
@ -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.
#[serde(skip)]
pub beacon_chain_start_method: BeaconChainStartMethod,
pub eth1_backend_method: Eth1BackendMethod,
pub network: network::NetworkConfig,
pub rpc: rpc::RPCConfig,
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.
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
/// finalized states and blocks.
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 {
fn default() -> Self {
Self {
@ -77,6 +98,7 @@ impl Default for Config {
rest_api: <_>::default(),
spec_constants: TESTNET_SPEC_CONSTANTS.into(),
beacon_chain_start_method: <_>::default(),
eth1_backend_method: <_>::default(),
}
}
}

View File

@ -6,8 +6,8 @@ pub mod error;
pub mod notifier;
use beacon_chain::{
lmd_ghost::ThreadSafeReducedTree, slot_clock::SystemTimeSlotClock, store::Store, BeaconChain,
BeaconChainBuilder,
lmd_ghost::ThreadSafeReducedTree, slot_clock::SystemTimeSlotClock, store::Store,
test_utils::generate_deterministic_keypairs, BeaconChain, BeaconChainBuilder,
};
use exit_future::Signal;
use futures::{future::Future, Stream};
@ -21,14 +21,14 @@ use tokio::runtime::TaskExecutor;
use tokio::timer::Interval;
use types::EthSpec;
pub use beacon_chain::BeaconChainTypes;
pub use config::{BeaconChainStartMethod, Config as ClientConfig};
pub use beacon_chain::{BeaconChainTypes, Eth1ChainBackend, InteropEth1ChainBackend};
pub use config::{BeaconChainStartMethod, Config as ClientConfig, Eth1BackendMethod};
pub use eth2_config::Eth2Config;
#[derive(Clone)]
pub struct ClientType<S: Store, E: EthSpec> {
_phantom_t: PhantomData<S>,
_phantom_u: PhantomData<E>,
_phantom_s: PhantomData<S>,
_phantom_e: PhantomData<E>,
}
impl<S, E> BeaconChainTypes for ClientType<S, E>
@ -39,6 +39,7 @@ where
type Store = S;
type SlotClock = SystemTimeSlotClock;
type LmdGhost = ThreadSafeReducedTree<S, E>;
type Eth1Chain = InteropEth1ChainBackend<E>;
type EthSpec = E;
}
@ -105,7 +106,7 @@ where
"method" => "recent"
);
BeaconChainBuilder::recent_genesis(
*validator_count,
&generate_deterministic_keypairs(*validator_count),
*minutes,
spec.clone(),
log.clone(),
@ -124,7 +125,7 @@ where
);
BeaconChainBuilder::quick_start(
*genesis_time,
*validator_count,
&generate_deterministic_keypairs(*validator_count),
spec.clone(),
log.clone(),
)?
@ -138,6 +139,24 @@ where
);
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 } => {
info!(
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(
beacon_chain_builder
.build(store)
.build(store, eth1_backend)
.map_err(error::Error::from)?,
);

View File

@ -914,7 +914,7 @@ fn process_blocks<T: BeaconChainTypes>(
BlockProcessingOutcome::ParentUnknown { parent } => {
// blocks should be sequential and all parents should exist
trace!(
log, "ParentBlockUnknown";
log, "Parent block is unknown";
"parent_root" => format!("{}", parent),
"baby_block_slot" => block.slot,
);
@ -923,34 +923,53 @@ fn process_blocks<T: BeaconChainTypes>(
block.slot
));
}
BlockProcessingOutcome::FutureSlot {
present_slot,
block_slot,
} => {
if present_slot + FUTURE_SLOT_TOLERANCE >= block_slot {
// The block is too far in the future, drop it.
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 {
present_slot,
block_slot,
} => {
if present_slot + FUTURE_SLOT_TOLERANCE >= block_slot {
// The block is too far in the future, drop it.
trace!(
self.log, "Block is ahead of our slot clock";
"msg" => "block for future slot rejected, check your time",
"present_slot" => present_slot,
"block_slot" => block_slot,
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
);
return Err(format!(
"Block at slot {} is too far in the future",
block.slot
));
} else {
// The block is in the future, but not too far.
trace!(
self.log, "Block is slightly ahead of our slot clock, ignoring.";
"present_slot" => present_slot,
"block_slot" => block_slot,
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
);
}
}
BlockProcessingOutcome::WouldRevertFinalizedSlot { .. } => {
trace!(
log, "FutureBlock";
"msg" => "block for future slot rejected, check your time",
"present_slot" => present_slot,
"block_slot" => block_slot,
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
self.log, "Finalized or earlier block processed";
"outcome" => format!("{:?}", outcome),
);
return Err(format!(
"Block at slot {} is too far in the future",
block.slot
));
} else {
// The block is in the future, but not too far.
// block reached our finalized slot or was earlier, move to the next block
}
BlockProcessingOutcome::GenesisBlock => {
trace!(
log, "QueuedFutureBlock";
"msg" => "queuing future block, check your time",
"present_slot" => present_slot,
"block_slot" => block_slot,
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
self.log, "Genesis block was processed";
"outcome" => format!("{:?}", outcome),
);
}
}
BlockProcessingOutcome::FinalizedSlot => {
trace!(
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
}
_ => {
trace!(
log, "InvalidBlock";
warn!(
log, "Invalid block received";
"msg" => "peer sent invalid block",
"outcome" => format!("{:?}", outcome),
);
@ -968,7 +987,7 @@ fn process_blocks<T: BeaconChainTypes>(
}
}
} else {
trace!(
warn!(
log, "BlockProcessingFailure";
"msg" => "unexpected condition in processing block.",
"outcome" => format!("{:?}", processing_result)

View File

@ -385,7 +385,7 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
"peer" => format!("{:?}", peer_id),
"msg" => "Failed to return all requested hashes",
"start_slot" => req.start_slot,
"current_slot" => self.chain.best_slot(),
"current_slot" => format!("{:?}", self.chain.slot()),
"requested" => req.count,
"returned" => blocks.len(),
);
@ -523,6 +523,12 @@ impl NetworkContext {
}
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))
// TODO: disconnect peers.
}

View File

@ -14,9 +14,12 @@ store = { path = "../store" }
version = { path = "../version" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "^1.0"
serde_yaml = "0.8"
slog = "^2.2.3"
slog-term = "^2.4.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" }
types = { path = "../../eth2/types" }
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 beacon_chain::{BeaconChain, BeaconChainTypes};
use hyper::{Body, Request};
use serde::Serialize;
use ssz_derive::Encode;
use std::sync::Arc;
use store::Store;
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)))
}
#[derive(Serialize)]
#[derive(Serialize, Encode)]
#[serde(bound = "T: EthSpec")]
pub struct BlockResponse<T: EthSpec> {
pub root: Hash256,
@ -77,11 +78,7 @@ pub fn get_block<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult
beacon_block: block,
};
let json: String = serde_json::to_string(&response).map_err(|e| {
ApiError::ServerError(format!("Unable to serialize BlockResponse: {:?}", e))
})?;
Ok(success_response(Body::from(json)))
ResponseBuilder::new(&req).body(&response)
}
/// 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)))
}
#[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")]
pub struct StateResponse<T: EthSpec> {
pub root: Hash256,
@ -144,11 +156,7 @@ pub fn get_state<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult
beacon_state: state,
};
let json: String = serde_json::to_string(&response).map_err(|e| {
ApiError::ServerError(format!("Unable to serialize StateResponse: {:?}", e))
})?;
Ok(success_response(Body::from(json)))
ResponseBuilder::new(&req).body(&response)
}
/// HTTP handler to return a `BeaconState` root at a given `slot`.

View File

@ -8,6 +8,7 @@ mod helpers;
mod metrics;
mod network;
mod node;
mod response_builder;
mod spec;
mod url_query;
mod validator;
@ -18,6 +19,7 @@ use eth2_config::Eth2Config;
use hyper::rt::Future;
use hyper::service::service_fn_ok;
use hyper::{Body, Method, Response, Server, StatusCode};
use response_builder::ResponseBuilder;
use slog::{info, o, warn};
use std::ops::Deref;
use std::path::PathBuf;
@ -145,6 +147,7 @@ pub fn start_server<T: BeaconChainTypes>(
beacon::get_latest_finalized_checkpoint::<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),
//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 mut state = if let Ok(state) = self.chain.state_at_slot(slot) {
state.as_ref().clone()
state.clone()
} else {
let log_clone = self.log.clone();
let f = sink

View File

@ -1,5 +1,5 @@
use clap::ArgMatches;
use client::{BeaconChainStartMethod, ClientConfig, Eth2Config};
use client::{BeaconChainStartMethod, ClientConfig, Eth1BackendMethod, Eth2Config};
use eth2_config::{read_from_file, write_to_file};
use lighthouse_bootstrap::Bootstrapper;
use rand::{distributions::Alphanumeric, Rng};
@ -25,6 +25,14 @@ type Config = (ClientConfig, Eth2Config);
pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result<Config> {
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() {
("testnet", Some(sub_cmd_args)) => {
process_testnet_subcommand(&mut builder, sub_cmd_args, log)?
@ -70,11 +78,15 @@ fn process_testnet_subcommand(
builder.set_random_datadir()?;
}
if cli_args.is_present("force") {
builder.clean_datadir()?;
}
let is_bootstrap = cli_args.subcommand_name() == Some("bootstrap");
if let Some(path_string) = cli_args.value_of("eth2-config") {
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
@ -85,6 +97,18 @@ fn process_testnet_subcommand(
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") {
let path = path_string
.parse::<PathBuf>()
@ -92,10 +116,6 @@ fn process_testnet_subcommand(
builder.load_client_config(path)?;
}
if cli_args.is_present("force") {
builder.clean_datadir()?;
}
info!(
log,
"Creating new datadir";
@ -117,6 +137,7 @@ fn process_testnet_subcommand(
.and_then(|s| s.parse::<u16>().ok());
builder.import_bootstrap_libp2p_address(server, port)?;
builder.import_bootstrap_enr_address(server)?;
builder.import_bootstrap_eth2_config(server)?;
builder.set_beacon_chain_start_method(BeaconChainStartMethod::HttpBootstrap {
@ -160,6 +181,32 @@ fn process_testnet_subcommand(
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()),
};
@ -250,7 +297,12 @@ impl<'a> ConfigBuilder<'a> {
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`,
/// 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,
port: Option<u16>,
) -> 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) {
info!(
@ -282,6 +334,28 @@ impl<'a> ConfigBuilder<'a> {
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.
///
/// 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.
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()?);
@ -307,6 +381,10 @@ impl<'a> ConfigBuilder<'a> {
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.
///
/// 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),
)
/*
* 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.
*/
@ -235,6 +245,13 @@ fn main() {
backup directory.")
.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`
*
@ -246,6 +263,7 @@ fn main() {
.arg(Arg::with_name("server")
.value_name("HTTP_SERVER")
.required(true)
.default_value("http://localhost:5052")
.help("A HTTP server, with a http:// prefix"))
.arg(Arg::with_name("libp2p-port")
.short("p")
@ -298,9 +316,14 @@ fn main() {
*
* Start a new node, using a genesis state loaded from a YAML file
*/
.subcommand(SubCommand::with_name("yaml")
.about("Creates a new datadir where the genesis state is read from YAML. Will fail to parse \
a YAML state that was generated to a different spec than that specified by --spec.")
.subcommand(SubCommand::with_name("file")
.about("Creates a new datadir where the genesis state is read from YAML. May fail to parse \
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")
.value_name("YAML_FILE")
.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::Future;
use slog::{error, info};
@ -47,55 +50,30 @@ pub fn run_beacon_node(
"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()) {
("disk", "minimal") => run::<ClientType<DiskStore, MinimalEthSpec>>(
&db_path,
client_config,
eth2_config,
executor,
runtime,
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,
),
("disk", "minimal") => run_client!(DiskStore, MinimalEthSpec),
("disk", "mainnet") => run_client!(DiskStore, MainnetEthSpec),
("disk", "interop") => run_client!(DiskStore, InteropEthSpec),
("memory", "minimal") => run_client!(MemoryStore, MinimalEthSpec),
("memory", "mainnet") => run_client!(MemoryStore, MainnetEthSpec),
("memory", "interop") => run_client!(MemoryStore, InteropEthSpec),
(db_type, spec) => {
error!(log, "Unknown runtime configuration"; "spec_constants" => spec, "db_type" => db_type);
Err("Unknown specification and/or db_type.".into())

View File

@ -2,7 +2,9 @@
* [Introduction](./intro.md)
* [Development Environment](./setup.md)
* [Testnets](./testnets.md)
* [Simple Local Testnet](./simple-testnet.md)
* [Interop](./interop.md)
* [Interop Tips & Tricks](./interop-tips.md)
* [Simple Local Testnet](./simple-testnet.md)
* [Interop](./interop.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
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
inter-operability testing with Lighthouse.
To allow for faster iteration cycles without the "merging to master" overhead,
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.**
## Chapters
## Environment
All that is required for inter-op is a built and tested [development
environment](setup). 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.
## 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**
- Read about the required [development environment](./interop-environment.md).
- Get an [overview](./interop-cli.md) of the Lighthouse CLI.
- See how we expect to handle some [interop scenarios](./interop-scenarios.md).
- See the [interop cheat-sheet](./interop-cheat-sheet.md) for useful CLI tips.

View File

@ -17,31 +17,11 @@ Foundation, Consensys and other individuals and organisations.
## Developer Resources
Documentation is provided for **researchers and developers** working on
Ethereum 2.0 and assumes prior knowledge on the topic.
Documentation is presently targeted at **researchers and developers**. It
assumes significant prior knowledge of Ethereum 2.0.
- Get started with [development environment setup](setup.html).
- [Run a simple testnet](simple-testnet.html) in Only Three CLI Commands™.
- Read about our interop workflow.
- API?
Topics:
## Release
Ethereum 2.0 is not fully specified or implemented and as such, Lighthouse is
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.
- Get started with [development environment setup](./setup.md).
- See the [interop docs](./interop.md).
- [Run a simple testnet](./simple-testnet.md) in Only Three CLI Commands™.

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:
@ -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 new node to `10` higher. Your first node's HTTP server was at TCP
> `5052` but this one will be at `5062`.
> - The `-r` flag creates a new data directory in your home with a random
> string appended, to avoid conflicting with any other running node.
> - The HTTP address is the API of the first node. The new node will download
> configuration via HTTP before starting sync via libp2p.
> - The `-r` flag creates a new data directory with a random string appended
> (avoids data directory collisions between nodes).
> - The default bootstrap HTTP address is `http://localhost:5052`. The new node
> 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;
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 rand::{prelude::*, rngs::StdRng};
@ -51,7 +52,7 @@ struct ForkedHarness {
impl ForkedHarness {
/// A new standard instance of with constant parameters.
pub fn new() -> Self {
let harness = BeaconChainHarness::new(VALIDATOR_COUNT);
let harness = BeaconChainHarness::new(generate_deterministic_keypairs(VALIDATOR_COUNT));
// Move past the zero slot.
harness.advance_slot();

View File

@ -31,3 +31,4 @@ tree_hash_derive = "0.2"
[dev-dependencies]
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 {
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.

View File

@ -9,7 +9,7 @@ fn default_values() {
let cache = CommitteeCache::default();
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_attestation_duties(0), None);
assert_eq!(cache.active_validator_count(), 0);

View File

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

View File

@ -13,3 +13,4 @@ reqwest = "0.9"
url = "1.2"
types = { path = "../../types" }
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 serde::Deserialize;
use slog::{error, Logger};
use std::borrow::Cow;
use std::net::Ipv4Addr;
use std::time::Duration;
use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Hash256, Slot};
use url::Host;
pub const RETRY_SLEEP_MILLIS: u64 = 100;
pub const RETRY_WARN_INTERVAL: u64 = 30;
#[derive(Debug)]
enum Error {
InvalidUrl,
@ -31,11 +36,35 @@ pub struct Bootstrapper {
}
impl Bootstrapper {
/// Parses the given `server` as a URL, instantiating `Self`.
pub fn from_server_string(server: String) -> Result<Self, String> {
Ok(Self {
/// Parses the given `server` as a URL, instantiating `Self` and blocking until a connection
/// can be made with the server.
///
/// 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))?,
})
};
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.

View File

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