diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index d5594a49a..ae89ac1e1 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -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" } diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index a142816ae..5f12df631 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -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`, 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>), Owned(BeaconState), } -impl<'a, T: EthSpec> AsRef> for StateCow<'a, T> { - fn as_ref(&self) -> &BeaconState { +impl<'a, T: EthSpec> BeaconStateCow<'a, T> { + pub fn maybe_as_mut_ref(&mut self) -> Option<&mut BeaconState> { 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> { +impl<'a, T: EthSpec> std::ops::Deref for BeaconStateCow<'a, T> { + type Target = BeaconState; + + fn deref(&self) -> &BeaconState { 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; + type Eth1Chain: Eth1ChainBackend; type EthSpec: types::EthSpec; } @@ -117,6 +128,8 @@ pub struct BeaconChain { /// Stores all operations (e.g., `Attestation`, `Deposit`, etc) that are candidates for /// inclusion in a block. pub op_pool: OperationPool, + /// Provides information from the Ethereum 1 (PoW) chain. + pub eth1_chain: Eth1Chain, /// Stores a "snapshot" of the chain at the time the head-of-the-chain block was received. canonical_head: RwLock>, /// The root of the genesis block. @@ -132,6 +145,7 @@ impl BeaconChain { /// Instantiate a new Beacon Chain, from genesis. pub fn from_genesis( store: Arc, + eth1_backend: T::Eth1Chain, mut genesis_state: BeaconState, mut genesis_block: BeaconBlock, spec: ChainSpec, @@ -176,6 +190,7 @@ impl BeaconChain { 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 BeaconChain { /// Attempt to load an existing instance from the given `store`. pub fn from_store( store: Arc, + eth1_backend: T::Eth1Chain, spec: ChainSpec, log: Logger, ) -> Result>, Error> { @@ -223,6 +239,7 @@ impl BeaconChain { 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 BeaconChain { /// /// 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, Error> { + pub fn state_at_slot(&self, slot: Slot) -> Result, 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 BeaconChain { } }; } - Ok(StateCow::Owned(state)) + Ok(BeaconStateCow::Owned(state)) } else { let state_root = self .rev_iter_state_roots() @@ -405,7 +422,7 @@ impl BeaconChain { .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 BeaconChain { /// /// 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, Error> { + pub fn state_now(&self) -> Result, Error> { self.state_at_slot(self.slot()?) } @@ -473,25 +490,24 @@ impl BeaconChain { 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 BeaconChain { 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 BeaconChain { 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 BeaconChain { 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 BeaconChain { /// 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 BeaconChain { 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 BeaconChain { 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 BeaconChain { .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 BeaconChain { 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 BeaconChain { .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 BeaconChain { 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(), }, diff --git a/beacon_node/beacon_chain/src/beacon_chain_builder.rs b/beacon_node/beacon_chain/src/beacon_chain_builder.rs index 06d2818e2..2a3537020 100644 --- a/beacon_node/beacon_chain/src/beacon_chain_builder.rs +++ b/beacon_node/beacon_chain/src/beacon_chain_builder.rs @@ -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 { @@ -32,21 +33,21 @@ pub struct BeaconChainBuilder { impl BeaconChainBuilder { pub fn recent_genesis( - validator_count: usize, + keypairs: &[Keypair], minutes: u64, spec: ChainSpec, log: Logger, ) -> Result { - 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 { - 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 BeaconChainBuilder { Ok(Self::from_genesis_state(genesis_state, spec, log)) } + pub fn ssz_state(file: &PathBuf, spec: ChainSpec, log: Logger) -> Result { + 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 { + 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 { - 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 BeaconChainBuilder { } } - pub fn build(self, store: Arc) -> Result, String> { + pub fn build( + self, + store: Arc, + eth1_backend: T::Eth1Chain, + ) -> Result, 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(genesis_state: &BeaconState, spec: &ChainSpec) - /// Reference: /// https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start fn interop_genesis_state( - validator_count: usize, + keypairs: &[Keypair], genesis_time: u64, spec: &ChainSpec, ) -> Result, 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( .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( 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::(validator_count, genesis_time, spec) + let keypairs = generate_deterministic_keypairs(validator_count); + + let state = interop_genesis_state::(&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" ); diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 5ef68f2cd..030689928 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -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); diff --git a/beacon_node/beacon_chain/src/eth1_chain.rs b/beacon_node/beacon_chain/src/eth1_chain.rs new file mode 100644 index 000000000..e4ccee3ba --- /dev/null +++ b/beacon_node/beacon_chain/src/eth1_chain.rs @@ -0,0 +1,110 @@ +use crate::BeaconChainTypes; +use eth2_hashing::hash; +use std::marker::PhantomData; +use types::{BeaconState, Deposit, Eth1Data, EthSpec, Hash256}; + +type Result = std::result::Result; + +/// Holds an `Eth1ChainBackend` and serves requests from the `BeaconChain`. +pub struct Eth1Chain { + backend: T::Eth1Chain, +} + +impl Eth1Chain { + 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, + ) -> Result { + 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, + ) -> Result> { + 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: Sized + Send + Sync { + fn new(server: String) -> Result; + + /// Returns the `Eth1Data` that should be included in a block being produced for the given + /// `state`. + fn eth1_data(&self, beacon_state: &BeaconState) -> Result; + + /// 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) -> Result>; +} + +pub struct InteropEth1ChainBackend { + _phantom: PhantomData, +} + +impl Eth1ChainBackend for InteropEth1ChainBackend { + fn new(_server: String) -> Result { + Ok(Self::default()) + } + + fn eth1_data(&self, state: &BeaconState) -> Result { + 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) -> Result> { + Ok(vec![]) + } +} + +impl Default for InteropEth1ChainBackend { + fn default() -> Self { + Self { + _phantom: PhantomData, + } + } +} + +/// Returns `int` as little-endian bytes with a length of 32. +fn int_to_bytes32(int: u64) -> Vec { + let mut vec = int.to_le_bytes().to_vec(); + vec.resize(32, 0); + vec +} diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 9c833f778..036172348 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -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; diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 1006fabf5..7670ac74e 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -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; 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) -> 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, keypairs: Vec) -> Self { + pub fn new(keypairs: Vec) -> 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 { - 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), + } }); } diff --git a/beacon_node/beacon_chain/tests/tests.rs b/beacon_node/beacon_chain/tests/tests.rs index ba7f7bf84..82fc88216 100644 --- a/beacon_node/beacon_chain/tests/tests.rs +++ b/beacon_node/beacon_chain/tests/tests.rs @@ -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; fn get_harness(validator_count: usize) -> BeaconChainHarness { - 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) + } +} diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 2f5389ce5..5b0553c5b 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -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 }, @@ -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(), } } } diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 766d12c56..1d3cb40ec 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -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 { - _phantom_t: PhantomData, - _phantom_u: PhantomData, + _phantom_s: PhantomData, + _phantom_e: PhantomData, } impl BeaconChainTypes for ClientType @@ -39,6 +39,7 @@ where type Store = S; type SlotClock = SystemTimeSlotClock; type LmdGhost = ThreadSafeReducedTree; + type Eth1Chain = InteropEth1ChainBackend; 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> = Arc::new( beacon_chain_builder - .build(store) + .build(store, eth1_backend) .map_err(error::Error::from)?, ); diff --git a/beacon_node/network/src/sync/manager.rs b/beacon_node/network/src/sync/manager.rs index 8ba7486a5..1eec51843 100644 --- a/beacon_node/network/src/sync/manager.rs +++ b/beacon_node/network/src/sync/manager.rs @@ -914,7 +914,7 @@ fn process_blocks( 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( 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( // 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( } } } else { - trace!( + warn!( log, "BlockProcessingFailure"; "msg" => "unexpected condition in processing block.", "outcome" => format!("{:?}", processing_result) diff --git a/beacon_node/network/src/sync/simple_sync.rs b/beacon_node/network/src/sync/simple_sync.rs index e1ca30b0a..056453a68 100644 --- a/beacon_node/network/src/sync/simple_sync.rs +++ b/beacon_node/network/src/sync/simple_sync.rs @@ -385,7 +385,7 @@ impl SimpleSync { "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. } diff --git a/beacon_node/rest_api/Cargo.toml b/beacon_node/rest_api/Cargo.toml index 057de4f94..863ea04da 100644 --- a/beacon_node/rest_api/Cargo.toml +++ b/beacon_node/rest_api/Cargo.toml @@ -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" diff --git a/beacon_node/rest_api/src/beacon.rs b/beacon_node/rest_api/src/beacon.rs index 1c66a2819..a4660836d 100644 --- a/beacon_node/rest_api/src/beacon.rs +++ b/beacon_node/rest_api/src/beacon.rs @@ -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(req: Request) -> ApiResult Ok(success_response(Body::from(json))) } -#[derive(Serialize)] +#[derive(Serialize, Encode)] #[serde(bound = "T: EthSpec")] pub struct BlockResponse { pub root: Hash256, @@ -77,11 +78,7 @@ pub fn get_block(req: Request) -> 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(req: Request) -> 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(req: Request) -> ApiResult { + let beacon_chain = req + .extensions() + .get::>>() + .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 { pub root: Hash256, @@ -144,11 +156,7 @@ pub fn get_state(req: Request) -> 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`. diff --git a/beacon_node/rest_api/src/lib.rs b/beacon_node/rest_api/src/lib.rs index 7c5ab30ef..4aab91e69 100644 --- a/beacon_node/rest_api/src/lib.rs +++ b/beacon_node/rest_api/src/lib.rs @@ -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( beacon::get_latest_finalized_checkpoint::(req) } (&Method::GET, "/beacon/state") => beacon::get_state::(req), + (&Method::GET, "/beacon/state/genesis") => beacon::get_genesis_state::(req), (&Method::GET, "/beacon/state_root") => beacon::get_state_root::(req), //TODO: Add aggreggate/filtered state lookups here, e.g. /beacon/validators/balances diff --git a/beacon_node/rest_api/src/response_builder.rs b/beacon_node/rest_api/src/response_builder.rs new file mode 100644 index 000000000..9b8819996 --- /dev/null +++ b/beacon_node/rest_api/src/response_builder.rs @@ -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) -> 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(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))) + } +} diff --git a/beacon_node/rpc/src/validator.rs b/beacon_node/rpc/src/validator.rs index 84995ca50..abc1cffc5 100644 --- a/beacon_node/rpc/src/validator.rs +++ b/beacon_node/rpc/src/validator.rs @@ -32,7 +32,7 @@ impl ValidatorService for ValidatorServiceInstance { 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 diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index c3dfad9ba..cf5616938 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -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 { 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::() + .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::() @@ -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::().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::() + .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, ) -> 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`. diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index ab9803eba..5d2388785 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -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) diff --git a/beacon_node/src/run.rs b/beacon_node/src/run.rs index 26225cc92..d036ef0c4 100644 --- a/beacon_node/src/run.rs +++ b/beacon_node/src/run.rs @@ -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::>( + &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::>( - &db_path, - client_config, - eth2_config, - executor, - runtime, - log, - ), - ("memory", "minimal") => run::>( - &db_path, - client_config, - eth2_config, - executor, - runtime, - log, - ), - ("disk", "mainnet") => run::>( - &db_path, - client_config, - eth2_config, - executor, - runtime, - log, - ), - ("memory", "mainnet") => run::>( - &db_path, - client_config, - eth2_config, - executor, - runtime, - log, - ), - ("disk", "interop") => run::>( - &db_path, - client_config, - eth2_config, - executor, - runtime, - log, - ), - ("memory", "interop") => run::>( - &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()) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index f0ad41144..4ffa694cd 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -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) diff --git a/book/src/interop-cheat-sheet.md b/book/src/interop-cheat-sheet.md new file mode 100644 index 000000000..ea7794c33 --- /dev/null +++ b/book/src/interop-cheat-sheet.md @@ -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. + + +### 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 +``` + + +### 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 +``` + + +### 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 +``` + + +### 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. + + +### 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"}% +``` diff --git a/book/src/interop-cli.md b/book/src/interop-cli.md new file mode 100644 index 000000000..3dad845f3 --- /dev/null +++ b/book/src/interop-cli.md @@ -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. diff --git a/book/src/interop-environment.md b/book/src/interop-environment.md new file mode 100644 index 000000000..6d3568e29 --- /dev/null +++ b/book/src/interop-environment.md @@ -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. diff --git a/book/src/interop-scenarios.md b/book/src/interop-scenarios.md new file mode 100644 index 000000000..dc8789362 --- /dev/null +++ b/book/src/interop-scenarios.md @@ -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 + + + +### 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. + + +### 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. + + +### 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. + + +### 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 +``` diff --git a/book/src/interop-tips.md b/book/src/interop-tips.md index e581139c4..0d52e896a 100644 --- a/book/src/interop-tips.md +++ b/book/src/interop-tips.md @@ -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"}% -``` diff --git a/book/src/interop.md b/book/src/interop.md index c1a1d4a69..cb119d59d 100644 --- a/book/src/interop.md +++ b/book/src/interop.md @@ -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. diff --git a/book/src/intro.md b/book/src/intro.md index e0e3cd6a0..ccf867a54 100644 --- a/book/src/intro.md +++ b/book/src/intro.md @@ -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™. diff --git a/book/src/simple-testnet.md b/book/src/simple-testnet.md index bf41e455d..b6fa19d6f 100644 --- a/book/src/simple-testnet.md +++ b/book/src/simple-testnet.md @@ -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. diff --git a/eth2/lmd_ghost/tests/test.rs b/eth2/lmd_ghost/tests/test.rs index 4c79a704e..49e9ff738 100644 --- a/eth2/lmd_ghost/tests/test.rs +++ b/eth2/lmd_ghost/tests/test.rs @@ -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(); diff --git a/eth2/types/Cargo.toml b/eth2/types/Cargo.toml index 36cfc39ec..95d7a0317 100644 --- a/eth2/types/Cargo.toml +++ b/eth2/types/Cargo.toml @@ -31,3 +31,4 @@ tree_hash_derive = "0.2" [dev-dependencies] env_logger = "0.6.0" +serde_json = "^1.0" diff --git a/eth2/types/src/beacon_state/beacon_state_types.rs b/eth2/types/src/beacon_state/beacon_state_types.rs index 0e76942dd..f589b3d3e 100644 --- a/eth2/types/src/beacon_state/beacon_state_types.rs +++ b/eth2/types/src/beacon_state/beacon_state_types.rs @@ -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. diff --git a/eth2/types/src/beacon_state/committee_cache/tests.rs b/eth2/types/src/beacon_state/committee_cache/tests.rs index 28e9d92f8..4c17d3f96 100644 --- a/eth2/types/src/beacon_state/committee_cache/tests.rs +++ b/eth2/types/src/beacon_state/committee_cache/tests.rs @@ -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); diff --git a/eth2/types/src/test_utils/builders/testing_proposer_slashing_builder.rs b/eth2/types/src/test_utils/builders/testing_proposer_slashing_builder.rs index 6c72b520f..b97293427 100644 --- a/eth2/types/src/test_utils/builders/testing_proposer_slashing_builder.rs +++ b/eth2/types/src/test_utils/builders/testing_proposer_slashing_builder.rs @@ -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) }; diff --git a/eth2/utils/lighthouse_bootstrap/Cargo.toml b/eth2/utils/lighthouse_bootstrap/Cargo.toml index 3f48505b8..cfc4c6baf 100644 --- a/eth2/utils/lighthouse_bootstrap/Cargo.toml +++ b/eth2/utils/lighthouse_bootstrap/Cargo.toml @@ -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"] } diff --git a/eth2/utils/lighthouse_bootstrap/src/lib.rs b/eth2/utils/lighthouse_bootstrap/src/lib.rs index dc70c6d21..92a587ff2 100644 --- a/eth2/utils/lighthouse_bootstrap/src/lib.rs +++ b/eth2/utils/lighthouse_bootstrap/src/lib.rs @@ -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 { - 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 { + 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. diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index d5d2fc27f..39b2e3eae 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -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()?;