Merge latest interop
This commit is contained in:
commit
2a1d6587a7
@ -18,6 +18,7 @@ rayon = "1.0"
|
|||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
|
serde_json = "^1.0"
|
||||||
slog = { version = "^2.2.3" , features = ["max_level_trace"] }
|
slog = { version = "^2.2.3" , features = ["max_level_trace"] }
|
||||||
sloggers = { version = "^0.3" }
|
sloggers = { version = "^0.3" }
|
||||||
slot_clock = { path = "../../eth2/utils/slot_clock" }
|
slot_clock = { path = "../../eth2/utils/slot_clock" }
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::checkpoint::CheckPoint;
|
use crate::checkpoint::CheckPoint;
|
||||||
use crate::errors::{BeaconChainError as Error, BlockProductionError};
|
use crate::errors::{BeaconChainError as Error, BlockProductionError};
|
||||||
|
use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend};
|
||||||
use crate::fork_choice::{Error as ForkChoiceError, ForkChoice};
|
use crate::fork_choice::{Error as ForkChoiceError, ForkChoice};
|
||||||
use crate::iter::{ReverseBlockRootIterator, ReverseStateRootIterator};
|
use crate::iter::{ReverseBlockRootIterator, ReverseStateRootIterator};
|
||||||
use crate::metrics;
|
use crate::metrics;
|
||||||
@ -45,11 +46,14 @@ pub enum BlockProcessingOutcome {
|
|||||||
block_slot: Slot,
|
block_slot: Slot,
|
||||||
},
|
},
|
||||||
/// The block state_root does not match the generated state.
|
/// The block state_root does not match the generated state.
|
||||||
StateRootMismatch,
|
StateRootMismatch { block: Hash256, local: Hash256 },
|
||||||
/// The block was a genesis block, these blocks cannot be re-imported.
|
/// The block was a genesis block, these blocks cannot be re-imported.
|
||||||
GenesisBlock,
|
GenesisBlock,
|
||||||
/// The slot is finalized, no need to import.
|
/// The slot is finalized, no need to import.
|
||||||
FinalizedSlot,
|
WouldRevertFinalizedSlot {
|
||||||
|
block_slot: Slot,
|
||||||
|
finalized_slot: Slot,
|
||||||
|
},
|
||||||
/// Block is already known, no need to re-import.
|
/// Block is already known, no need to re-import.
|
||||||
BlockIsAlreadyKnown,
|
BlockIsAlreadyKnown,
|
||||||
/// The block could not be applied to the state, it is invalid.
|
/// The block could not be applied to the state, it is invalid.
|
||||||
@ -76,25 +80,31 @@ pub enum AttestationProcessingOutcome {
|
|||||||
Invalid(AttestationValidationError),
|
Invalid(AttestationValidationError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum StateCow<'a, T: EthSpec> {
|
/// Effectively a `Cow<BeaconState>`, however when it is `Borrowed` it holds a `RwLockReadGuard` (a
|
||||||
|
/// read-lock on some read/write-locked state).
|
||||||
|
///
|
||||||
|
/// Only has a small subset of the functionality of a `std::borrow::Cow`.
|
||||||
|
pub enum BeaconStateCow<'a, T: EthSpec> {
|
||||||
Borrowed(RwLockReadGuard<'a, CheckPoint<T>>),
|
Borrowed(RwLockReadGuard<'a, CheckPoint<T>>),
|
||||||
Owned(BeaconState<T>),
|
Owned(BeaconState<T>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: EthSpec> AsRef<BeaconState<T>> for StateCow<'a, T> {
|
impl<'a, T: EthSpec> BeaconStateCow<'a, T> {
|
||||||
fn as_ref(&self) -> &BeaconState<T> {
|
pub fn maybe_as_mut_ref(&mut self) -> Option<&mut BeaconState<T>> {
|
||||||
match self {
|
match self {
|
||||||
StateCow::Borrowed(checkpoint) => &checkpoint.beacon_state,
|
BeaconStateCow::Borrowed(_) => None,
|
||||||
StateCow::Owned(state) => &state,
|
BeaconStateCow::Owned(ref mut state) => Some(state),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: EthSpec> StateCow<'a, T> {
|
impl<'a, T: EthSpec> std::ops::Deref for BeaconStateCow<'a, T> {
|
||||||
pub fn as_mut_ref(&mut self) -> Option<&mut BeaconState<T>> {
|
type Target = BeaconState<T>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &BeaconState<T> {
|
||||||
match self {
|
match self {
|
||||||
StateCow::Borrowed(_) => None,
|
BeaconStateCow::Borrowed(checkpoint) => &checkpoint.beacon_state,
|
||||||
StateCow::Owned(ref mut state) => Some(state),
|
BeaconStateCow::Owned(state) => &state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,6 +113,7 @@ pub trait BeaconChainTypes: Send + Sync + 'static {
|
|||||||
type Store: store::Store;
|
type Store: store::Store;
|
||||||
type SlotClock: slot_clock::SlotClock;
|
type SlotClock: slot_clock::SlotClock;
|
||||||
type LmdGhost: LmdGhost<Self::Store, Self::EthSpec>;
|
type LmdGhost: LmdGhost<Self::Store, Self::EthSpec>;
|
||||||
|
type Eth1Chain: Eth1ChainBackend<Self::EthSpec>;
|
||||||
type EthSpec: types::EthSpec;
|
type EthSpec: types::EthSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +128,8 @@ pub struct BeaconChain<T: BeaconChainTypes> {
|
|||||||
/// Stores all operations (e.g., `Attestation`, `Deposit`, etc) that are candidates for
|
/// Stores all operations (e.g., `Attestation`, `Deposit`, etc) that are candidates for
|
||||||
/// inclusion in a block.
|
/// inclusion in a block.
|
||||||
pub op_pool: OperationPool<T::EthSpec>,
|
pub op_pool: OperationPool<T::EthSpec>,
|
||||||
|
/// Provides information from the Ethereum 1 (PoW) chain.
|
||||||
|
pub eth1_chain: Eth1Chain<T>,
|
||||||
/// Stores a "snapshot" of the chain at the time the head-of-the-chain block was received.
|
/// Stores a "snapshot" of the chain at the time the head-of-the-chain block was received.
|
||||||
canonical_head: RwLock<CheckPoint<T::EthSpec>>,
|
canonical_head: RwLock<CheckPoint<T::EthSpec>>,
|
||||||
/// The root of the genesis block.
|
/// The root of the genesis block.
|
||||||
@ -132,6 +145,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
/// Instantiate a new Beacon Chain, from genesis.
|
/// Instantiate a new Beacon Chain, from genesis.
|
||||||
pub fn from_genesis(
|
pub fn from_genesis(
|
||||||
store: Arc<T::Store>,
|
store: Arc<T::Store>,
|
||||||
|
eth1_backend: T::Eth1Chain,
|
||||||
mut genesis_state: BeaconState<T::EthSpec>,
|
mut genesis_state: BeaconState<T::EthSpec>,
|
||||||
mut genesis_block: BeaconBlock<T::EthSpec>,
|
mut genesis_block: BeaconBlock<T::EthSpec>,
|
||||||
spec: ChainSpec,
|
spec: ChainSpec,
|
||||||
@ -176,6 +190,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
spec,
|
spec,
|
||||||
slot_clock,
|
slot_clock,
|
||||||
op_pool: OperationPool::new(),
|
op_pool: OperationPool::new(),
|
||||||
|
eth1_chain: Eth1Chain::new(eth1_backend),
|
||||||
canonical_head,
|
canonical_head,
|
||||||
genesis_block_root,
|
genesis_block_root,
|
||||||
fork_choice: ForkChoice::new(store.clone(), &genesis_block, genesis_block_root),
|
fork_choice: ForkChoice::new(store.clone(), &genesis_block, genesis_block_root),
|
||||||
@ -187,6 +202,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
/// Attempt to load an existing instance from the given `store`.
|
/// Attempt to load an existing instance from the given `store`.
|
||||||
pub fn from_store(
|
pub fn from_store(
|
||||||
store: Arc<T::Store>,
|
store: Arc<T::Store>,
|
||||||
|
eth1_backend: T::Eth1Chain,
|
||||||
spec: ChainSpec,
|
spec: ChainSpec,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
) -> Result<Option<BeaconChain<T>>, Error> {
|
) -> Result<Option<BeaconChain<T>>, Error> {
|
||||||
@ -223,6 +239,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
slot_clock,
|
slot_clock,
|
||||||
fork_choice: ForkChoice::new(store.clone(), last_finalized_block, last_finalized_root),
|
fork_choice: ForkChoice::new(store.clone(), last_finalized_block, last_finalized_root),
|
||||||
op_pool,
|
op_pool,
|
||||||
|
eth1_chain: Eth1Chain::new(eth1_backend),
|
||||||
canonical_head: RwLock::new(p.canonical_head),
|
canonical_head: RwLock::new(p.canonical_head),
|
||||||
genesis_block_root: p.genesis_block_root,
|
genesis_block_root: p.genesis_block_root,
|
||||||
store,
|
store,
|
||||||
@ -373,11 +390,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
///
|
///
|
||||||
/// Returns `None` when the state is not found in the database or there is an error skipping
|
/// Returns `None` when the state is not found in the database or there is an error skipping
|
||||||
/// to a future state.
|
/// to a future state.
|
||||||
pub fn state_at_slot(&self, slot: Slot) -> Result<StateCow<T::EthSpec>, Error> {
|
pub fn state_at_slot(&self, slot: Slot) -> Result<BeaconStateCow<T::EthSpec>, Error> {
|
||||||
let head_state = &self.head().beacon_state;
|
let head_state = &self.head().beacon_state;
|
||||||
|
|
||||||
if slot == head_state.slot {
|
if slot == head_state.slot {
|
||||||
Ok(StateCow::Borrowed(self.head()))
|
Ok(BeaconStateCow::Borrowed(self.head()))
|
||||||
} else if slot > head_state.slot {
|
} else if slot > head_state.slot {
|
||||||
let head_state_slot = head_state.slot;
|
let head_state_slot = head_state.slot;
|
||||||
let mut state = head_state.clone();
|
let mut state = head_state.clone();
|
||||||
@ -397,7 +414,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(StateCow::Owned(state))
|
Ok(BeaconStateCow::Owned(state))
|
||||||
} else {
|
} else {
|
||||||
let state_root = self
|
let state_root = self
|
||||||
.rev_iter_state_roots()
|
.rev_iter_state_roots()
|
||||||
@ -405,7 +422,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
.map(|(root, _slot)| root)
|
.map(|(root, _slot)| root)
|
||||||
.ok_or_else(|| Error::NoStateForSlot(slot))?;
|
.ok_or_else(|| Error::NoStateForSlot(slot))?;
|
||||||
|
|
||||||
Ok(StateCow::Owned(
|
Ok(BeaconStateCow::Owned(
|
||||||
self.store
|
self.store
|
||||||
.get(&state_root)?
|
.get(&state_root)?
|
||||||
.ok_or_else(|| Error::NoStateForSlot(slot))?,
|
.ok_or_else(|| Error::NoStateForSlot(slot))?,
|
||||||
@ -421,7 +438,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
///
|
///
|
||||||
/// Returns `None` when there is an error skipping to a future state or the slot clock cannot
|
/// Returns `None` when there is an error skipping to a future state or the slot clock cannot
|
||||||
/// be read.
|
/// be read.
|
||||||
pub fn state_now(&self) -> Result<StateCow<T::EthSpec>, Error> {
|
pub fn state_now(&self) -> Result<BeaconStateCow<T::EthSpec>, Error> {
|
||||||
self.state_at_slot(self.slot()?)
|
self.state_at_slot(self.slot()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,25 +490,24 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
let head_state = &self.head().beacon_state;
|
let head_state = &self.head().beacon_state;
|
||||||
|
|
||||||
let mut state = if epoch(slot) == epoch(head_state.slot) {
|
let mut state = if epoch(slot) == epoch(head_state.slot) {
|
||||||
StateCow::Borrowed(self.head())
|
BeaconStateCow::Borrowed(self.head())
|
||||||
} else {
|
} else {
|
||||||
self.state_at_slot(slot)?
|
self.state_at_slot(slot)?
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(state) = state.as_mut_ref() {
|
if let Some(state) = state.maybe_as_mut_ref() {
|
||||||
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
|
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if epoch(state.as_ref().slot) != epoch(slot) {
|
if epoch(state.slot) != epoch(slot) {
|
||||||
return Err(Error::InvariantViolated(format!(
|
return Err(Error::InvariantViolated(format!(
|
||||||
"Epochs in consistent in proposer lookup: state: {}, requested: {}",
|
"Epochs in consistent in proposer lookup: state: {}, requested: {}",
|
||||||
epoch(state.as_ref().slot),
|
epoch(state.slot),
|
||||||
epoch(slot)
|
epoch(slot)
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
state
|
state
|
||||||
.as_ref()
|
|
||||||
.get_beacon_proposer_index(slot, RelativeEpoch::Current, &self.spec)
|
.get_beacon_proposer_index(slot, RelativeEpoch::Current, &self.spec)
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
@ -509,26 +525,25 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
let head_state = &self.head().beacon_state;
|
let head_state = &self.head().beacon_state;
|
||||||
|
|
||||||
let mut state = if epoch == as_epoch(head_state.slot) {
|
let mut state = if epoch == as_epoch(head_state.slot) {
|
||||||
StateCow::Borrowed(self.head())
|
BeaconStateCow::Borrowed(self.head())
|
||||||
} else {
|
} else {
|
||||||
self.state_at_slot(epoch.start_slot(T::EthSpec::slots_per_epoch()))?
|
self.state_at_slot(epoch.start_slot(T::EthSpec::slots_per_epoch()))?
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(state) = state.as_mut_ref() {
|
if let Some(state) = state.maybe_as_mut_ref() {
|
||||||
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
|
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if as_epoch(state.as_ref().slot) != epoch {
|
if as_epoch(state.slot) != epoch {
|
||||||
return Err(Error::InvariantViolated(format!(
|
return Err(Error::InvariantViolated(format!(
|
||||||
"Epochs in consistent in attestation duties lookup: state: {}, requested: {}",
|
"Epochs in consistent in attestation duties lookup: state: {}, requested: {}",
|
||||||
as_epoch(state.as_ref().slot),
|
as_epoch(state.slot),
|
||||||
epoch
|
epoch
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(attestation_duty) = state
|
if let Some(attestation_duty) =
|
||||||
.as_ref()
|
state.get_attestation_duties(validator_index, RelativeEpoch::Current)?
|
||||||
.get_attestation_duties(validator_index, RelativeEpoch::Current)?
|
|
||||||
{
|
{
|
||||||
Ok(Some((attestation_duty.slot, attestation_duty.shard)))
|
Ok(Some((attestation_duty.slot, attestation_duty.shard)))
|
||||||
} else {
|
} else {
|
||||||
@ -549,12 +564,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
let head_block_root = self.head().beacon_block_root;
|
let head_block_root = self.head().beacon_block_root;
|
||||||
let head_block_slot = self.head().beacon_block.slot;
|
let head_block_slot = self.head().beacon_block.slot;
|
||||||
|
|
||||||
self.produce_attestation_data_for_block(
|
self.produce_attestation_data_for_block(shard, head_block_root, head_block_slot, &*state)
|
||||||
shard,
|
|
||||||
head_block_root,
|
|
||||||
head_block_slot,
|
|
||||||
state.as_ref(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce an `AttestationData` that attests to the chain denoted by `block_root` and `state`.
|
/// Produce an `AttestationData` that attests to the chain denoted by `block_root` and `state`.
|
||||||
@ -876,7 +886,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
match self.state_now() {
|
match self.state_now() {
|
||||||
Ok(state) => self
|
Ok(state) => self
|
||||||
.op_pool
|
.op_pool
|
||||||
.insert_voluntary_exit(exit, state.as_ref(), &self.spec),
|
.insert_voluntary_exit(exit, &*state, &self.spec),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
&self.log,
|
&self.log,
|
||||||
@ -892,9 +902,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
/// Accept some transfer and queue it for inclusion in an appropriate block.
|
/// Accept some transfer and queue it for inclusion in an appropriate block.
|
||||||
pub fn process_transfer(&self, transfer: Transfer) -> Result<(), TransferValidationError> {
|
pub fn process_transfer(&self, transfer: Transfer) -> Result<(), TransferValidationError> {
|
||||||
match self.state_now() {
|
match self.state_now() {
|
||||||
Ok(state) => self
|
Ok(state) => self.op_pool.insert_transfer(transfer, &*state, &self.spec),
|
||||||
.op_pool
|
|
||||||
.insert_transfer(transfer, state.as_ref(), &self.spec),
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
&self.log,
|
&self.log,
|
||||||
@ -915,7 +923,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
match self.state_now() {
|
match self.state_now() {
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
self.op_pool
|
self.op_pool
|
||||||
.insert_proposer_slashing(proposer_slashing, state.as_ref(), &self.spec)
|
.insert_proposer_slashing(proposer_slashing, &*state, &self.spec)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
@ -937,7 +945,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
match self.state_now() {
|
match self.state_now() {
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
self.op_pool
|
self.op_pool
|
||||||
.insert_attester_slashing(attester_slashing, state.as_ref(), &self.spec)
|
.insert_attester_slashing(attester_slashing, &*state, &self.spec)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
@ -968,14 +976,17 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
.epoch
|
.epoch
|
||||||
.start_slot(T::EthSpec::slots_per_epoch());
|
.start_slot(T::EthSpec::slots_per_epoch());
|
||||||
|
|
||||||
if block.slot <= finalized_slot {
|
|
||||||
return Ok(BlockProcessingOutcome::FinalizedSlot);
|
|
||||||
}
|
|
||||||
|
|
||||||
if block.slot == 0 {
|
if block.slot == 0 {
|
||||||
return Ok(BlockProcessingOutcome::GenesisBlock);
|
return Ok(BlockProcessingOutcome::GenesisBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if block.slot <= finalized_slot {
|
||||||
|
return Ok(BlockProcessingOutcome::WouldRevertFinalizedSlot {
|
||||||
|
block_slot: block.slot,
|
||||||
|
finalized_slot: finalized_slot,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let block_root_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_BLOCK_ROOT);
|
let block_root_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_BLOCK_ROOT);
|
||||||
|
|
||||||
let block_root = block.canonical_root();
|
let block_root = block.canonical_root();
|
||||||
@ -1073,7 +1084,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
let state_root = state.canonical_root();
|
let state_root = state.canonical_root();
|
||||||
|
|
||||||
if block.state_root != state_root {
|
if block.state_root != state_root {
|
||||||
return Ok(BlockProcessingOutcome::StateRootMismatch);
|
return Ok(BlockProcessingOutcome::StateRootMismatch {
|
||||||
|
block: block.state_root,
|
||||||
|
local: state_root,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics::stop_timer(state_root_timer);
|
metrics::stop_timer(state_root_timer);
|
||||||
@ -1158,7 +1172,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
.state_at_slot(slot - 1)
|
.state_at_slot(slot - 1)
|
||||||
.map_err(|_| BlockProductionError::UnableToProduceAtSlot(slot))?;
|
.map_err(|_| BlockProductionError::UnableToProduceAtSlot(slot))?;
|
||||||
|
|
||||||
self.produce_block_on_state(state.as_ref().clone(), slot, randao_reveal)
|
self.produce_block_on_state(state.clone(), slot, randao_reveal)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce a block for some `slot` upon the given `state`.
|
/// Produce a block for some `slot` upon the given `state`.
|
||||||
@ -1207,16 +1221,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
body: BeaconBlockBody {
|
body: BeaconBlockBody {
|
||||||
randao_reveal,
|
randao_reveal,
|
||||||
// TODO: replace with real data.
|
// TODO: replace with real data.
|
||||||
eth1_data: Eth1Data {
|
eth1_data: self.eth1_chain.eth1_data_for_block_production(&state)?,
|
||||||
deposit_count: state.eth1_data.deposit_count,
|
|
||||||
deposit_root: Hash256::zero(),
|
|
||||||
block_hash: Hash256::zero(),
|
|
||||||
},
|
|
||||||
graffiti,
|
graffiti,
|
||||||
proposer_slashings: proposer_slashings.into(),
|
proposer_slashings: proposer_slashings.into(),
|
||||||
attester_slashings: attester_slashings.into(),
|
attester_slashings: attester_slashings.into(),
|
||||||
attestations: self.op_pool.get_attestations(&state, &self.spec).into(),
|
attestations: self.op_pool.get_attestations(&state, &self.spec).into(),
|
||||||
deposits: self.op_pool.get_deposits(&state).into(),
|
deposits: self.eth1_chain.deposits_for_block_inclusion(&state)?.into(),
|
||||||
voluntary_exits: self.op_pool.get_voluntary_exits(&state, &self.spec).into(),
|
voluntary_exits: self.op_pool.get_voluntary_exits(&state, &self.spec).into(),
|
||||||
transfers: self.op_pool.get_transfers(&state, &self.spec).into(),
|
transfers: self.op_pool.get_transfers(&state, &self.spec).into(),
|
||||||
},
|
},
|
||||||
|
@ -4,16 +4,17 @@ use lighthouse_bootstrap::Bootstrapper;
|
|||||||
use merkle_proof::MerkleTree;
|
use merkle_proof::MerkleTree;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use slog::Logger;
|
use slog::Logger;
|
||||||
use ssz::Encode;
|
use ssz::{Decode, Encode};
|
||||||
use state_processing::initialize_beacon_state_from_eth1;
|
use state_processing::initialize_beacon_state_from_eth1;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use tree_hash::{SignedRoot, TreeHash};
|
use tree_hash::{SignedRoot, TreeHash};
|
||||||
use types::{
|
use types::{
|
||||||
test_utils::generate_deterministic_keypairs, BeaconBlock, BeaconState, ChainSpec, Deposit,
|
BeaconBlock, BeaconState, ChainSpec, Deposit, DepositData, Domain, EthSpec, Fork, Hash256,
|
||||||
DepositData, Domain, EthSpec, Fork, Hash256, PublicKey, Signature,
|
Keypair, PublicKey, Signature,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum BuildStrategy<T: BeaconChainTypes> {
|
enum BuildStrategy<T: BeaconChainTypes> {
|
||||||
@ -32,21 +33,21 @@ pub struct BeaconChainBuilder<T: BeaconChainTypes> {
|
|||||||
|
|
||||||
impl<T: BeaconChainTypes> BeaconChainBuilder<T> {
|
impl<T: BeaconChainTypes> BeaconChainBuilder<T> {
|
||||||
pub fn recent_genesis(
|
pub fn recent_genesis(
|
||||||
validator_count: usize,
|
keypairs: &[Keypair],
|
||||||
minutes: u64,
|
minutes: u64,
|
||||||
spec: ChainSpec,
|
spec: ChainSpec,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
) -> Result<Self, String> {
|
) -> Result<Self, String> {
|
||||||
Self::quick_start(recent_genesis_time(minutes), validator_count, spec, log)
|
Self::quick_start(recent_genesis_time(minutes), keypairs, spec, log)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn quick_start(
|
pub fn quick_start(
|
||||||
genesis_time: u64,
|
genesis_time: u64,
|
||||||
validator_count: usize,
|
keypairs: &[Keypair],
|
||||||
spec: ChainSpec,
|
spec: ChainSpec,
|
||||||
log: Logger,
|
log: Logger,
|
||||||
) -> Result<Self, String> {
|
) -> Result<Self, String> {
|
||||||
let genesis_state = interop_genesis_state(validator_count, genesis_time, &spec)?;
|
let genesis_state = interop_genesis_state(keypairs, genesis_time, &spec)?;
|
||||||
|
|
||||||
Ok(Self::from_genesis_state(genesis_state, spec, log))
|
Ok(Self::from_genesis_state(genesis_state, spec, log))
|
||||||
}
|
}
|
||||||
@ -61,8 +62,32 @@ impl<T: BeaconChainTypes> BeaconChainBuilder<T> {
|
|||||||
Ok(Self::from_genesis_state(genesis_state, spec, log))
|
Ok(Self::from_genesis_state(genesis_state, spec, log))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ssz_state(file: &PathBuf, spec: ChainSpec, log: Logger) -> Result<Self, String> {
|
||||||
|
let mut file = File::open(file.clone())
|
||||||
|
.map_err(|e| format!("Unable to open SSZ genesis state file {:?}: {:?}", file, e))?;
|
||||||
|
|
||||||
|
let mut bytes = vec![];
|
||||||
|
file.read_to_end(&mut bytes)
|
||||||
|
.map_err(|e| format!("Failed to read SSZ file: {:?}", e))?;
|
||||||
|
|
||||||
|
let genesis_state = BeaconState::from_ssz_bytes(&bytes)
|
||||||
|
.map_err(|e| format!("Unable to parse SSZ genesis state file: {:?}", e))?;
|
||||||
|
|
||||||
|
Ok(Self::from_genesis_state(genesis_state, spec, log))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn json_state(file: &PathBuf, spec: ChainSpec, log: Logger) -> Result<Self, String> {
|
||||||
|
let file = File::open(file.clone())
|
||||||
|
.map_err(|e| format!("Unable to open JSON genesis state file {:?}: {:?}", file, e))?;
|
||||||
|
|
||||||
|
let genesis_state = serde_json::from_reader(file)
|
||||||
|
.map_err(|e| format!("Unable to parse JSON genesis state file: {:?}", e))?;
|
||||||
|
|
||||||
|
Ok(Self::from_genesis_state(genesis_state, spec, log))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn http_bootstrap(server: &str, spec: ChainSpec, log: Logger) -> Result<Self, String> {
|
pub fn http_bootstrap(server: &str, spec: ChainSpec, log: Logger) -> Result<Self, String> {
|
||||||
let bootstrapper = Bootstrapper::from_server_string(server.to_string())
|
let bootstrapper = Bootstrapper::connect(server.to_string(), &log)
|
||||||
.map_err(|e| format!("Failed to initialize bootstrap client: {}", e))?;
|
.map_err(|e| format!("Failed to initialize bootstrap client: {}", e))?;
|
||||||
|
|
||||||
let (genesis_state, genesis_block) = bootstrapper
|
let (genesis_state, genesis_block) = bootstrapper
|
||||||
@ -102,16 +127,23 @@ impl<T: BeaconChainTypes> BeaconChainBuilder<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self, store: Arc<T::Store>) -> Result<BeaconChain<T>, String> {
|
pub fn build(
|
||||||
|
self,
|
||||||
|
store: Arc<T::Store>,
|
||||||
|
eth1_backend: T::Eth1Chain,
|
||||||
|
) -> Result<BeaconChain<T>, String> {
|
||||||
Ok(match self.build_strategy {
|
Ok(match self.build_strategy {
|
||||||
BuildStrategy::LoadFromStore => BeaconChain::from_store(store, self.spec, self.log)
|
BuildStrategy::LoadFromStore => {
|
||||||
|
BeaconChain::from_store(store, eth1_backend, self.spec, self.log)
|
||||||
.map_err(|e| format!("Error loading BeaconChain from database: {:?}", e))?
|
.map_err(|e| format!("Error loading BeaconChain from database: {:?}", e))?
|
||||||
.ok_or_else(|| format!("Unable to find exising BeaconChain in database."))?,
|
.ok_or_else(|| format!("Unable to find exising BeaconChain in database."))?
|
||||||
|
}
|
||||||
BuildStrategy::FromGenesis {
|
BuildStrategy::FromGenesis {
|
||||||
genesis_block,
|
genesis_block,
|
||||||
genesis_state,
|
genesis_state,
|
||||||
} => BeaconChain::from_genesis(
|
} => BeaconChain::from_genesis(
|
||||||
store,
|
store,
|
||||||
|
eth1_backend,
|
||||||
genesis_state.as_ref().clone(),
|
genesis_state.as_ref().clone(),
|
||||||
genesis_block.as_ref().clone(),
|
genesis_block.as_ref().clone(),
|
||||||
self.spec,
|
self.spec,
|
||||||
@ -135,12 +167,11 @@ fn genesis_block<T: EthSpec>(genesis_state: &BeaconState<T>, spec: &ChainSpec) -
|
|||||||
/// Reference:
|
/// Reference:
|
||||||
/// https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start
|
/// https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start
|
||||||
fn interop_genesis_state<T: EthSpec>(
|
fn interop_genesis_state<T: EthSpec>(
|
||||||
validator_count: usize,
|
keypairs: &[Keypair],
|
||||||
genesis_time: u64,
|
genesis_time: u64,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<BeaconState<T>, String> {
|
) -> Result<BeaconState<T>, String> {
|
||||||
let keypairs = generate_deterministic_keypairs(validator_count);
|
let eth1_block_hash = Hash256::from_slice(&[0x42; 32]);
|
||||||
let eth1_block_hash = Hash256::from_slice(&[42; 32]);
|
|
||||||
let eth1_timestamp = 2_u64.pow(40);
|
let eth1_timestamp = 2_u64.pow(40);
|
||||||
let amount = spec.max_effective_balance;
|
let amount = spec.max_effective_balance;
|
||||||
|
|
||||||
@ -155,7 +186,7 @@ fn interop_genesis_state<T: EthSpec>(
|
|||||||
.map(|keypair| {
|
.map(|keypair| {
|
||||||
let mut data = DepositData {
|
let mut data = DepositData {
|
||||||
withdrawal_credentials: withdrawal_credentials(&keypair.pk),
|
withdrawal_credentials: withdrawal_credentials(&keypair.pk),
|
||||||
pubkey: keypair.pk.into(),
|
pubkey: keypair.pk.clone().into(),
|
||||||
amount,
|
amount,
|
||||||
signature: Signature::empty_signature().into(),
|
signature: Signature::empty_signature().into(),
|
||||||
};
|
};
|
||||||
@ -212,6 +243,9 @@ fn interop_genesis_state<T: EthSpec>(
|
|||||||
|
|
||||||
state.genesis_time = genesis_time;
|
state.genesis_time = genesis_time;
|
||||||
|
|
||||||
|
// Invalid all the caches after all the manual state surgery.
|
||||||
|
state.drop_all_caches();
|
||||||
|
|
||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +271,7 @@ fn recent_genesis_time(minutes: u64) -> u64 {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use types::{EthSpec, MinimalEthSpec};
|
use types::{test_utils::generate_deterministic_keypairs, EthSpec, MinimalEthSpec};
|
||||||
|
|
||||||
type TestEthSpec = MinimalEthSpec;
|
type TestEthSpec = MinimalEthSpec;
|
||||||
|
|
||||||
@ -247,12 +281,14 @@ mod test {
|
|||||||
let genesis_time = 42;
|
let genesis_time = 42;
|
||||||
let spec = &TestEthSpec::default_spec();
|
let spec = &TestEthSpec::default_spec();
|
||||||
|
|
||||||
let state = interop_genesis_state::<TestEthSpec>(validator_count, genesis_time, spec)
|
let keypairs = generate_deterministic_keypairs(validator_count);
|
||||||
|
|
||||||
|
let state = interop_genesis_state::<TestEthSpec>(&keypairs, genesis_time, spec)
|
||||||
.expect("should build state");
|
.expect("should build state");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.eth1_data.block_hash,
|
state.eth1_data.block_hash,
|
||||||
Hash256::from_slice(&[42; 32]),
|
Hash256::from_slice(&[0x42; 32]),
|
||||||
"eth1 block hash should be co-ordinated junk"
|
"eth1 block hash should be co-ordinated junk"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::eth1_chain::Error as Eth1ChainError;
|
||||||
use crate::fork_choice::Error as ForkChoiceError;
|
use crate::fork_choice::Error as ForkChoiceError;
|
||||||
use state_processing::per_block_processing::errors::AttestationValidationError;
|
use state_processing::per_block_processing::errors::AttestationValidationError;
|
||||||
use state_processing::BlockProcessingError;
|
use state_processing::BlockProcessingError;
|
||||||
@ -33,6 +34,7 @@ pub enum BeaconChainError {
|
|||||||
MissingBeaconBlock(Hash256),
|
MissingBeaconBlock(Hash256),
|
||||||
MissingBeaconState(Hash256),
|
MissingBeaconState(Hash256),
|
||||||
SlotProcessingError(SlotProcessingError),
|
SlotProcessingError(SlotProcessingError),
|
||||||
|
UnableToAdvanceState(String),
|
||||||
NoStateForAttestation {
|
NoStateForAttestation {
|
||||||
beacon_block_root: Hash256,
|
beacon_block_root: Hash256,
|
||||||
},
|
},
|
||||||
@ -42,6 +44,7 @@ pub enum BeaconChainError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
easy_from_to!(SlotProcessingError, BeaconChainError);
|
easy_from_to!(SlotProcessingError, BeaconChainError);
|
||||||
|
easy_from_to!(AttestationValidationError, BeaconChainError);
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum BlockProductionError {
|
pub enum BlockProductionError {
|
||||||
@ -50,10 +53,11 @@ pub enum BlockProductionError {
|
|||||||
UnableToProduceAtSlot(Slot),
|
UnableToProduceAtSlot(Slot),
|
||||||
SlotProcessingError(SlotProcessingError),
|
SlotProcessingError(SlotProcessingError),
|
||||||
BlockProcessingError(BlockProcessingError),
|
BlockProcessingError(BlockProcessingError),
|
||||||
|
Eth1ChainError(Eth1ChainError),
|
||||||
BeaconStateError(BeaconStateError),
|
BeaconStateError(BeaconStateError),
|
||||||
}
|
}
|
||||||
|
|
||||||
easy_from_to!(BlockProcessingError, BlockProductionError);
|
easy_from_to!(BlockProcessingError, BlockProductionError);
|
||||||
easy_from_to!(BeaconStateError, BlockProductionError);
|
easy_from_to!(BeaconStateError, BlockProductionError);
|
||||||
easy_from_to!(SlotProcessingError, BlockProductionError);
|
easy_from_to!(SlotProcessingError, BlockProductionError);
|
||||||
easy_from_to!(AttestationValidationError, BeaconChainError);
|
easy_from_to!(Eth1ChainError, BlockProductionError);
|
||||||
|
110
beacon_node/beacon_chain/src/eth1_chain.rs
Normal file
110
beacon_node/beacon_chain/src/eth1_chain.rs
Normal 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
|
||||||
|
}
|
@ -6,6 +6,7 @@ mod beacon_chain;
|
|||||||
mod beacon_chain_builder;
|
mod beacon_chain_builder;
|
||||||
mod checkpoint;
|
mod checkpoint;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
mod eth1_chain;
|
||||||
mod fork_choice;
|
mod fork_choice;
|
||||||
mod iter;
|
mod iter;
|
||||||
mod metrics;
|
mod metrics;
|
||||||
@ -18,6 +19,7 @@ pub use self::beacon_chain::{
|
|||||||
pub use self::checkpoint::CheckPoint;
|
pub use self::checkpoint::CheckPoint;
|
||||||
pub use self::errors::{BeaconChainError, BlockProductionError};
|
pub use self::errors::{BeaconChainError, BlockProductionError};
|
||||||
pub use beacon_chain_builder::BeaconChainBuilder;
|
pub use beacon_chain_builder::BeaconChainBuilder;
|
||||||
|
pub use eth1_chain::{Eth1ChainBackend, InteropEth1ChainBackend};
|
||||||
pub use lmd_ghost;
|
pub use lmd_ghost;
|
||||||
pub use metrics::scrape_for_metrics;
|
pub use metrics::scrape_for_metrics;
|
||||||
pub use parking_lot;
|
pub use parking_lot;
|
||||||
|
@ -1,22 +1,28 @@
|
|||||||
use crate::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome};
|
use crate::{
|
||||||
|
AttestationProcessingOutcome, BeaconChain, BeaconChainBuilder, BeaconChainTypes,
|
||||||
|
BlockProcessingOutcome, InteropEth1ChainBackend,
|
||||||
|
};
|
||||||
use lmd_ghost::LmdGhost;
|
use lmd_ghost::LmdGhost;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use sloggers::{null::NullLoggerBuilder, Build};
|
use sloggers::{terminal::TerminalLoggerBuilder, types::Severity, Build};
|
||||||
use slot_clock::TestingSlotClock;
|
use slot_clock::TestingSlotClock;
|
||||||
use state_processing::per_slot_processing;
|
use state_processing::per_slot_processing;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use store::MemoryStore;
|
use store::MemoryStore;
|
||||||
use store::Store;
|
|
||||||
use tree_hash::{SignedRoot, TreeHash};
|
use tree_hash::{SignedRoot, TreeHash};
|
||||||
use types::{
|
use types::{
|
||||||
test_utils::TestingBeaconStateBuilder, AggregateSignature, Attestation,
|
AggregateSignature, Attestation, AttestationDataAndCustodyBit, BeaconBlock, BeaconState,
|
||||||
AttestationDataAndCustodyBit, BeaconBlock, BeaconState, BitList, ChainSpec, Domain, EthSpec,
|
BitList, ChainSpec, Domain, EthSpec, Hash256, Keypair, RelativeEpoch, SecretKey, Signature,
|
||||||
Hash256, Keypair, RelativeEpoch, SecretKey, Signature, Slot,
|
Slot,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use types::test_utils::generate_deterministic_keypairs;
|
||||||
|
|
||||||
pub use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY};
|
pub use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY};
|
||||||
|
|
||||||
|
pub const HARNESS_GENESIS_TIME: u64 = 1567552690; // 4th September 2019
|
||||||
|
|
||||||
/// Indicates how the `BeaconChainHarness` should produce blocks.
|
/// Indicates how the `BeaconChainHarness` should produce blocks.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum BlockStrategy {
|
pub enum BlockStrategy {
|
||||||
@ -60,6 +66,7 @@ where
|
|||||||
type Store = MemoryStore;
|
type Store = MemoryStore;
|
||||||
type SlotClock = TestingSlotClock;
|
type SlotClock = TestingSlotClock;
|
||||||
type LmdGhost = L;
|
type LmdGhost = L;
|
||||||
|
type Eth1Chain = InteropEth1ChainBackend<E>;
|
||||||
type EthSpec = E;
|
type EthSpec = E;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,40 +90,21 @@ where
|
|||||||
E: EthSpec,
|
E: EthSpec,
|
||||||
{
|
{
|
||||||
/// Instantiate a new harness with `validator_count` initial validators.
|
/// Instantiate a new harness with `validator_count` initial validators.
|
||||||
pub fn new(validator_count: usize) -> Self {
|
pub fn new(keypairs: Vec<Keypair>) -> Self {
|
||||||
let state_builder = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(
|
|
||||||
validator_count,
|
|
||||||
&E::default_spec(),
|
|
||||||
);
|
|
||||||
let (genesis_state, keypairs) = state_builder.build();
|
|
||||||
|
|
||||||
Self::from_state_and_keypairs(genesis_state, keypairs)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Instantiate a new harness with an initial validator for each key supplied.
|
|
||||||
pub fn from_keypairs(keypairs: Vec<Keypair>) -> Self {
|
|
||||||
let state_builder = TestingBeaconStateBuilder::from_keypairs(keypairs, &E::default_spec());
|
|
||||||
let (genesis_state, keypairs) = state_builder.build();
|
|
||||||
|
|
||||||
Self::from_state_and_keypairs(genesis_state, keypairs)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Instantiate a new harness with the given genesis state and a keypair for each of the
|
|
||||||
/// initial validators in the given state.
|
|
||||||
pub fn from_state_and_keypairs(genesis_state: BeaconState<E>, keypairs: Vec<Keypair>) -> Self {
|
|
||||||
let spec = E::default_spec();
|
let spec = E::default_spec();
|
||||||
|
|
||||||
|
let log = TerminalLoggerBuilder::new()
|
||||||
|
.level(Severity::Warning)
|
||||||
|
.build()
|
||||||
|
.expect("logger should build");
|
||||||
|
|
||||||
let store = Arc::new(MemoryStore::open());
|
let store = Arc::new(MemoryStore::open());
|
||||||
|
|
||||||
let mut genesis_block = BeaconBlock::empty(&spec);
|
|
||||||
genesis_block.state_root = Hash256::from_slice(&genesis_state.tree_hash_root());
|
|
||||||
|
|
||||||
let builder = NullLoggerBuilder;
|
|
||||||
let log = builder.build().expect("logger should build");
|
|
||||||
|
|
||||||
let chain =
|
let chain =
|
||||||
BeaconChain::from_genesis(store, genesis_state, genesis_block, spec.clone(), log)
|
BeaconChainBuilder::quick_start(HARNESS_GENESIS_TIME, &keypairs, spec.clone(), log)
|
||||||
.expect("Terminate if beacon chain generation fails");
|
.unwrap_or_else(|e| panic!("Failed to create beacon chain builder: {}", e))
|
||||||
|
.build(store.clone(), InteropEth1ChainBackend::default())
|
||||||
|
.unwrap_or_else(|e| panic!("Failed to build beacon chain: {}", e));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
chain,
|
chain,
|
||||||
@ -156,7 +144,10 @@ where
|
|||||||
BlockStrategy::ForkCanonicalChainAt { previous_slot, .. } => previous_slot,
|
BlockStrategy::ForkCanonicalChainAt { previous_slot, .. } => previous_slot,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.get_state_at_slot(state_slot)
|
self.chain
|
||||||
|
.state_at_slot(state_slot)
|
||||||
|
.expect("should find state for slot")
|
||||||
|
.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine the first slot where a block should be built.
|
// Determine the first slot where a block should be built.
|
||||||
@ -194,21 +185,6 @@ where
|
|||||||
head_block_root.expect("did not produce any blocks")
|
head_block_root.expect("did not produce any blocks")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_state_at_slot(&self, state_slot: Slot) -> BeaconState<E> {
|
|
||||||
let state_root = self
|
|
||||||
.chain
|
|
||||||
.rev_iter_state_roots()
|
|
||||||
.find(|(_hash, slot)| *slot == state_slot)
|
|
||||||
.map(|(hash, _slot)| hash)
|
|
||||||
.expect("could not find state root");
|
|
||||||
|
|
||||||
self.chain
|
|
||||||
.store
|
|
||||||
.get(&state_root)
|
|
||||||
.expect("should read db")
|
|
||||||
.expect("should find state root")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a newly created block, signed by the proposer for the given slot.
|
/// Returns a newly created block, signed by the proposer for the given slot.
|
||||||
fn build_block(
|
fn build_block(
|
||||||
&self,
|
&self,
|
||||||
@ -282,9 +258,14 @@ where
|
|||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|attestation| {
|
.for_each(|attestation| {
|
||||||
self.chain
|
match self
|
||||||
|
.chain
|
||||||
.process_attestation(attestation)
|
.process_attestation(attestation)
|
||||||
.expect("should process attestation");
|
.expect("should not error during attestation processing")
|
||||||
|
{
|
||||||
|
AttestationProcessingOutcome::Processed => (),
|
||||||
|
other => panic!("did not successfully process attestation: {:?}", other),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,14 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
use beacon_chain::test_utils::{
|
use beacon_chain::AttestationProcessingOutcome;
|
||||||
|
use beacon_chain::{
|
||||||
|
test_utils::{
|
||||||
AttestationStrategy, BeaconChainHarness, BlockStrategy, CommonTypes, PersistedBeaconChain,
|
AttestationStrategy, BeaconChainHarness, BlockStrategy, CommonTypes, PersistedBeaconChain,
|
||||||
BEACON_CHAIN_DB_KEY,
|
BEACON_CHAIN_DB_KEY,
|
||||||
|
},
|
||||||
|
BlockProcessingOutcome,
|
||||||
};
|
};
|
||||||
use beacon_chain::AttestationProcessingOutcome;
|
|
||||||
use lmd_ghost::ThreadSafeReducedTree;
|
use lmd_ghost::ThreadSafeReducedTree;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use store::{MemoryStore, Store};
|
use store::{MemoryStore, Store};
|
||||||
@ -25,7 +28,7 @@ lazy_static! {
|
|||||||
type TestForkChoice = ThreadSafeReducedTree<MemoryStore, MinimalEthSpec>;
|
type TestForkChoice = ThreadSafeReducedTree<MemoryStore, MinimalEthSpec>;
|
||||||
|
|
||||||
fn get_harness(validator_count: usize) -> BeaconChainHarness<TestForkChoice, MinimalEthSpec> {
|
fn get_harness(validator_count: usize) -> BeaconChainHarness<TestForkChoice, MinimalEthSpec> {
|
||||||
let harness = BeaconChainHarness::from_keypairs(KEYPAIRS[0..validator_count].to_vec());
|
let harness = BeaconChainHarness::new(KEYPAIRS[0..validator_count].to_vec());
|
||||||
|
|
||||||
harness.advance_slot();
|
harness.advance_slot();
|
||||||
|
|
||||||
@ -461,3 +464,48 @@ fn free_attestations_added_to_fork_choice_all_updated() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_skip_slot_test(skip_slots: u64) {
|
||||||
|
let num_validators = 8;
|
||||||
|
let harness_a = get_harness(num_validators);
|
||||||
|
let harness_b = get_harness(num_validators);
|
||||||
|
|
||||||
|
for _ in 0..skip_slots {
|
||||||
|
harness_a.advance_slot();
|
||||||
|
harness_b.advance_slot();
|
||||||
|
}
|
||||||
|
|
||||||
|
harness_a.extend_chain(
|
||||||
|
1,
|
||||||
|
BlockStrategy::OnCanonicalHead,
|
||||||
|
// No attestation required for test.
|
||||||
|
AttestationStrategy::SomeValidators(vec![]),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
harness_a.chain.head().beacon_block.slot,
|
||||||
|
Slot::new(skip_slots + 1)
|
||||||
|
);
|
||||||
|
assert_eq!(harness_b.chain.head().beacon_block.slot, Slot::new(0));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
harness_b
|
||||||
|
.chain
|
||||||
|
.process_block(harness_a.chain.head().beacon_block.clone()),
|
||||||
|
Ok(BlockProcessingOutcome::Processed {
|
||||||
|
block_root: harness_a.chain.head().beacon_block_root
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
harness_b.chain.head().beacon_block.slot,
|
||||||
|
Slot::new(skip_slots + 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn produces_and_processes_with_genesis_skip_slots() {
|
||||||
|
for i in 0..MinimalEthSpec::slots_per_epoch() * 4 {
|
||||||
|
run_skip_slot_test(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,6 +23,7 @@ pub struct Config {
|
|||||||
/// files. It can only be configured via the CLI.
|
/// files. It can only be configured via the CLI.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub beacon_chain_start_method: BeaconChainStartMethod,
|
pub beacon_chain_start_method: BeaconChainStartMethod,
|
||||||
|
pub eth1_backend_method: Eth1BackendMethod,
|
||||||
pub network: network::NetworkConfig,
|
pub network: network::NetworkConfig,
|
||||||
pub rpc: rpc::RPCConfig,
|
pub rpc: rpc::RPCConfig,
|
||||||
pub rest_api: rest_api::ApiConfig,
|
pub rest_api: rest_api::ApiConfig,
|
||||||
@ -54,6 +55,10 @@ pub enum BeaconChainStartMethod {
|
|||||||
},
|
},
|
||||||
/// Create a new beacon chain by loading a YAML-encoded genesis state from a file.
|
/// Create a new beacon chain by loading a YAML-encoded genesis state from a file.
|
||||||
Yaml { file: PathBuf },
|
Yaml { file: PathBuf },
|
||||||
|
/// Create a new beacon chain by loading a SSZ-encoded genesis state from a file.
|
||||||
|
Ssz { file: PathBuf },
|
||||||
|
/// Create a new beacon chain by loading a JSON-encoded genesis state from a file.
|
||||||
|
Json { file: PathBuf },
|
||||||
/// Create a new beacon chain by using a HTTP server (running our REST-API) to load genesis and
|
/// Create a new beacon chain by using a HTTP server (running our REST-API) to load genesis and
|
||||||
/// finalized states and blocks.
|
/// finalized states and blocks.
|
||||||
HttpBootstrap { server: String, port: Option<u16> },
|
HttpBootstrap { server: String, port: Option<u16> },
|
||||||
@ -65,6 +70,22 @@ impl Default for BeaconChainStartMethod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines which Eth1 backend the client should use.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Eth1BackendMethod {
|
||||||
|
/// Use the mocked eth1 backend used in interop testing
|
||||||
|
Interop,
|
||||||
|
/// Use a web3 connection to a running Eth1 node.
|
||||||
|
Web3 { server: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Eth1BackendMethod {
|
||||||
|
fn default() -> Self {
|
||||||
|
Eth1BackendMethod::Interop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -77,6 +98,7 @@ impl Default for Config {
|
|||||||
rest_api: <_>::default(),
|
rest_api: <_>::default(),
|
||||||
spec_constants: TESTNET_SPEC_CONSTANTS.into(),
|
spec_constants: TESTNET_SPEC_CONSTANTS.into(),
|
||||||
beacon_chain_start_method: <_>::default(),
|
beacon_chain_start_method: <_>::default(),
|
||||||
|
eth1_backend_method: <_>::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ pub mod error;
|
|||||||
pub mod notifier;
|
pub mod notifier;
|
||||||
|
|
||||||
use beacon_chain::{
|
use beacon_chain::{
|
||||||
lmd_ghost::ThreadSafeReducedTree, slot_clock::SystemTimeSlotClock, store::Store, BeaconChain,
|
lmd_ghost::ThreadSafeReducedTree, slot_clock::SystemTimeSlotClock, store::Store,
|
||||||
BeaconChainBuilder,
|
test_utils::generate_deterministic_keypairs, BeaconChain, BeaconChainBuilder,
|
||||||
};
|
};
|
||||||
use exit_future::Signal;
|
use exit_future::Signal;
|
||||||
use futures::{future::Future, Stream};
|
use futures::{future::Future, Stream};
|
||||||
@ -21,14 +21,14 @@ use tokio::runtime::TaskExecutor;
|
|||||||
use tokio::timer::Interval;
|
use tokio::timer::Interval;
|
||||||
use types::EthSpec;
|
use types::EthSpec;
|
||||||
|
|
||||||
pub use beacon_chain::BeaconChainTypes;
|
pub use beacon_chain::{BeaconChainTypes, Eth1ChainBackend, InteropEth1ChainBackend};
|
||||||
pub use config::{BeaconChainStartMethod, Config as ClientConfig};
|
pub use config::{BeaconChainStartMethod, Config as ClientConfig, Eth1BackendMethod};
|
||||||
pub use eth2_config::Eth2Config;
|
pub use eth2_config::Eth2Config;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ClientType<S: Store, E: EthSpec> {
|
pub struct ClientType<S: Store, E: EthSpec> {
|
||||||
_phantom_t: PhantomData<S>,
|
_phantom_s: PhantomData<S>,
|
||||||
_phantom_u: PhantomData<E>,
|
_phantom_e: PhantomData<E>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, E> BeaconChainTypes for ClientType<S, E>
|
impl<S, E> BeaconChainTypes for ClientType<S, E>
|
||||||
@ -39,6 +39,7 @@ where
|
|||||||
type Store = S;
|
type Store = S;
|
||||||
type SlotClock = SystemTimeSlotClock;
|
type SlotClock = SystemTimeSlotClock;
|
||||||
type LmdGhost = ThreadSafeReducedTree<S, E>;
|
type LmdGhost = ThreadSafeReducedTree<S, E>;
|
||||||
|
type Eth1Chain = InteropEth1ChainBackend<E>;
|
||||||
type EthSpec = E;
|
type EthSpec = E;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +106,7 @@ where
|
|||||||
"method" => "recent"
|
"method" => "recent"
|
||||||
);
|
);
|
||||||
BeaconChainBuilder::recent_genesis(
|
BeaconChainBuilder::recent_genesis(
|
||||||
*validator_count,
|
&generate_deterministic_keypairs(*validator_count),
|
||||||
*minutes,
|
*minutes,
|
||||||
spec.clone(),
|
spec.clone(),
|
||||||
log.clone(),
|
log.clone(),
|
||||||
@ -124,7 +125,7 @@ where
|
|||||||
);
|
);
|
||||||
BeaconChainBuilder::quick_start(
|
BeaconChainBuilder::quick_start(
|
||||||
*genesis_time,
|
*genesis_time,
|
||||||
*validator_count,
|
&generate_deterministic_keypairs(*validator_count),
|
||||||
spec.clone(),
|
spec.clone(),
|
||||||
log.clone(),
|
log.clone(),
|
||||||
)?
|
)?
|
||||||
@ -138,6 +139,24 @@ where
|
|||||||
);
|
);
|
||||||
BeaconChainBuilder::yaml_state(file, spec.clone(), log.clone())?
|
BeaconChainBuilder::yaml_state(file, spec.clone(), log.clone())?
|
||||||
}
|
}
|
||||||
|
BeaconChainStartMethod::Ssz { file } => {
|
||||||
|
info!(
|
||||||
|
log,
|
||||||
|
"Starting beacon chain";
|
||||||
|
"file" => format!("{:?}", file),
|
||||||
|
"method" => "ssz"
|
||||||
|
);
|
||||||
|
BeaconChainBuilder::ssz_state(file, spec.clone(), log.clone())?
|
||||||
|
}
|
||||||
|
BeaconChainStartMethod::Json { file } => {
|
||||||
|
info!(
|
||||||
|
log,
|
||||||
|
"Starting beacon chain";
|
||||||
|
"file" => format!("{:?}", file),
|
||||||
|
"method" => "json"
|
||||||
|
);
|
||||||
|
BeaconChainBuilder::json_state(file, spec.clone(), log.clone())?
|
||||||
|
}
|
||||||
BeaconChainStartMethod::HttpBootstrap { server, port } => {
|
BeaconChainStartMethod::HttpBootstrap { server, port } => {
|
||||||
info!(
|
info!(
|
||||||
log,
|
log,
|
||||||
@ -150,9 +169,11 @@ where
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let eth1_backend = T::Eth1Chain::new(String::new()).map_err(|e| format!("{:?}", e))?;
|
||||||
|
|
||||||
let beacon_chain: Arc<BeaconChain<T>> = Arc::new(
|
let beacon_chain: Arc<BeaconChain<T>> = Arc::new(
|
||||||
beacon_chain_builder
|
beacon_chain_builder
|
||||||
.build(store)
|
.build(store, eth1_backend)
|
||||||
.map_err(error::Error::from)?,
|
.map_err(error::Error::from)?,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -914,7 +914,7 @@ fn process_blocks<T: BeaconChainTypes>(
|
|||||||
BlockProcessingOutcome::ParentUnknown { parent } => {
|
BlockProcessingOutcome::ParentUnknown { parent } => {
|
||||||
// blocks should be sequential and all parents should exist
|
// blocks should be sequential and all parents should exist
|
||||||
trace!(
|
trace!(
|
||||||
log, "ParentBlockUnknown";
|
log, "Parent block is unknown";
|
||||||
"parent_root" => format!("{}", parent),
|
"parent_root" => format!("{}", parent),
|
||||||
"baby_block_slot" => block.slot,
|
"baby_block_slot" => block.slot,
|
||||||
);
|
);
|
||||||
@ -923,6 +923,13 @@ fn process_blocks<T: BeaconChainTypes>(
|
|||||||
block.slot
|
block.slot
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
BlockProcessingOutcome::BlockIsAlreadyKnown => {
|
||||||
|
// this block is already known to us, move to the next
|
||||||
|
debug!(
|
||||||
|
log, "Imported a block that is already known";
|
||||||
|
"parent_root" => format!("{}", parent),
|
||||||
|
"baby_block_slot" => block.slot,
|
||||||
|
);
|
||||||
BlockProcessingOutcome::FutureSlot {
|
BlockProcessingOutcome::FutureSlot {
|
||||||
present_slot,
|
present_slot,
|
||||||
block_slot,
|
block_slot,
|
||||||
@ -930,7 +937,7 @@ fn process_blocks<T: BeaconChainTypes>(
|
|||||||
if present_slot + FUTURE_SLOT_TOLERANCE >= block_slot {
|
if present_slot + FUTURE_SLOT_TOLERANCE >= block_slot {
|
||||||
// The block is too far in the future, drop it.
|
// The block is too far in the future, drop it.
|
||||||
trace!(
|
trace!(
|
||||||
log, "FutureBlock";
|
self.log, "Block is ahead of our slot clock";
|
||||||
"msg" => "block for future slot rejected, check your time",
|
"msg" => "block for future slot rejected, check your time",
|
||||||
"present_slot" => present_slot,
|
"present_slot" => present_slot,
|
||||||
"block_slot" => block_slot,
|
"block_slot" => block_slot,
|
||||||
@ -943,14 +950,26 @@ fn process_blocks<T: BeaconChainTypes>(
|
|||||||
} else {
|
} else {
|
||||||
// The block is in the future, but not too far.
|
// The block is in the future, but not too far.
|
||||||
trace!(
|
trace!(
|
||||||
log, "QueuedFutureBlock";
|
self.log, "Block is slightly ahead of our slot clock, ignoring.";
|
||||||
"msg" => "queuing future block, check your time",
|
|
||||||
"present_slot" => present_slot,
|
"present_slot" => present_slot,
|
||||||
"block_slot" => block_slot,
|
"block_slot" => block_slot,
|
||||||
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
|
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BlockProcessingOutcome::WouldRevertFinalizedSlot { .. } => {
|
||||||
|
trace!(
|
||||||
|
self.log, "Finalized or earlier block processed";
|
||||||
|
"outcome" => format!("{:?}", outcome),
|
||||||
|
);
|
||||||
|
// block reached our finalized slot or was earlier, move to the next block
|
||||||
|
}
|
||||||
|
BlockProcessingOutcome::GenesisBlock => {
|
||||||
|
trace!(
|
||||||
|
self.log, "Genesis block was processed";
|
||||||
|
"outcome" => format!("{:?}", outcome),
|
||||||
|
);
|
||||||
|
}
|
||||||
BlockProcessingOutcome::FinalizedSlot => {
|
BlockProcessingOutcome::FinalizedSlot => {
|
||||||
trace!(
|
trace!(
|
||||||
log, "Finalized or earlier block processed";
|
log, "Finalized or earlier block processed";
|
||||||
@ -959,8 +978,8 @@ fn process_blocks<T: BeaconChainTypes>(
|
|||||||
// block reached our finalized slot or was earlier, move to the next block
|
// block reached our finalized slot or was earlier, move to the next block
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
trace!(
|
warn!(
|
||||||
log, "InvalidBlock";
|
log, "Invalid block received";
|
||||||
"msg" => "peer sent invalid block",
|
"msg" => "peer sent invalid block",
|
||||||
"outcome" => format!("{:?}", outcome),
|
"outcome" => format!("{:?}", outcome),
|
||||||
);
|
);
|
||||||
@ -968,7 +987,7 @@ fn process_blocks<T: BeaconChainTypes>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trace!(
|
warn!(
|
||||||
log, "BlockProcessingFailure";
|
log, "BlockProcessingFailure";
|
||||||
"msg" => "unexpected condition in processing block.",
|
"msg" => "unexpected condition in processing block.",
|
||||||
"outcome" => format!("{:?}", processing_result)
|
"outcome" => format!("{:?}", processing_result)
|
||||||
|
@ -385,7 +385,7 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
"peer" => format!("{:?}", peer_id),
|
"peer" => format!("{:?}", peer_id),
|
||||||
"msg" => "Failed to return all requested hashes",
|
"msg" => "Failed to return all requested hashes",
|
||||||
"start_slot" => req.start_slot,
|
"start_slot" => req.start_slot,
|
||||||
"current_slot" => self.chain.best_slot(),
|
"current_slot" => format!("{:?}", self.chain.slot()),
|
||||||
"requested" => req.count,
|
"requested" => req.count,
|
||||||
"returned" => blocks.len(),
|
"returned" => blocks.len(),
|
||||||
);
|
);
|
||||||
@ -523,6 +523,12 @@ impl NetworkContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn disconnect(&mut self, peer_id: PeerId, reason: GoodbyeReason) {
|
pub fn disconnect(&mut self, peer_id: PeerId, reason: GoodbyeReason) {
|
||||||
|
warn!(
|
||||||
|
&self.log,
|
||||||
|
"Disconnecting peer (RPC)";
|
||||||
|
"reason" => format!("{:?}", reason),
|
||||||
|
"peer_id" => format!("{:?}", peer_id),
|
||||||
|
);
|
||||||
self.send_rpc_request(None, peer_id, RPCRequest::Goodbye(reason))
|
self.send_rpc_request(None, peer_id, RPCRequest::Goodbye(reason))
|
||||||
// TODO: disconnect peers.
|
// TODO: disconnect peers.
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,12 @@ store = { path = "../store" }
|
|||||||
version = { path = "../version" }
|
version = { path = "../version" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "^1.0"
|
serde_json = "^1.0"
|
||||||
|
serde_yaml = "0.8"
|
||||||
slog = "^2.2.3"
|
slog = "^2.2.3"
|
||||||
slog-term = "^2.4.0"
|
slog-term = "^2.4.0"
|
||||||
slog-async = "^2.3.0"
|
slog-async = "^2.3.0"
|
||||||
|
eth2_ssz = { path = "../../eth2/utils/ssz" }
|
||||||
|
eth2_ssz_derive = { path = "../../eth2/utils/ssz_derive" }
|
||||||
state_processing = { path = "../../eth2/state_processing" }
|
state_processing = { path = "../../eth2/state_processing" }
|
||||||
types = { path = "../../eth2/types" }
|
types = { path = "../../eth2/types" }
|
||||||
clap = "2.32.0"
|
clap = "2.32.0"
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use super::{success_response, ApiResult};
|
use super::{success_response, ApiResult, ResponseBuilder};
|
||||||
use crate::{helpers::*, ApiError, UrlQuery};
|
use crate::{helpers::*, ApiError, UrlQuery};
|
||||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
use hyper::{Body, Request};
|
use hyper::{Body, Request};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use ssz_derive::Encode;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use store::Store;
|
use store::Store;
|
||||||
use types::{BeaconBlock, BeaconState, EthSpec, Hash256, Slot};
|
use types::{BeaconBlock, BeaconState, EthSpec, Hash256, Slot};
|
||||||
@ -33,7 +34,7 @@ pub fn get_head<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult
|
|||||||
Ok(success_response(Body::from(json)))
|
Ok(success_response(Body::from(json)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Encode)]
|
||||||
#[serde(bound = "T: EthSpec")]
|
#[serde(bound = "T: EthSpec")]
|
||||||
pub struct BlockResponse<T: EthSpec> {
|
pub struct BlockResponse<T: EthSpec> {
|
||||||
pub root: Hash256,
|
pub root: Hash256,
|
||||||
@ -77,11 +78,7 @@ pub fn get_block<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult
|
|||||||
beacon_block: block,
|
beacon_block: block,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json: String = serde_json::to_string(&response).map_err(|e| {
|
ResponseBuilder::new(&req).body(&response)
|
||||||
ApiError::ServerError(format!("Unable to serialize BlockResponse: {:?}", e))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(success_response(Body::from(json)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HTTP handler to return a `BeaconBlock` root at a given `slot`.
|
/// HTTP handler to return a `BeaconBlock` root at a given `slot`.
|
||||||
@ -104,7 +101,22 @@ pub fn get_block_root<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiR
|
|||||||
Ok(success_response(Body::from(json)))
|
Ok(success_response(Body::from(json)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
/// HTTP handler to return a `BeaconState` at a given `root` or `slot`.
|
||||||
|
///
|
||||||
|
/// Will not return a state if the request slot is in the future. Will return states higher than
|
||||||
|
/// the current head by skipping slots.
|
||||||
|
pub fn get_genesis_state<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult {
|
||||||
|
let beacon_chain = req
|
||||||
|
.extensions()
|
||||||
|
.get::<Arc<BeaconChain<T>>>()
|
||||||
|
.ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?;
|
||||||
|
|
||||||
|
let (_root, state) = state_at_slot(&beacon_chain, Slot::new(0))?;
|
||||||
|
|
||||||
|
ResponseBuilder::new(&req).body(&state)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Encode)]
|
||||||
#[serde(bound = "T: EthSpec")]
|
#[serde(bound = "T: EthSpec")]
|
||||||
pub struct StateResponse<T: EthSpec> {
|
pub struct StateResponse<T: EthSpec> {
|
||||||
pub root: Hash256,
|
pub root: Hash256,
|
||||||
@ -144,11 +156,7 @@ pub fn get_state<T: BeaconChainTypes + 'static>(req: Request<Body>) -> ApiResult
|
|||||||
beacon_state: state,
|
beacon_state: state,
|
||||||
};
|
};
|
||||||
|
|
||||||
let json: String = serde_json::to_string(&response).map_err(|e| {
|
ResponseBuilder::new(&req).body(&response)
|
||||||
ApiError::ServerError(format!("Unable to serialize StateResponse: {:?}", e))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(success_response(Body::from(json)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HTTP handler to return a `BeaconState` root at a given `slot`.
|
/// HTTP handler to return a `BeaconState` root at a given `slot`.
|
||||||
|
@ -8,6 +8,7 @@ mod helpers;
|
|||||||
mod metrics;
|
mod metrics;
|
||||||
mod network;
|
mod network;
|
||||||
mod node;
|
mod node;
|
||||||
|
mod response_builder;
|
||||||
mod spec;
|
mod spec;
|
||||||
mod url_query;
|
mod url_query;
|
||||||
mod validator;
|
mod validator;
|
||||||
@ -18,6 +19,7 @@ use eth2_config::Eth2Config;
|
|||||||
use hyper::rt::Future;
|
use hyper::rt::Future;
|
||||||
use hyper::service::service_fn_ok;
|
use hyper::service::service_fn_ok;
|
||||||
use hyper::{Body, Method, Response, Server, StatusCode};
|
use hyper::{Body, Method, Response, Server, StatusCode};
|
||||||
|
use response_builder::ResponseBuilder;
|
||||||
use slog::{info, o, warn};
|
use slog::{info, o, warn};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@ -145,6 +147,7 @@ pub fn start_server<T: BeaconChainTypes>(
|
|||||||
beacon::get_latest_finalized_checkpoint::<T>(req)
|
beacon::get_latest_finalized_checkpoint::<T>(req)
|
||||||
}
|
}
|
||||||
(&Method::GET, "/beacon/state") => beacon::get_state::<T>(req),
|
(&Method::GET, "/beacon/state") => beacon::get_state::<T>(req),
|
||||||
|
(&Method::GET, "/beacon/state/genesis") => beacon::get_genesis_state::<T>(req),
|
||||||
(&Method::GET, "/beacon/state_root") => beacon::get_state_root::<T>(req),
|
(&Method::GET, "/beacon/state_root") => beacon::get_state_root::<T>(req),
|
||||||
|
|
||||||
//TODO: Add aggreggate/filtered state lookups here, e.g. /beacon/validators/balances
|
//TODO: Add aggreggate/filtered state lookups here, e.g. /beacon/validators/balances
|
||||||
|
50
beacon_node/rest_api/src/response_builder.rs
Normal file
50
beacon_node/rest_api/src/response_builder.rs
Normal 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)))
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@ impl<T: BeaconChainTypes> ValidatorService for ValidatorServiceInstance<T> {
|
|||||||
let slot = epoch.start_slot(T::EthSpec::slots_per_epoch());
|
let slot = epoch.start_slot(T::EthSpec::slots_per_epoch());
|
||||||
|
|
||||||
let mut state = if let Ok(state) = self.chain.state_at_slot(slot) {
|
let mut state = if let Ok(state) = self.chain.state_at_slot(slot) {
|
||||||
state.as_ref().clone()
|
state.clone()
|
||||||
} else {
|
} else {
|
||||||
let log_clone = self.log.clone();
|
let log_clone = self.log.clone();
|
||||||
let f = sink
|
let f = sink
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use client::{BeaconChainStartMethod, ClientConfig, Eth2Config};
|
use client::{BeaconChainStartMethod, ClientConfig, Eth1BackendMethod, Eth2Config};
|
||||||
use eth2_config::{read_from_file, write_to_file};
|
use eth2_config::{read_from_file, write_to_file};
|
||||||
use lighthouse_bootstrap::Bootstrapper;
|
use lighthouse_bootstrap::Bootstrapper;
|
||||||
use rand::{distributions::Alphanumeric, Rng};
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
@ -25,6 +25,14 @@ type Config = (ClientConfig, Eth2Config);
|
|||||||
pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result<Config> {
|
pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result<Config> {
|
||||||
let mut builder = ConfigBuilder::new(cli_args, log)?;
|
let mut builder = ConfigBuilder::new(cli_args, log)?;
|
||||||
|
|
||||||
|
if let Some(server) = cli_args.value_of("eth1-server") {
|
||||||
|
builder.set_eth1_backend_method(Eth1BackendMethod::Web3 {
|
||||||
|
server: server.into(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
builder.set_eth1_backend_method(Eth1BackendMethod::Interop)
|
||||||
|
}
|
||||||
|
|
||||||
match cli_args.subcommand() {
|
match cli_args.subcommand() {
|
||||||
("testnet", Some(sub_cmd_args)) => {
|
("testnet", Some(sub_cmd_args)) => {
|
||||||
process_testnet_subcommand(&mut builder, sub_cmd_args, log)?
|
process_testnet_subcommand(&mut builder, sub_cmd_args, log)?
|
||||||
@ -70,11 +78,15 @@ fn process_testnet_subcommand(
|
|||||||
builder.set_random_datadir()?;
|
builder.set_random_datadir()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cli_args.is_present("force") {
|
||||||
|
builder.clean_datadir()?;
|
||||||
|
}
|
||||||
|
|
||||||
let is_bootstrap = cli_args.subcommand_name() == Some("bootstrap");
|
let is_bootstrap = cli_args.subcommand_name() == Some("bootstrap");
|
||||||
|
|
||||||
if let Some(path_string) = cli_args.value_of("eth2-config") {
|
if let Some(path_string) = cli_args.value_of("eth2-config") {
|
||||||
if is_bootstrap {
|
if is_bootstrap {
|
||||||
return Err("Cannot supply --eth2-config when using bootsrap".to_string());
|
return Err("Cannot supply --eth2-config when using bootstrap".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = path_string
|
let path = path_string
|
||||||
@ -85,6 +97,18 @@ fn process_testnet_subcommand(
|
|||||||
builder.update_spec_from_subcommand(&cli_args)?;
|
builder.update_spec_from_subcommand(&cli_args)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(slot_time) = cli_args.value_of("slot-time") {
|
||||||
|
if is_bootstrap {
|
||||||
|
return Err("Cannot supply --slot-time flag whilst using bootstrap.".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let slot_time = slot_time
|
||||||
|
.parse::<u64>()
|
||||||
|
.map_err(|e| format!("Unable to parse slot-time: {:?}", e))?;
|
||||||
|
|
||||||
|
builder.set_slot_time(slot_time);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(path_string) = cli_args.value_of("client-config") {
|
if let Some(path_string) = cli_args.value_of("client-config") {
|
||||||
let path = path_string
|
let path = path_string
|
||||||
.parse::<PathBuf>()
|
.parse::<PathBuf>()
|
||||||
@ -92,10 +116,6 @@ fn process_testnet_subcommand(
|
|||||||
builder.load_client_config(path)?;
|
builder.load_client_config(path)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli_args.is_present("force") {
|
|
||||||
builder.clean_datadir()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
log,
|
log,
|
||||||
"Creating new datadir";
|
"Creating new datadir";
|
||||||
@ -117,6 +137,7 @@ fn process_testnet_subcommand(
|
|||||||
.and_then(|s| s.parse::<u16>().ok());
|
.and_then(|s| s.parse::<u16>().ok());
|
||||||
|
|
||||||
builder.import_bootstrap_libp2p_address(server, port)?;
|
builder.import_bootstrap_libp2p_address(server, port)?;
|
||||||
|
builder.import_bootstrap_enr_address(server)?;
|
||||||
builder.import_bootstrap_eth2_config(server)?;
|
builder.import_bootstrap_eth2_config(server)?;
|
||||||
|
|
||||||
builder.set_beacon_chain_start_method(BeaconChainStartMethod::HttpBootstrap {
|
builder.set_beacon_chain_start_method(BeaconChainStartMethod::HttpBootstrap {
|
||||||
@ -160,6 +181,32 @@ fn process_testnet_subcommand(
|
|||||||
genesis_time,
|
genesis_time,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
("file", Some(cli_args)) => {
|
||||||
|
let file = cli_args
|
||||||
|
.value_of("file")
|
||||||
|
.ok_or_else(|| "No filename specified")?
|
||||||
|
.parse::<PathBuf>()
|
||||||
|
.map_err(|e| format!("Unable to parse filename: {:?}", e))?;
|
||||||
|
|
||||||
|
let format = cli_args
|
||||||
|
.value_of("format")
|
||||||
|
.ok_or_else(|| "No file format specified")?;
|
||||||
|
|
||||||
|
let start_method = match format {
|
||||||
|
"yaml" => BeaconChainStartMethod::Yaml { file },
|
||||||
|
"ssz" => BeaconChainStartMethod::Ssz { file },
|
||||||
|
"json" => BeaconChainStartMethod::Json { file },
|
||||||
|
other => return Err(format!("Unknown genesis file format: {}", other)),
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.set_beacon_chain_start_method(start_method)
|
||||||
|
}
|
||||||
|
(cmd, Some(_)) => {
|
||||||
|
return Err(format!(
|
||||||
|
"Invalid valid method specified: {}. See 'testnet --help'.",
|
||||||
|
cmd
|
||||||
|
))
|
||||||
|
}
|
||||||
_ => return Err("No testnet method specified. See 'testnet --help'.".into()),
|
_ => return Err("No testnet method specified. See 'testnet --help'.".into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -250,7 +297,12 @@ impl<'a> ConfigBuilder<'a> {
|
|||||||
self.client_config.beacon_chain_start_method = method;
|
self.client_config.beacon_chain_start_method = method;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import the libp2p address for `server` into the list of bootnodes in `self`.
|
/// Sets the method for starting the beacon chain.
|
||||||
|
pub fn set_eth1_backend_method(&mut self, method: Eth1BackendMethod) {
|
||||||
|
self.client_config.eth1_backend_method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import the libp2p address for `server` into the list of libp2p nodes to connect with.
|
||||||
///
|
///
|
||||||
/// If `port` is `Some`, it is used as the port for the `Multiaddr`. If `port` is `None`,
|
/// If `port` is `Some`, it is used as the port for the `Multiaddr`. If `port` is `None`,
|
||||||
/// attempts to connect to the `server` via HTTP and retrieve it's libp2p listen port.
|
/// attempts to connect to the `server` via HTTP and retrieve it's libp2p listen port.
|
||||||
@ -259,7 +311,7 @@ impl<'a> ConfigBuilder<'a> {
|
|||||||
server: &str,
|
server: &str,
|
||||||
port: Option<u16>,
|
port: Option<u16>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let bootstrapper = Bootstrapper::from_server_string(server.to_string())?;
|
let bootstrapper = Bootstrapper::connect(server.to_string(), &self.log)?;
|
||||||
|
|
||||||
if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr(port) {
|
if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr(port) {
|
||||||
info!(
|
info!(
|
||||||
@ -282,6 +334,28 @@ impl<'a> ConfigBuilder<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Import the enr address for `server` into the list of initial enrs (boot nodes).
|
||||||
|
pub fn import_bootstrap_enr_address(&mut self, server: &str) -> Result<()> {
|
||||||
|
let bootstrapper = Bootstrapper::connect(server.to_string(), &self.log)?;
|
||||||
|
|
||||||
|
if let Ok(enr) = bootstrapper.enr() {
|
||||||
|
info!(
|
||||||
|
self.log,
|
||||||
|
"Loaded bootstrapper libp2p address";
|
||||||
|
"enr" => format!("{:?}", enr)
|
||||||
|
);
|
||||||
|
|
||||||
|
self.client_config.network.boot_nodes.push(enr);
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
self.log,
|
||||||
|
"Unable to estimate a bootstrapper enr address, this node may not find any peers."
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the config data_dir to be an random directory.
|
/// Set the config data_dir to be an random directory.
|
||||||
///
|
///
|
||||||
/// Useful for easily spinning up ephemeral testnets.
|
/// Useful for easily spinning up ephemeral testnets.
|
||||||
@ -296,7 +370,7 @@ impl<'a> ConfigBuilder<'a> {
|
|||||||
|
|
||||||
/// Imports an `Eth2Config` from `server`, returning an error if this fails.
|
/// Imports an `Eth2Config` from `server`, returning an error if this fails.
|
||||||
pub fn import_bootstrap_eth2_config(&mut self, server: &str) -> Result<()> {
|
pub fn import_bootstrap_eth2_config(&mut self, server: &str) -> Result<()> {
|
||||||
let bootstrapper = Bootstrapper::from_server_string(server.to_string())?;
|
let bootstrapper = Bootstrapper::connect(server.to_string(), &self.log)?;
|
||||||
|
|
||||||
self.update_eth2_config(bootstrapper.eth2_config()?);
|
self.update_eth2_config(bootstrapper.eth2_config()?);
|
||||||
|
|
||||||
@ -307,6 +381,10 @@ impl<'a> ConfigBuilder<'a> {
|
|||||||
self.eth2_config = eth2_config;
|
self.eth2_config = eth2_config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_slot_time(&mut self, milliseconds_per_slot: u64) {
|
||||||
|
self.eth2_config.spec.milliseconds_per_slot = milliseconds_per_slot;
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads the subcommand and tries to update `self.eth2_config` based up on the `--spec` flag.
|
/// Reads the subcommand and tries to update `self.eth2_config` based up on the `--spec` flag.
|
||||||
///
|
///
|
||||||
/// Returns an error if the `--spec` flag is not present in the given `cli_args`.
|
/// Returns an error if the `--spec` flag is not present in the given `cli_args`.
|
||||||
|
@ -163,6 +163,16 @@ fn main() {
|
|||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Eth1 Integration
|
||||||
|
*/
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("eth1-server")
|
||||||
|
.long("eth1-server")
|
||||||
|
.value_name("SERVER")
|
||||||
|
.help("Specifies the server for a web3 connection to the Eth1 chain.")
|
||||||
|
.takes_value(true)
|
||||||
|
)
|
||||||
/*
|
/*
|
||||||
* Database parameters.
|
* Database parameters.
|
||||||
*/
|
*/
|
||||||
@ -235,6 +245,13 @@ fn main() {
|
|||||||
backup directory.")
|
backup directory.")
|
||||||
.conflicts_with("random-datadir")
|
.conflicts_with("random-datadir")
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("slot-time")
|
||||||
|
.long("slot-time")
|
||||||
|
.short("t")
|
||||||
|
.value_name("MILLISECONDS")
|
||||||
|
.help("Defines the slot time when creating a new testnet.")
|
||||||
|
)
|
||||||
/*
|
/*
|
||||||
* `boostrap`
|
* `boostrap`
|
||||||
*
|
*
|
||||||
@ -246,6 +263,7 @@ fn main() {
|
|||||||
.arg(Arg::with_name("server")
|
.arg(Arg::with_name("server")
|
||||||
.value_name("HTTP_SERVER")
|
.value_name("HTTP_SERVER")
|
||||||
.required(true)
|
.required(true)
|
||||||
|
.default_value("http://localhost:5052")
|
||||||
.help("A HTTP server, with a http:// prefix"))
|
.help("A HTTP server, with a http:// prefix"))
|
||||||
.arg(Arg::with_name("libp2p-port")
|
.arg(Arg::with_name("libp2p-port")
|
||||||
.short("p")
|
.short("p")
|
||||||
@ -298,9 +316,14 @@ fn main() {
|
|||||||
*
|
*
|
||||||
* Start a new node, using a genesis state loaded from a YAML file
|
* Start a new node, using a genesis state loaded from a YAML file
|
||||||
*/
|
*/
|
||||||
.subcommand(SubCommand::with_name("yaml")
|
.subcommand(SubCommand::with_name("file")
|
||||||
.about("Creates a new datadir where the genesis state is read from YAML. Will fail to parse \
|
.about("Creates a new datadir where the genesis state is read from YAML. May fail to parse \
|
||||||
a YAML state that was generated to a different spec than that specified by --spec.")
|
a file that was generated to a different spec than that specified by --spec.")
|
||||||
|
.arg(Arg::with_name("format")
|
||||||
|
.value_name("FORMAT")
|
||||||
|
.required(true)
|
||||||
|
.possible_values(&["yaml", "ssz", "json"])
|
||||||
|
.help("The encoding of the state in the file."))
|
||||||
.arg(Arg::with_name("file")
|
.arg(Arg::with_name("file")
|
||||||
.value_name("YAML_FILE")
|
.value_name("YAML_FILE")
|
||||||
.required(true)
|
.required(true)
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
use client::{error, notifier, BeaconChainTypes, Client, ClientConfig, ClientType, Eth2Config};
|
use client::{
|
||||||
|
error, notifier, BeaconChainTypes, Client, ClientConfig, ClientType, Eth1BackendMethod,
|
||||||
|
Eth2Config,
|
||||||
|
};
|
||||||
use futures::sync::oneshot;
|
use futures::sync::oneshot;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use slog::{error, info};
|
use slog::{error, info};
|
||||||
@ -47,55 +50,30 @@ pub fn run_beacon_node(
|
|||||||
"spec_constants" => &spec_constants,
|
"spec_constants" => &spec_constants,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
macro_rules! run_client {
|
||||||
|
($store: ty, $eth_spec: ty) => {
|
||||||
|
run::<ClientType<$store, $eth_spec>>(
|
||||||
|
&db_path,
|
||||||
|
client_config,
|
||||||
|
eth2_config,
|
||||||
|
executor,
|
||||||
|
runtime,
|
||||||
|
log,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Eth1BackendMethod::Web3 { .. } = client_config.eth1_backend_method {
|
||||||
|
return Err("Starting from web3 backend is not supported for interop.".into());
|
||||||
|
}
|
||||||
|
|
||||||
match (db_type.as_str(), spec_constants.as_str()) {
|
match (db_type.as_str(), spec_constants.as_str()) {
|
||||||
("disk", "minimal") => run::<ClientType<DiskStore, MinimalEthSpec>>(
|
("disk", "minimal") => run_client!(DiskStore, MinimalEthSpec),
|
||||||
&db_path,
|
("disk", "mainnet") => run_client!(DiskStore, MainnetEthSpec),
|
||||||
client_config,
|
("disk", "interop") => run_client!(DiskStore, InteropEthSpec),
|
||||||
eth2_config,
|
("memory", "minimal") => run_client!(MemoryStore, MinimalEthSpec),
|
||||||
executor,
|
("memory", "mainnet") => run_client!(MemoryStore, MainnetEthSpec),
|
||||||
runtime,
|
("memory", "interop") => run_client!(MemoryStore, InteropEthSpec),
|
||||||
log,
|
|
||||||
),
|
|
||||||
("memory", "minimal") => run::<ClientType<MemoryStore, MinimalEthSpec>>(
|
|
||||||
&db_path,
|
|
||||||
client_config,
|
|
||||||
eth2_config,
|
|
||||||
executor,
|
|
||||||
runtime,
|
|
||||||
log,
|
|
||||||
),
|
|
||||||
("disk", "mainnet") => run::<ClientType<DiskStore, MainnetEthSpec>>(
|
|
||||||
&db_path,
|
|
||||||
client_config,
|
|
||||||
eth2_config,
|
|
||||||
executor,
|
|
||||||
runtime,
|
|
||||||
log,
|
|
||||||
),
|
|
||||||
("memory", "mainnet") => run::<ClientType<MemoryStore, MainnetEthSpec>>(
|
|
||||||
&db_path,
|
|
||||||
client_config,
|
|
||||||
eth2_config,
|
|
||||||
executor,
|
|
||||||
runtime,
|
|
||||||
log,
|
|
||||||
),
|
|
||||||
("disk", "interop") => run::<ClientType<DiskStore, InteropEthSpec>>(
|
|
||||||
&db_path,
|
|
||||||
client_config,
|
|
||||||
eth2_config,
|
|
||||||
executor,
|
|
||||||
runtime,
|
|
||||||
log,
|
|
||||||
),
|
|
||||||
("memory", "interop") => run::<ClientType<MemoryStore, InteropEthSpec>>(
|
|
||||||
&db_path,
|
|
||||||
client_config,
|
|
||||||
eth2_config,
|
|
||||||
executor,
|
|
||||||
runtime,
|
|
||||||
log,
|
|
||||||
),
|
|
||||||
(db_type, spec) => {
|
(db_type, spec) => {
|
||||||
error!(log, "Unknown runtime configuration"; "spec_constants" => spec, "db_type" => db_type);
|
error!(log, "Unknown runtime configuration"; "spec_constants" => spec, "db_type" => db_type);
|
||||||
Err("Unknown specification and/or db_type.".into())
|
Err("Unknown specification and/or db_type.".into())
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
* [Introduction](./intro.md)
|
* [Introduction](./intro.md)
|
||||||
* [Development Environment](./setup.md)
|
* [Development Environment](./setup.md)
|
||||||
* [Testnets](./testnets.md)
|
|
||||||
* [Simple Local Testnet](./simple-testnet.md)
|
* [Simple Local Testnet](./simple-testnet.md)
|
||||||
* [Interop](./interop.md)
|
* [Interop](./interop.md)
|
||||||
* [Interop Tips & Tricks](./interop-tips.md)
|
* [Environment](./interop-environment.md)
|
||||||
|
* [CLI Overview](./interop-cli.md)
|
||||||
|
* [Scenarios](./interop-scenarios.md)
|
||||||
|
* [Cheat-sheet](./interop-cheat-sheet.md)
|
||||||
|
139
book/src/interop-cheat-sheet.md
Normal file
139
book/src/interop-cheat-sheet.md
Normal 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
29
book/src/interop-cli.md
Normal 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.
|
30
book/src/interop-environment.md
Normal file
30
book/src/interop-environment.md
Normal 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.
|
98
book/src/interop-scenarios.md
Normal file
98
book/src/interop-scenarios.md
Normal 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
|
||||||
|
```
|
@ -1,104 +1 @@
|
|||||||
# Interop Tips & Tricks
|
# Interop Tips & Tricks
|
||||||
|
|
||||||
This document contains a list of tips and tricks that may be useful during
|
|
||||||
interop testing.
|
|
||||||
|
|
||||||
## Command-line Interface
|
|
||||||
|
|
||||||
The `--help` command provides detail on the CLI interface. Here are some
|
|
||||||
interop-specific CLI commands.
|
|
||||||
|
|
||||||
### Specify a boot node by multiaddr
|
|
||||||
|
|
||||||
You can specify a static list of multiaddrs when booting Lighthouse using
|
|
||||||
the `--libp2p-addresses` command.
|
|
||||||
|
|
||||||
#### Example:
|
|
||||||
|
|
||||||
Runs an 8 validator quick-start chain, peering with `/ip4/192.168.0.1/tcp/9000` on boot.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./beacon_node --libp2p-addresses /ip4/192.168.0.1/tcp/9000 testnet -f quick 8 1567222226
|
|
||||||
```
|
|
||||||
|
|
||||||
### Specify a boot node by ENR
|
|
||||||
|
|
||||||
You can specify a static list of Discv5 addresses when booting Lighthouse using
|
|
||||||
the `--boot-nodes` command.
|
|
||||||
|
|
||||||
#### Example:
|
|
||||||
|
|
||||||
Runs an 8 validator quick-start chain, peering with `-IW4QB2...` on boot.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./beacon_node --boot-nodes -IW4QB2Hi8TPuEzQ41Cdf1r2AUU1FFVFDBJdJyOkWk2qXpZfFZQy2YnJIyoT_5fnbtrXUouoskmydZl4pIg90clIkYUDgmlwhH8AAAGDdGNwgiMog3VkcIIjKIlzZWNwMjU2azGhAjg0-DsTkQynhJCRnLLttBK1RS78lmUkLa-wgzAi-Ob5 testnet -f quick 8 1567222226
|
|
||||||
```
|
|
||||||
|
|
||||||
### Avoid port clashes when starting nodes
|
|
||||||
|
|
||||||
Starting a second Lighthouse node on the same machine will fail due to TCP/UDP
|
|
||||||
port collisions. Use the `-b` (`--port-bump`) flag to increase all listening
|
|
||||||
ports by some `n`.
|
|
||||||
|
|
||||||
#### Example:
|
|
||||||
|
|
||||||
Increase all ports by `10` (using multiples of `10` is recommended).
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./beacon_node -b 10 testnet -f quick 8 1567222226
|
|
||||||
```
|
|
||||||
|
|
||||||
## HTTP API
|
|
||||||
|
|
||||||
Examples assume there is a Lighthouse node exposing a HTTP API on
|
|
||||||
`localhost:5052`. Responses are JSON.
|
|
||||||
|
|
||||||
### Get the node's ENR
|
|
||||||
|
|
||||||
```
|
|
||||||
$ curl localhost:5052/network/enr
|
|
||||||
|
|
||||||
"-IW4QFyf1VlY5pZs0xZuvKMRZ9_cdl9WMCDAAJXZiZiuGcfRYoU40VPrYDLQj5prneJIz3zcbTjHp9BbThc-yiymJO8HgmlwhH8AAAGDdGNwgiMog3VkcIIjKIlzZWNwMjU2azGhAjg0-DsTkQynhJCRnLLttBK1RS78lmUkLa-wgzAi-Ob5"%
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get a list of connected peer ids
|
|
||||||
|
|
||||||
```
|
|
||||||
$ curl localhost:5052/network/peers
|
|
||||||
|
|
||||||
["QmeMFRTWfo3KbVG7dEBXGhyRMa29yfmnJBXW84rKuGEhuL"]%
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get the node's peer id
|
|
||||||
|
|
||||||
```
|
|
||||||
curl localhost:5052/network/peer_id
|
|
||||||
|
|
||||||
"QmRD1qs2AqNNRdBcGHUGpUGkpih5cmdL32mhh22Sy79xsJ"%
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get the list of listening libp2p addresses
|
|
||||||
|
|
||||||
Lists all the libp2p multiaddrs that the node is listening on.
|
|
||||||
|
|
||||||
```
|
|
||||||
curl localhost:5052/network/listen_addresses
|
|
||||||
|
|
||||||
["/ip4/127.0.0.1/tcp/9000","/ip4/192.168.1.121/tcp/9000","/ip4/172.17.0.1/tcp/9000","/ip4/172.42.0.1/tcp/9000","/ip6/::1/tcp/9000","/ip6/fdd3:c293:1bc::203/tcp/9000","/ip6/fdd3:c293:1bc:0:9aa9:b2ea:c610:44db/tcp/9000"]%
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get the node's beacon chain head
|
|
||||||
|
|
||||||
```
|
|
||||||
curl localhost:5052/beacon/head
|
|
||||||
|
|
||||||
{"slot":0,"block_root":"0x827bf71805540aa13f6d8c7d18b41b287b2094a4d7a28cbb8deb061dbf5df4f5","state_root":"0x90a78d73294bc9c7519a64e1912161be0e823eb472012ff54204e15a4d717fa5"}%
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get the node's finalized checkpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
curl localhost:5052/beacon/latest_finalized_checkpoint
|
|
||||||
|
|
||||||
{"epoch":0,"root":"0x0000000000000000000000000000000000000000000000000000000000000000"}%
|
|
||||||
```
|
|
||||||
|
@ -3,84 +3,9 @@
|
|||||||
This guide is intended for other Ethereum 2.0 client developers performing
|
This guide is intended for other Ethereum 2.0 client developers performing
|
||||||
inter-operability testing with Lighthouse.
|
inter-operability testing with Lighthouse.
|
||||||
|
|
||||||
To allow for faster iteration cycles without the "merging to master" overhead,
|
## Chapters
|
||||||
we will use the [`interop`](https://github.com/sigp/lighthouse/tree/interop)
|
|
||||||
branch of [sigp/lighthouse](https://github.com/sigp/lighthouse/tree/interop)
|
|
||||||
for September 2019 interop. **Please use ensure you `git checkout interop`
|
|
||||||
after cloning the repo.**
|
|
||||||
|
|
||||||
## Environment
|
- Read about the required [development environment](./interop-environment.md).
|
||||||
|
- Get an [overview](./interop-cli.md) of the Lighthouse CLI.
|
||||||
All that is required for inter-op is a built and tested [development
|
- See how we expect to handle some [interop scenarios](./interop-scenarios.md).
|
||||||
environment](setup). When lighthouse boots, it will create the following
|
- See the [interop cheat-sheet](./interop-cheat-sheet.md) for useful CLI tips.
|
||||||
directories:
|
|
||||||
|
|
||||||
- `~/.lighthouse`: database and configuration for the beacon node.
|
|
||||||
- `~/.lighthouse-validator`: database and configuration for the validator
|
|
||||||
client.
|
|
||||||
|
|
||||||
After building the binaries with `cargo build --release --all`, there will be a
|
|
||||||
`target/release` directory in the root of the Lighthouse repository. This is
|
|
||||||
where the `beacon_node` and `validator_client` binaries are located.
|
|
||||||
|
|
||||||
## Interop Procedure
|
|
||||||
|
|
||||||
The following scenarios are documented:
|
|
||||||
|
|
||||||
- [Starting a "quick-start" beacon node](#quick-start-beacon-node) from a
|
|
||||||
`(validator_count, genesis)` tuple.
|
|
||||||
- [Starting a validator client](#validator-client) with `n` interop keypairs.
|
|
||||||
- [Starting a node from a genesis state file](#starting-from-a-genesis-file).
|
|
||||||
- [Exporting a genesis state file](#exporting-a-genesis-file) from a running Lighthouse
|
|
||||||
node.
|
|
||||||
|
|
||||||
First, setup a Lighthouse development environment and navigate to the
|
|
||||||
`target/release` directory (this is where the binaries are located).
|
|
||||||
|
|
||||||
#### Quick-start Beacon Node
|
|
||||||
|
|
||||||
|
|
||||||
To start the node (each time creating a fresh database and configuration in
|
|
||||||
`~/.lighthouse`), use:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./beacon_node testnet -f quick 8 1567222226
|
|
||||||
```
|
|
||||||
> Notes:
|
|
||||||
>
|
|
||||||
> - This method conforms the ["Quick-start
|
|
||||||
genesis"](https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start#quick-start-genesis)
|
|
||||||
method in the `ethereum/eth2.0-pm` repository.
|
|
||||||
> - The `-f` flag ignores any existing database or configuration, backing them
|
|
||||||
> up before re-initializing.
|
|
||||||
> - `8` is the validator count and `1567222226` is the genesis time.
|
|
||||||
> - See `$ ./beacon_node testnet quick --help` for more configuration options.
|
|
||||||
|
|
||||||
#### Validator Client
|
|
||||||
|
|
||||||
Start the validator client with:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ ./validator_client testnet -b insecure 0 8
|
|
||||||
```
|
|
||||||
> Notes:
|
|
||||||
>
|
|
||||||
> - The `-b` flag means the validator client will "bootstrap" specs and config
|
|
||||||
> from the beacon node.
|
|
||||||
> - The `insecure` command dictates that the [interop
|
|
||||||
> keypairs](https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start#pubkeyprivkey-generation)
|
|
||||||
> will be used.
|
|
||||||
> - The `0 8` indicates that this validator client should manage 8 validators,
|
|
||||||
> starting at validator 0 (the first deposited validator).
|
|
||||||
> - The validator client will try to connect to the beacon node at `localhost`.
|
|
||||||
> See `--help` to configure that address and other features.
|
|
||||||
> - The validator client will operate very unsafely in `testnet` mode, happily
|
|
||||||
> swapping between chains and creating double-votes.
|
|
||||||
|
|
||||||
#### Starting from a genesis file
|
|
||||||
|
|
||||||
**TODO**
|
|
||||||
|
|
||||||
#### Exporting a genesis file
|
|
||||||
|
|
||||||
**TODO**
|
|
||||||
|
@ -17,31 +17,11 @@ Foundation, Consensys and other individuals and organisations.
|
|||||||
|
|
||||||
## Developer Resources
|
## Developer Resources
|
||||||
|
|
||||||
Documentation is provided for **researchers and developers** working on
|
Documentation is presently targeted at **researchers and developers**. It
|
||||||
Ethereum 2.0 and assumes prior knowledge on the topic.
|
assumes significant prior knowledge of Ethereum 2.0.
|
||||||
|
|
||||||
- Get started with [development environment setup](setup.html).
|
Topics:
|
||||||
- [Run a simple testnet](simple-testnet.html) in Only Three CLI Commands™.
|
|
||||||
- Read about our interop workflow.
|
|
||||||
- API?
|
|
||||||
|
|
||||||
## Release
|
- Get started with [development environment setup](./setup.md).
|
||||||
|
- See the [interop docs](./interop.md).
|
||||||
Ethereum 2.0 is not fully specified or implemented and as such, Lighthouse is
|
- [Run a simple testnet](./simple-testnet.md) in Only Three CLI Commands™.
|
||||||
still **under development**.
|
|
||||||
|
|
||||||
We are on-track to provide a public, multi-client testnet in late-2019 and an
|
|
||||||
initial production-grade blockchain in 2020.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
Lighthouse has been in development since mid-2018 and has an extensive feature
|
|
||||||
set:
|
|
||||||
|
|
||||||
- Libp2p networking stack, featuring Discovery v5.
|
|
||||||
- Optimized `BeaconChain` state machine, up-to-date and
|
|
||||||
passing all tests.
|
|
||||||
- RESTful HTTP API.
|
|
||||||
- Documented and feature-rich CLI interface.
|
|
||||||
- Capable of running small, local testnets with 250ms slot times.
|
|
||||||
- Detailed metrics exposed in the Prometheus format.
|
|
||||||
|
@ -66,7 +66,7 @@ In a new terminal terminal, run:
|
|||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ./beacon_node -b 10 testnet -r bootstrap http://localhost:5052
|
$ ./beacon_node -b 10 testnet -r bootstrap
|
||||||
```
|
```
|
||||||
|
|
||||||
> Notes:
|
> Notes:
|
||||||
@ -74,7 +74,8 @@ $ ./beacon_node -b 10 testnet -r bootstrap http://localhost:5052
|
|||||||
> - The `-b` (or `--port-bump`) increases all the listening TCP/UDP ports of
|
> - The `-b` (or `--port-bump`) increases all the listening TCP/UDP ports of
|
||||||
> the new node to `10` higher. Your first node's HTTP server was at TCP
|
> the new node to `10` higher. Your first node's HTTP server was at TCP
|
||||||
> `5052` but this one will be at `5062`.
|
> `5052` but this one will be at `5062`.
|
||||||
> - The `-r` flag creates a new data directory in your home with a random
|
> - The `-r` flag creates a new data directory with a random string appended
|
||||||
> string appended, to avoid conflicting with any other running node.
|
> (avoids data directory collisions between nodes).
|
||||||
> - The HTTP address is the API of the first node. The new node will download
|
> - The default bootstrap HTTP address is `http://localhost:5052`. The new node
|
||||||
> configuration via HTTP before starting sync via libp2p.
|
> will download configuration via HTTP before starting sync via libp2p.
|
||||||
|
> - See `$ ./beacon_node testnet bootstrap --help` for more configuration.
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
use beacon_chain::test_utils::{
|
use beacon_chain::test_utils::{
|
||||||
AttestationStrategy, BeaconChainHarness as BaseBeaconChainHarness, BlockStrategy,
|
generate_deterministic_keypairs, AttestationStrategy,
|
||||||
|
BeaconChainHarness as BaseBeaconChainHarness, BlockStrategy,
|
||||||
};
|
};
|
||||||
use lmd_ghost::{LmdGhost, ThreadSafeReducedTree as BaseThreadSafeReducedTree};
|
use lmd_ghost::{LmdGhost, ThreadSafeReducedTree as BaseThreadSafeReducedTree};
|
||||||
use rand::{prelude::*, rngs::StdRng};
|
use rand::{prelude::*, rngs::StdRng};
|
||||||
@ -51,7 +52,7 @@ struct ForkedHarness {
|
|||||||
impl ForkedHarness {
|
impl ForkedHarness {
|
||||||
/// A new standard instance of with constant parameters.
|
/// A new standard instance of with constant parameters.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let harness = BeaconChainHarness::new(VALIDATOR_COUNT);
|
let harness = BeaconChainHarness::new(generate_deterministic_keypairs(VALIDATOR_COUNT));
|
||||||
|
|
||||||
// Move past the zero slot.
|
// Move past the zero slot.
|
||||||
harness.advance_slot();
|
harness.advance_slot();
|
||||||
|
@ -31,3 +31,4 @@ tree_hash_derive = "0.2"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.6.0"
|
env_logger = "0.6.0"
|
||||||
|
serde_json = "^1.0"
|
||||||
|
@ -120,6 +120,13 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq {
|
|||||||
fn epochs_per_historical_vector() -> usize {
|
fn epochs_per_historical_vector() -> usize {
|
||||||
Self::EpochsPerHistoricalVector::to_usize()
|
Self::EpochsPerHistoricalVector::to_usize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the `SLOTS_PER_ETH1_VOTING_PERIOD` constant for this specification.
|
||||||
|
///
|
||||||
|
/// Spec v0.8.1
|
||||||
|
fn slots_per_eth1_voting_period() -> usize {
|
||||||
|
Self::EpochsPerHistoricalVector::to_usize()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Macro to inherit some type values from another EthSpec.
|
/// Macro to inherit some type values from another EthSpec.
|
||||||
|
@ -9,7 +9,7 @@ fn default_values() {
|
|||||||
let cache = CommitteeCache::default();
|
let cache = CommitteeCache::default();
|
||||||
|
|
||||||
assert_eq!(cache.is_initialized_at(Epoch::new(0)), false);
|
assert_eq!(cache.is_initialized_at(Epoch::new(0)), false);
|
||||||
assert_eq!(cache.active_validator_indices(), &[]);
|
assert!(&cache.active_validator_indices().is_empty());
|
||||||
assert_eq!(cache.get_crosslink_committee_for_shard(0), None);
|
assert_eq!(cache.get_crosslink_committee_for_shard(0), None);
|
||||||
assert_eq!(cache.get_attestation_duties(0), None);
|
assert_eq!(cache.get_attestation_duties(0), None);
|
||||||
assert_eq!(cache.active_validator_count(), 0);
|
assert_eq!(cache.active_validator_count(), 0);
|
||||||
|
@ -39,15 +39,15 @@ impl TestingProposerSlashingBuilder {
|
|||||||
..header_1.clone()
|
..header_1.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let epoch = slot.epoch(T::slots_per_epoch());
|
||||||
|
|
||||||
header_1.signature = {
|
header_1.signature = {
|
||||||
let message = header_1.signed_root();
|
let message = header_1.signed_root();
|
||||||
let epoch = slot.epoch(T::slots_per_epoch());
|
|
||||||
signer(proposer_index, &message[..], epoch, Domain::BeaconProposer)
|
signer(proposer_index, &message[..], epoch, Domain::BeaconProposer)
|
||||||
};
|
};
|
||||||
|
|
||||||
header_2.signature = {
|
header_2.signature = {
|
||||||
let message = header_2.signed_root();
|
let message = header_2.signed_root();
|
||||||
let epoch = slot.epoch(T::slots_per_epoch());
|
|
||||||
signer(proposer_index, &message[..], epoch, Domain::BeaconProposer)
|
signer(proposer_index, &message[..], epoch, Domain::BeaconProposer)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13,3 +13,4 @@ reqwest = "0.9"
|
|||||||
url = "1.2"
|
url = "1.2"
|
||||||
types = { path = "../../types" }
|
types = { path = "../../types" }
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
|
slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_trace"] }
|
||||||
|
@ -5,11 +5,16 @@ use eth2_libp2p::{
|
|||||||
};
|
};
|
||||||
use reqwest::{Error as HttpError, Url};
|
use reqwest::{Error as HttpError, Url};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use slog::{error, Logger};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
use std::time::Duration;
|
||||||
use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Hash256, Slot};
|
use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Hash256, Slot};
|
||||||
use url::Host;
|
use url::Host;
|
||||||
|
|
||||||
|
pub const RETRY_SLEEP_MILLIS: u64 = 100;
|
||||||
|
pub const RETRY_WARN_INTERVAL: u64 = 30;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Error {
|
enum Error {
|
||||||
InvalidUrl,
|
InvalidUrl,
|
||||||
@ -31,11 +36,35 @@ pub struct Bootstrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Bootstrapper {
|
impl Bootstrapper {
|
||||||
/// Parses the given `server` as a URL, instantiating `Self`.
|
/// Parses the given `server` as a URL, instantiating `Self` and blocking until a connection
|
||||||
pub fn from_server_string(server: String) -> Result<Self, String> {
|
/// can be made with the server.
|
||||||
Ok(Self {
|
///
|
||||||
|
/// Never times out.
|
||||||
|
pub fn connect(server: String, log: &Logger) -> Result<Self, String> {
|
||||||
|
let bootstrapper = Self {
|
||||||
url: Url::parse(&server).map_err(|e| format!("Invalid bootstrap server url: {}", e))?,
|
url: Url::parse(&server).map_err(|e| format!("Invalid bootstrap server url: {}", e))?,
|
||||||
})
|
};
|
||||||
|
|
||||||
|
let mut retry_count = 0;
|
||||||
|
loop {
|
||||||
|
match bootstrapper.enr() {
|
||||||
|
Ok(_) => break,
|
||||||
|
Err(_) => {
|
||||||
|
if retry_count % RETRY_WARN_INTERVAL == 0 {
|
||||||
|
error!(
|
||||||
|
log,
|
||||||
|
"Failed to contact bootstrap server";
|
||||||
|
"retry_count" => retry_count,
|
||||||
|
"retry_delay_millis" => RETRY_SLEEP_MILLIS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
retry_count += 1;
|
||||||
|
std::thread::sleep(Duration::from_millis(RETRY_SLEEP_MILLIS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(bootstrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a multiaddr using the HTTP server URL that is not guaranteed to be correct.
|
/// Build a multiaddr using the HTTP server URL that is not guaranteed to be correct.
|
||||||
|
@ -247,10 +247,13 @@ fn process_testnet_subcommand(
|
|||||||
) -> Result<(ClientConfig, Eth2Config)> {
|
) -> Result<(ClientConfig, Eth2Config)> {
|
||||||
let eth2_config = if cli_args.is_present("bootstrap") {
|
let eth2_config = if cli_args.is_present("bootstrap") {
|
||||||
info!(log, "Connecting to bootstrap server");
|
info!(log, "Connecting to bootstrap server");
|
||||||
let bootstrapper = Bootstrapper::from_server_string(format!(
|
let bootstrapper = Bootstrapper::connect(
|
||||||
|
format!(
|
||||||
"http://{}:{}",
|
"http://{}:{}",
|
||||||
client_config.server, client_config.server_http_port
|
client_config.server, client_config.server_http_port
|
||||||
))?;
|
),
|
||||||
|
&log,
|
||||||
|
)?;
|
||||||
|
|
||||||
let eth2_config = bootstrapper.eth2_config()?;
|
let eth2_config = bootstrapper.eth2_config()?;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user