mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-17 21:08:32 +00:00
Merge branch 'master' into improved-message-validation
This commit is contained in:
@@ -12,28 +12,35 @@ write_ssz_files = [] # Writes debugging .ssz files to /tmp during block process
|
||||
eth2_config = { path = "../../eth2/utils/eth2_config" }
|
||||
merkle_proof = { path = "../../eth2/utils/merkle_proof" }
|
||||
store = { path = "../store" }
|
||||
parking_lot = "0.7"
|
||||
lazy_static = "1.3.0"
|
||||
parking_lot = "0.9.0"
|
||||
lazy_static = "1.4.0"
|
||||
lighthouse_metrics = { path = "../../eth2/utils/lighthouse_metrics" }
|
||||
lighthouse_bootstrap = { path = "../../eth2/utils/lighthouse_bootstrap" }
|
||||
log = "0.4"
|
||||
log = "0.4.8"
|
||||
operation_pool = { path = "../../eth2/operation_pool" }
|
||||
rayon = "1.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_yaml = "0.8"
|
||||
serde_json = "^1.0"
|
||||
slog = { version = "^2.2.3" , features = ["max_level_trace"] }
|
||||
sloggers = { version = "^0.3" }
|
||||
rayon = "1.2.0"
|
||||
serde = "1.0.102"
|
||||
serde_derive = "1.0.102"
|
||||
serde_yaml = "0.8.11"
|
||||
serde_json = "1.0.41"
|
||||
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
||||
sloggers = "0.3.4"
|
||||
slot_clock = { path = "../../eth2/utils/slot_clock" }
|
||||
eth2_hashing = { path = "../../eth2/utils/eth2_hashing" }
|
||||
eth2_ssz = "0.1"
|
||||
eth2_ssz_derive = "0.1"
|
||||
eth2_hashing = "0.1.0"
|
||||
eth2_ssz = "0.1.2"
|
||||
eth2_ssz_derive = "0.1.0"
|
||||
state_processing = { path = "../../eth2/state_processing" }
|
||||
tree_hash = "0.1"
|
||||
tree_hash = "0.1.0"
|
||||
types = { path = "../../eth2/types" }
|
||||
lmd_ghost = { path = "../../eth2/lmd_ghost" }
|
||||
eth1 = { path = "../eth1" }
|
||||
websocket_server = { path = "../websocket_server" }
|
||||
futures = "0.1.25"
|
||||
exit-future = "0.1.3"
|
||||
genesis = { path = "../genesis" }
|
||||
integer-sqrt = "0.1"
|
||||
rand = "0.7.2"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.5.5"
|
||||
lazy_static = "1.3.0"
|
||||
lazy_static = "1.4.0"
|
||||
environment = { path = "../../lighthouse/environment" }
|
||||
|
||||
@@ -16,7 +16,7 @@ use ssz::Encode;
|
||||
use state_processing::per_block_processing::{
|
||||
errors::{
|
||||
AttestationValidationError, AttesterSlashingValidationError, DepositValidationError,
|
||||
ExitValidationError, ProposerSlashingValidationError, TransferValidationError,
|
||||
ExitValidationError, ProposerSlashingValidationError,
|
||||
},
|
||||
verify_attestation_for_state, VerifySignatures,
|
||||
};
|
||||
@@ -26,7 +26,6 @@ use state_processing::{
|
||||
use std::fs;
|
||||
use std::io::prelude::*;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use store::iter::{BlockRootsIterator, StateRootsIterator};
|
||||
use store::{Error as DBError, Store};
|
||||
use tree_hash::TreeHash;
|
||||
@@ -44,6 +43,8 @@ pub const GRAFFITI: &str = "sigp/lighthouse-0.0.0-prerelease";
|
||||
/// Only useful for testing.
|
||||
const WRITE_BLOCK_PROCESSING_SSZ: bool = cfg!(feature = "write_ssz_files");
|
||||
|
||||
const BLOCK_SKIPPING_LOGGING_THRESHOLD: u64 = 3;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BlockProcessingOutcome {
|
||||
/// Block was valid and imported into the block graph.
|
||||
@@ -111,9 +112,9 @@ pub struct BeaconChain<T: BeaconChainTypes> {
|
||||
/// inclusion in a block.
|
||||
pub op_pool: OperationPool<T::EthSpec>,
|
||||
/// Provides information from the Ethereum 1 (PoW) chain.
|
||||
pub eth1_chain: Eth1Chain<T>,
|
||||
pub eth1_chain: Option<Eth1Chain<T::Eth1Chain, T::EthSpec>>,
|
||||
/// Stores a "snapshot" of the chain at the time the head-of-the-chain block was received.
|
||||
canonical_head: RwLock<CheckPoint<T::EthSpec>>,
|
||||
pub(crate) canonical_head: RwLock<CheckPoint<T::EthSpec>>,
|
||||
/// The root of the genesis block.
|
||||
pub genesis_block_root: Hash256,
|
||||
/// A state-machine that is updated with information from the network and chooses a canonical
|
||||
@@ -122,119 +123,12 @@ pub struct BeaconChain<T: BeaconChainTypes> {
|
||||
/// A handler for events generated by the beacon chain.
|
||||
pub event_handler: T::EventHandler,
|
||||
/// Logging to CLI, etc.
|
||||
log: Logger,
|
||||
pub(crate) log: Logger,
|
||||
}
|
||||
|
||||
type BeaconInfo<T> = (BeaconBlock<T>, BeaconState<T>);
|
||||
type BeaconBlockAndState<T> = (BeaconBlock<T>, BeaconState<T>);
|
||||
|
||||
impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
/// Instantiate a new Beacon Chain, from genesis.
|
||||
pub fn from_genesis(
|
||||
store: Arc<T::Store>,
|
||||
eth1_backend: T::Eth1Chain,
|
||||
event_handler: T::EventHandler,
|
||||
mut genesis_state: BeaconState<T::EthSpec>,
|
||||
mut genesis_block: BeaconBlock<T::EthSpec>,
|
||||
spec: ChainSpec,
|
||||
log: Logger,
|
||||
) -> Result<Self, Error> {
|
||||
genesis_state.build_all_caches(&spec)?;
|
||||
|
||||
let genesis_state_root = genesis_state.canonical_root();
|
||||
store.put(&genesis_state_root, &genesis_state)?;
|
||||
|
||||
genesis_block.state_root = genesis_state_root;
|
||||
|
||||
let genesis_block_root = genesis_block.block_header().canonical_root();
|
||||
store.put(&genesis_block_root, &genesis_block)?;
|
||||
|
||||
// Also store the genesis block under the `ZERO_HASH` key.
|
||||
let genesis_block_root = genesis_block.canonical_root();
|
||||
store.put(&Hash256::zero(), &genesis_block)?;
|
||||
|
||||
let canonical_head = RwLock::new(CheckPoint::new(
|
||||
genesis_block.clone(),
|
||||
genesis_block_root,
|
||||
genesis_state.clone(),
|
||||
genesis_state_root,
|
||||
));
|
||||
|
||||
// Slot clock
|
||||
let slot_clock = T::SlotClock::new(
|
||||
spec.genesis_slot,
|
||||
Duration::from_secs(genesis_state.genesis_time),
|
||||
Duration::from_millis(spec.milliseconds_per_slot),
|
||||
);
|
||||
|
||||
info!(log, "Beacon chain initialized from genesis";
|
||||
"validator_count" => genesis_state.validators.len(),
|
||||
"state_root" => format!("{}", genesis_state_root),
|
||||
"block_root" => format!("{}", genesis_block_root),
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
spec,
|
||||
slot_clock,
|
||||
op_pool: OperationPool::new(),
|
||||
eth1_chain: Eth1Chain::new(eth1_backend),
|
||||
canonical_head,
|
||||
genesis_block_root,
|
||||
fork_choice: ForkChoice::new(store.clone(), &genesis_block, genesis_block_root),
|
||||
event_handler,
|
||||
store,
|
||||
log,
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempt to load an existing instance from the given `store`.
|
||||
pub fn from_store(
|
||||
store: Arc<T::Store>,
|
||||
eth1_backend: T::Eth1Chain,
|
||||
event_handler: T::EventHandler,
|
||||
spec: ChainSpec,
|
||||
log: Logger,
|
||||
) -> Result<Option<BeaconChain<T>>, Error> {
|
||||
let key = Hash256::from_slice(&BEACON_CHAIN_DB_KEY.as_bytes());
|
||||
let p: PersistedBeaconChain<T> = match store.get(&key) {
|
||||
Err(e) => return Err(e.into()),
|
||||
Ok(None) => return Ok(None),
|
||||
Ok(Some(p)) => p,
|
||||
};
|
||||
|
||||
let state = &p.canonical_head.beacon_state;
|
||||
|
||||
let slot_clock = T::SlotClock::new(
|
||||
spec.genesis_slot,
|
||||
Duration::from_secs(state.genesis_time),
|
||||
Duration::from_millis(spec.milliseconds_per_slot),
|
||||
);
|
||||
|
||||
let last_finalized_root = p.canonical_head.beacon_state.finalized_checkpoint.root;
|
||||
let last_finalized_block = &p.canonical_head.beacon_block;
|
||||
|
||||
let op_pool = p.op_pool.into_operation_pool(state, &spec);
|
||||
|
||||
info!(log, "Beacon chain initialized from store";
|
||||
"head_root" => format!("{}", p.canonical_head.beacon_block_root),
|
||||
"head_epoch" => format!("{}", p.canonical_head.beacon_block.slot.epoch(T::EthSpec::slots_per_epoch())),
|
||||
"finalized_root" => format!("{}", last_finalized_root),
|
||||
"finalized_epoch" => format!("{}", last_finalized_block.slot.epoch(T::EthSpec::slots_per_epoch())),
|
||||
);
|
||||
|
||||
Ok(Some(BeaconChain {
|
||||
spec,
|
||||
slot_clock,
|
||||
fork_choice: ForkChoice::new(store.clone(), last_finalized_block, last_finalized_root),
|
||||
op_pool,
|
||||
event_handler,
|
||||
eth1_chain: Eth1Chain::new(eth1_backend),
|
||||
canonical_head: RwLock::new(p.canonical_head),
|
||||
genesis_block_root: p.genesis_block_root,
|
||||
store,
|
||||
log,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Attempt to save this instance to `self.store`.
|
||||
pub fn persist(&self) -> Result<(), Error> {
|
||||
let timer = metrics::start_timer(&metrics::PERSIST_CHAIN);
|
||||
@@ -324,6 +218,44 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
ReverseBlockRootIterator::new((head.beacon_block_root, head.beacon_block.slot), iter)
|
||||
}
|
||||
|
||||
/// Traverse backwards from `block_root` to find the block roots of its ancestors.
|
||||
///
|
||||
/// ## Notes
|
||||
///
|
||||
/// `slot` always decreases by `1`.
|
||||
/// - Skipped slots contain the root of the closest prior
|
||||
/// non-skipped slot (identical to the way they are stored in `state.block_roots`) .
|
||||
/// - Iterator returns `(Hash256, Slot)`.
|
||||
/// - The provided `block_root` is included as the first item in the iterator.
|
||||
pub fn rev_iter_block_roots_from(
|
||||
&self,
|
||||
block_root: Hash256,
|
||||
) -> Result<ReverseBlockRootIterator<T::EthSpec, T::Store>, Error> {
|
||||
let block = self
|
||||
.get_block(&block_root)?
|
||||
.ok_or_else(|| Error::MissingBeaconBlock(block_root))?;
|
||||
let state = self
|
||||
.get_state(&block.state_root)?
|
||||
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
|
||||
let iter = BlockRootsIterator::owned(self.store.clone(), state);
|
||||
Ok(ReverseBlockRootIterator::new(
|
||||
(block_root, block.slot),
|
||||
iter,
|
||||
))
|
||||
}
|
||||
|
||||
/// Traverse backwards from `block_root` to find the root of the ancestor block at `slot`.
|
||||
pub fn get_ancestor_block_root(
|
||||
&self,
|
||||
block_root: Hash256,
|
||||
slot: Slot,
|
||||
) -> Result<Option<Hash256>, Error> {
|
||||
Ok(self
|
||||
.rev_iter_block_roots_from(block_root)?
|
||||
.find(|(_, ancestor_slot)| *ancestor_slot == slot)
|
||||
.map(|(ancestor_block_root, _)| ancestor_block_root))
|
||||
}
|
||||
|
||||
/// Iterates across all `(state_root, slot)` pairs from the head of the chain (inclusive) to
|
||||
/// the earliest reachable ancestor (may or may not be genesis).
|
||||
///
|
||||
@@ -354,6 +286,18 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
Ok(self.store.get(block_root)?)
|
||||
}
|
||||
|
||||
/// Returns the state at the given root, if any.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// May return a database error.
|
||||
pub fn get_state(
|
||||
&self,
|
||||
state_root: &Hash256,
|
||||
) -> Result<Option<BeaconState<T::EthSpec>>, Error> {
|
||||
Ok(self.store.get(state_root)?)
|
||||
}
|
||||
|
||||
/// Returns a `Checkpoint` representing the head block and state. Contains the "best block";
|
||||
/// the head of the canonical `BeaconChain`.
|
||||
///
|
||||
@@ -374,6 +318,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
if slot == head_state.slot {
|
||||
Ok(head_state)
|
||||
} else if slot > head_state.slot {
|
||||
if slot > head_state.slot + BLOCK_SKIPPING_LOGGING_THRESHOLD {
|
||||
warn!(
|
||||
self.log,
|
||||
"Skipping more than {} blocks", BLOCK_SKIPPING_LOGGING_THRESHOLD;
|
||||
"head_slot" => head_state.slot,
|
||||
"request_slot" => slot
|
||||
)
|
||||
}
|
||||
let head_state_slot = head_state.slot;
|
||||
let mut state = head_state;
|
||||
while state.slot < slot {
|
||||
@@ -483,15 +435,15 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
}
|
||||
|
||||
state
|
||||
.get_beacon_proposer_index(slot, RelativeEpoch::Current, &self.spec)
|
||||
.get_beacon_proposer_index(slot, &self.spec)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns the attestation slot and shard for a given validator index.
|
||||
/// Returns the attestation slot and committee index for a given validator index.
|
||||
///
|
||||
/// Information is read from the current state, so only information from the present and prior
|
||||
/// epoch is available.
|
||||
pub fn validator_attestation_slot_and_shard(
|
||||
pub fn validator_attestation_slot_and_index(
|
||||
&self,
|
||||
validator_index: usize,
|
||||
epoch: Epoch,
|
||||
@@ -518,25 +470,25 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
if let Some(attestation_duty) =
|
||||
state.get_attestation_duties(validator_index, RelativeEpoch::Current)?
|
||||
{
|
||||
Ok(Some((attestation_duty.slot, attestation_duty.shard)))
|
||||
Ok(Some((attestation_duty.slot, attestation_duty.index)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Produce an `AttestationData` that is valid for the given `slot` `shard`.
|
||||
/// Produce an `AttestationData` that is valid for the given `slot`, `index`.
|
||||
///
|
||||
/// Always attests to the canonical chain.
|
||||
pub fn produce_attestation_data(
|
||||
&self,
|
||||
shard: u64,
|
||||
slot: Slot,
|
||||
index: CommitteeIndex,
|
||||
) -> Result<AttestationData, Error> {
|
||||
let state = self.state_at_slot(slot)?;
|
||||
let head = self.head();
|
||||
|
||||
self.produce_attestation_data_for_block(
|
||||
shard,
|
||||
index,
|
||||
head.beacon_block_root,
|
||||
head.beacon_block.slot,
|
||||
&state,
|
||||
@@ -549,7 +501,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
/// function should be used as it attests to the canonical chain.
|
||||
pub fn produce_attestation_data_for_block(
|
||||
&self,
|
||||
shard: u64,
|
||||
index: CommitteeIndex,
|
||||
head_block_root: Hash256,
|
||||
head_block_slot: Slot,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
@@ -590,18 +542,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
root: target_root,
|
||||
};
|
||||
|
||||
let parent_crosslink = state.get_current_crosslink(shard)?;
|
||||
let crosslink = Crosslink {
|
||||
shard,
|
||||
parent_root: Hash256::from_slice(&parent_crosslink.tree_hash_root()),
|
||||
start_epoch: parent_crosslink.end_epoch,
|
||||
end_epoch: std::cmp::min(
|
||||
target.epoch,
|
||||
parent_crosslink.end_epoch + self.spec.max_epochs_per_crosslink,
|
||||
),
|
||||
data_root: Hash256::zero(),
|
||||
};
|
||||
|
||||
// Collect some metrics.
|
||||
metrics::inc_counter(&metrics::ATTESTATION_PRODUCTION_SUCCESSES);
|
||||
metrics::stop_timer(timer);
|
||||
@@ -610,15 +550,16 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
self.log,
|
||||
"Produced beacon attestation data";
|
||||
"beacon_block_root" => format!("{}", head_block_root),
|
||||
"shard" => shard,
|
||||
"slot" => state.slot
|
||||
"slot" => state.slot,
|
||||
"index" => index
|
||||
);
|
||||
|
||||
Ok(AttestationData {
|
||||
slot: state.slot,
|
||||
index,
|
||||
beacon_block_root: head_block_root,
|
||||
source: state.current_justified_checkpoint.clone(),
|
||||
target,
|
||||
crosslink,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -647,7 +588,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
self.log,
|
||||
"Beacon attestation imported";
|
||||
"target_epoch" => attestation.data.target.epoch,
|
||||
"shard" => attestation.data.crosslink.shard,
|
||||
"index" => attestation.data.index,
|
||||
);
|
||||
let _ = self
|
||||
.event_handler
|
||||
@@ -766,16 +707,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
|
||||
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
|
||||
|
||||
let attestation_slot = state.get_attestation_data_slot(&attestation.data)?;
|
||||
|
||||
// Reject any attestation where the `state` loaded from `data.beacon_block_root`
|
||||
// has a higher slot than the attestation.
|
||||
//
|
||||
// Permitting this would allow for attesters to vote on _future_ slots.
|
||||
if state.slot > attestation_slot {
|
||||
if state.slot > attestation.data.slot {
|
||||
Ok(AttestationProcessingOutcome::AttestsToFutureState {
|
||||
state: state.slot,
|
||||
attestation: attestation_slot,
|
||||
attestation: attestation.data.slot,
|
||||
})
|
||||
} else {
|
||||
self.process_attestation_for_state_and_block(
|
||||
@@ -874,20 +813,27 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
|
||||
Ok(AttestationProcessingOutcome::Invalid(e))
|
||||
} else {
|
||||
// Provide the attestation to fork choice, updating the validator latest messages but
|
||||
// _without_ finding and updating the head.
|
||||
if let Err(e) = self
|
||||
.fork_choice
|
||||
.process_attestation(&state, &attestation, block)
|
||||
// If the attestation is from the current or previous epoch, supply it to the fork
|
||||
// choice. This is FMD GHOST.
|
||||
let current_epoch = self.epoch()?;
|
||||
if attestation.data.target.epoch == current_epoch
|
||||
|| attestation.data.target.epoch == current_epoch - 1
|
||||
{
|
||||
error!(
|
||||
self.log,
|
||||
"Add attestation to fork choice failed";
|
||||
"fork_choice_integrity" => format!("{:?}", self.fork_choice.verify_integrity()),
|
||||
"beacon_block_root" => format!("{}", attestation.data.beacon_block_root),
|
||||
"error" => format!("{:?}", e)
|
||||
);
|
||||
return Err(e.into());
|
||||
// Provide the attestation to fork choice, updating the validator latest messages but
|
||||
// _without_ finding and updating the head.
|
||||
if let Err(e) = self
|
||||
.fork_choice
|
||||
.process_attestation(&state, &attestation, block)
|
||||
{
|
||||
error!(
|
||||
self.log,
|
||||
"Add attestation to fork choice failed";
|
||||
"fork_choice_integrity" => format!("{:?}", self.fork_choice.verify_integrity()),
|
||||
"beacon_block_root" => format!("{}", attestation.data.beacon_block_root),
|
||||
"error" => format!("{:?}", e)
|
||||
);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
|
||||
// Provide the valid attestation to op pool, which may choose to retain the
|
||||
@@ -927,22 +873,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept some transfer and queue it for inclusion in an appropriate block.
|
||||
pub fn process_transfer(&self, transfer: Transfer) -> Result<(), TransferValidationError> {
|
||||
match self.wall_clock_state() {
|
||||
Ok(state) => self.op_pool.insert_transfer(transfer, &state, &self.spec),
|
||||
Err(e) => {
|
||||
error!(
|
||||
&self.log,
|
||||
"Unable to process transfer";
|
||||
"error" => format!("{:?}", e),
|
||||
"reason" => "no state"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept some proposer slashing and queue it for inclusion in an appropriate block.
|
||||
pub fn process_proposer_slashing(
|
||||
&self,
|
||||
@@ -1003,7 +933,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
self.log,
|
||||
"Beacon block imported";
|
||||
"block_root" => format!("{:?}", block_root),
|
||||
"block_slot" => format!("{:?}", block_root),
|
||||
"block_slot" => format!("{:?}", block.slot.as_u64()),
|
||||
);
|
||||
let _ = self.event_handler.register(EventKind::BeaconBlockImported {
|
||||
block_root: *block_root,
|
||||
@@ -1213,7 +1143,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
metrics::start_timer(&metrics::BLOCK_PROCESSING_FORK_CHOICE_REGISTER);
|
||||
|
||||
// Register the new block with the fork choice service.
|
||||
if let Err(e) = self.fork_choice.process_block(&state, &block, block_root) {
|
||||
if let Err(e) = self
|
||||
.fork_choice
|
||||
.process_block(self, &state, &block, block_root)
|
||||
{
|
||||
error!(
|
||||
self.log,
|
||||
"Add block to fork choice failed";
|
||||
@@ -1260,7 +1193,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
&self,
|
||||
randao_reveal: Signature,
|
||||
slot: Slot,
|
||||
) -> Result<BeaconInfo<T::EthSpec>, BlockProductionError> {
|
||||
) -> Result<BeaconBlockAndState<T::EthSpec>, BlockProductionError> {
|
||||
let state = self
|
||||
.state_at_slot(slot - 1)
|
||||
.map_err(|_| BlockProductionError::UnableToProduceAtSlot(slot))?;
|
||||
@@ -1281,10 +1214,15 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
mut state: BeaconState<T::EthSpec>,
|
||||
produce_at_slot: Slot,
|
||||
randao_reveal: Signature,
|
||||
) -> Result<BeaconInfo<T::EthSpec>, BlockProductionError> {
|
||||
) -> Result<BeaconBlockAndState<T::EthSpec>, BlockProductionError> {
|
||||
metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS);
|
||||
let timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES);
|
||||
|
||||
let eth1_chain = self
|
||||
.eth1_chain
|
||||
.as_ref()
|
||||
.ok_or_else(|| BlockProductionError::NoEth1ChainConnection)?;
|
||||
|
||||
// If required, transition the new state to the present slot.
|
||||
while state.slot < produce_at_slot {
|
||||
per_slot_processing(&mut state, &self.spec)?;
|
||||
@@ -1309,19 +1247,20 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
let mut block = BeaconBlock {
|
||||
slot: state.slot,
|
||||
parent_root,
|
||||
state_root: Hash256::zero(), // Updated after the state is calculated.
|
||||
signature: Signature::empty_signature(), // To be completed by a validator.
|
||||
state_root: Hash256::zero(),
|
||||
// The block is not signed here, that is the task of a validator client.
|
||||
signature: Signature::empty_signature(),
|
||||
body: BeaconBlockBody {
|
||||
randao_reveal,
|
||||
// TODO: replace with real data.
|
||||
eth1_data: self.eth1_chain.eth1_data_for_block_production(&state)?,
|
||||
eth1_data: eth1_chain.eth1_data_for_block_production(&state, &self.spec)?,
|
||||
graffiti,
|
||||
proposer_slashings: proposer_slashings.into(),
|
||||
attester_slashings: attester_slashings.into(),
|
||||
attestations: self.op_pool.get_attestations(&state, &self.spec).into(),
|
||||
deposits: self.eth1_chain.deposits_for_block_inclusion(&state)?.into(),
|
||||
deposits: eth1_chain
|
||||
.deposits_for_block_inclusion(&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(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -210,22 +210,19 @@ fn interop_genesis_state<T: EthSpec>(
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut proofs = vec![];
|
||||
for i in 1..=deposit_root_leaves.len() {
|
||||
// Note: this implementation is not so efficient.
|
||||
//
|
||||
// If `MerkleTree` had a push method, we could just build one tree and sample it instead of
|
||||
// rebuilding the tree for each deposit.
|
||||
let tree = MerkleTree::create(
|
||||
&deposit_root_leaves[0..i],
|
||||
spec.deposit_contract_tree_depth as usize,
|
||||
);
|
||||
let depth = spec.deposit_contract_tree_depth as usize;
|
||||
let mut tree = MerkleTree::create(&[], depth);
|
||||
for (i, deposit_leaf) in deposit_root_leaves.iter().enumerate() {
|
||||
if let Err(_) = tree.push_leaf(*deposit_leaf, depth) {
|
||||
return Err(String::from("Failed to push leaf"));
|
||||
}
|
||||
|
||||
let (_, mut proof) = tree.generate_proof(i - 1, spec.deposit_contract_tree_depth as usize);
|
||||
proof.push(Hash256::from_slice(&int_to_bytes32(i)));
|
||||
let (_, mut proof) = tree.generate_proof(i, depth);
|
||||
proof.push(Hash256::from_slice(&int_to_bytes32(i + 1)));
|
||||
|
||||
assert_eq!(
|
||||
proof.len(),
|
||||
spec.deposit_contract_tree_depth as usize + 1,
|
||||
depth + 1,
|
||||
"Deposit proof should be correct len"
|
||||
);
|
||||
|
||||
|
||||
627
beacon_node/beacon_chain/src/builder.rs
Normal file
627
beacon_node/beacon_chain/src/builder.rs
Normal file
@@ -0,0 +1,627 @@
|
||||
use crate::eth1_chain::CachingEth1Backend;
|
||||
use crate::events::NullEventHandler;
|
||||
use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY};
|
||||
use crate::{
|
||||
BeaconChain, BeaconChainTypes, CheckPoint, Eth1Chain, Eth1ChainBackend, EventHandler,
|
||||
ForkChoice,
|
||||
};
|
||||
use eth1::Config as Eth1Config;
|
||||
use lmd_ghost::{LmdGhost, ThreadSafeReducedTree};
|
||||
use operation_pool::OperationPool;
|
||||
use parking_lot::RwLock;
|
||||
use slog::{info, Logger};
|
||||
use slot_clock::{SlotClock, TestingSlotClock};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use store::Store;
|
||||
use types::{BeaconBlock, BeaconState, ChainSpec, EthSpec, Hash256, Slot};
|
||||
|
||||
/// An empty struct used to "witness" all the `BeaconChainTypes` traits. It has no user-facing
|
||||
/// functionality and only exists to satisfy the type system.
|
||||
pub struct Witness<TStore, TSlotClock, TLmdGhost, TEth1Backend, TEthSpec, TEventHandler>(
|
||||
PhantomData<(
|
||||
TStore,
|
||||
TSlotClock,
|
||||
TLmdGhost,
|
||||
TEth1Backend,
|
||||
TEthSpec,
|
||||
TEventHandler,
|
||||
)>,
|
||||
);
|
||||
|
||||
impl<TStore, TSlotClock, TLmdGhost, TEth1Backend, TEthSpec, TEventHandler> BeaconChainTypes
|
||||
for Witness<TStore, TSlotClock, TLmdGhost, TEth1Backend, TEthSpec, TEventHandler>
|
||||
where
|
||||
TStore: Store + 'static,
|
||||
TSlotClock: SlotClock + 'static,
|
||||
TLmdGhost: LmdGhost<TStore, TEthSpec> + 'static,
|
||||
TEth1Backend: Eth1ChainBackend<TEthSpec> + 'static,
|
||||
TEthSpec: EthSpec + 'static,
|
||||
TEventHandler: EventHandler<TEthSpec> + 'static,
|
||||
{
|
||||
type Store = TStore;
|
||||
type SlotClock = TSlotClock;
|
||||
type LmdGhost = TLmdGhost;
|
||||
type Eth1Chain = TEth1Backend;
|
||||
type EthSpec = TEthSpec;
|
||||
type EventHandler = TEventHandler;
|
||||
}
|
||||
|
||||
/// Builds a `BeaconChain` by either creating anew from genesis, or, resuming from an existing chain
|
||||
/// persisted to `store`.
|
||||
///
|
||||
/// Types may be elided and the compiler will infer them if all necessary builder methods have been
|
||||
/// called. If type inference errors are being raised, it is likely that not all required methods
|
||||
/// have been called.
|
||||
///
|
||||
/// See the tests for an example of a complete working example.
|
||||
pub struct BeaconChainBuilder<T: BeaconChainTypes> {
|
||||
store: Option<Arc<T::Store>>,
|
||||
/// The finalized checkpoint to anchor the chain. May be genesis or a higher
|
||||
/// checkpoint.
|
||||
pub finalized_checkpoint: Option<CheckPoint<T::EthSpec>>,
|
||||
genesis_block_root: Option<Hash256>,
|
||||
op_pool: Option<OperationPool<T::EthSpec>>,
|
||||
fork_choice: Option<ForkChoice<T>>,
|
||||
eth1_chain: Option<Eth1Chain<T::Eth1Chain, T::EthSpec>>,
|
||||
event_handler: Option<T::EventHandler>,
|
||||
slot_clock: Option<T::SlotClock>,
|
||||
spec: ChainSpec,
|
||||
log: Option<Logger>,
|
||||
}
|
||||
|
||||
impl<TStore, TSlotClock, TLmdGhost, TEth1Backend, TEthSpec, TEventHandler>
|
||||
BeaconChainBuilder<
|
||||
Witness<TStore, TSlotClock, TLmdGhost, TEth1Backend, TEthSpec, TEventHandler>,
|
||||
>
|
||||
where
|
||||
TStore: Store + 'static,
|
||||
TSlotClock: SlotClock + 'static,
|
||||
TLmdGhost: LmdGhost<TStore, TEthSpec> + 'static,
|
||||
TEth1Backend: Eth1ChainBackend<TEthSpec> + 'static,
|
||||
TEthSpec: EthSpec + 'static,
|
||||
TEventHandler: EventHandler<TEthSpec> + 'static,
|
||||
{
|
||||
/// Returns a new builder.
|
||||
///
|
||||
/// The `_eth_spec_instance` parameter is only supplied to make concrete the `TEthSpec` trait.
|
||||
/// This should generally be either the `MinimalEthSpec` or `MainnetEthSpec` types.
|
||||
pub fn new(_eth_spec_instance: TEthSpec) -> Self {
|
||||
Self {
|
||||
store: None,
|
||||
finalized_checkpoint: None,
|
||||
genesis_block_root: None,
|
||||
op_pool: None,
|
||||
fork_choice: None,
|
||||
eth1_chain: None,
|
||||
event_handler: None,
|
||||
slot_clock: None,
|
||||
spec: TEthSpec::default_spec(),
|
||||
log: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Override the default spec (as defined by `TEthSpec`).
|
||||
///
|
||||
/// This method should generally be called immediately after `Self::new` to ensure components
|
||||
/// are started with a consistent spec.
|
||||
pub fn custom_spec(mut self, spec: ChainSpec) -> Self {
|
||||
self.spec = spec;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the store (database).
|
||||
///
|
||||
/// Should generally be called early in the build chain.
|
||||
pub fn store(mut self, store: Arc<TStore>) -> Self {
|
||||
self.store = Some(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the logger.
|
||||
///
|
||||
/// Should generally be called early in the build chain.
|
||||
pub fn logger(mut self, logger: Logger) -> Self {
|
||||
self.log = Some(logger);
|
||||
self
|
||||
}
|
||||
|
||||
/// Attempt to load an existing chain from the builder's `Store`.
|
||||
///
|
||||
/// May initialize several components; including the op_pool and finalized checkpoints.
|
||||
pub fn resume_from_db(mut self) -> Result<Self, String> {
|
||||
let log = self
|
||||
.log
|
||||
.as_ref()
|
||||
.ok_or_else(|| "resume_from_db requires a log".to_string())?;
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Starting beacon chain";
|
||||
"method" => "resume"
|
||||
);
|
||||
|
||||
let store = self
|
||||
.store
|
||||
.clone()
|
||||
.ok_or_else(|| "load_from_store requires a store.".to_string())?;
|
||||
|
||||
let key = Hash256::from_slice(&BEACON_CHAIN_DB_KEY.as_bytes());
|
||||
let p: PersistedBeaconChain<
|
||||
Witness<TStore, TSlotClock, TLmdGhost, TEth1Backend, TEthSpec, TEventHandler>,
|
||||
> = match store.get(&key) {
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"DB error when reading persisted beacon chain: {:?}",
|
||||
e
|
||||
))
|
||||
}
|
||||
Ok(None) => return Err("No persisted beacon chain found in store".into()),
|
||||
Ok(Some(p)) => p,
|
||||
};
|
||||
|
||||
self.op_pool = Some(
|
||||
p.op_pool
|
||||
.into_operation_pool(&p.canonical_head.beacon_state, &self.spec),
|
||||
);
|
||||
|
||||
self.finalized_checkpoint = Some(p.canonical_head);
|
||||
self.genesis_block_root = Some(p.genesis_block_root);
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Starts a new chain from a genesis state.
|
||||
pub fn genesis_state(
|
||||
mut self,
|
||||
mut beacon_state: BeaconState<TEthSpec>,
|
||||
) -> Result<Self, String> {
|
||||
let store = self
|
||||
.store
|
||||
.clone()
|
||||
.ok_or_else(|| "genesis_state requires a store")?;
|
||||
|
||||
let mut beacon_block = genesis_block(&beacon_state, &self.spec);
|
||||
|
||||
beacon_state
|
||||
.build_all_caches(&self.spec)
|
||||
.map_err(|e| format!("Failed to build genesis state caches: {:?}", e))?;
|
||||
|
||||
let beacon_state_root = beacon_state.canonical_root();
|
||||
beacon_block.state_root = beacon_state_root;
|
||||
let beacon_block_root = beacon_block.canonical_root();
|
||||
|
||||
self.genesis_block_root = Some(beacon_block_root);
|
||||
|
||||
store
|
||||
.put(&beacon_state_root, &beacon_state)
|
||||
.map_err(|e| format!("Failed to store genesis state: {:?}", e))?;
|
||||
store
|
||||
.put(&beacon_block_root, &beacon_block)
|
||||
.map_err(|e| format!("Failed to store genesis block: {:?}", e))?;
|
||||
|
||||
// Store the genesis block under the `ZERO_HASH` key.
|
||||
store.put(&Hash256::zero(), &beacon_block).map_err(|e| {
|
||||
format!(
|
||||
"Failed to store genesis block under 0x00..00 alias: {:?}",
|
||||
e
|
||||
)
|
||||
})?;
|
||||
|
||||
self.finalized_checkpoint = Some(CheckPoint {
|
||||
beacon_block_root,
|
||||
beacon_block,
|
||||
beacon_state_root,
|
||||
beacon_state,
|
||||
});
|
||||
|
||||
Ok(self.empty_op_pool())
|
||||
}
|
||||
|
||||
/// Sets the `BeaconChain` fork choice backend.
|
||||
///
|
||||
/// Requires the store and state to have been specified earlier in the build chain.
|
||||
pub fn fork_choice_backend(mut self, backend: TLmdGhost) -> Result<Self, String> {
|
||||
let store = self
|
||||
.store
|
||||
.clone()
|
||||
.ok_or_else(|| "reduced_tree_fork_choice requires a store")?;
|
||||
let genesis_block_root = self
|
||||
.genesis_block_root
|
||||
.ok_or_else(|| "fork_choice_backend requires a genesis_block_root")?;
|
||||
|
||||
self.fork_choice = Some(ForkChoice::new(
|
||||
store,
|
||||
backend,
|
||||
genesis_block_root,
|
||||
self.spec.genesis_slot,
|
||||
));
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Sets the `BeaconChain` eth1 backend.
|
||||
pub fn eth1_backend(mut self, backend: Option<TEth1Backend>) -> Self {
|
||||
self.eth1_chain = backend.map(Eth1Chain::new);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `BeaconChain` event handler backend.
|
||||
///
|
||||
/// For example, provide `WebSocketSender` as a `handler`.
|
||||
pub fn event_handler(mut self, handler: TEventHandler) -> Self {
|
||||
self.event_handler = Some(handler);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `BeaconChain` slot clock.
|
||||
///
|
||||
/// For example, provide `SystemTimeSlotClock` as a `clock`.
|
||||
pub fn slot_clock(mut self, clock: TSlotClock) -> Self {
|
||||
self.slot_clock = Some(clock);
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a new, empty operation pool.
|
||||
fn empty_op_pool(mut self) -> Self {
|
||||
self.op_pool = Some(OperationPool::new());
|
||||
self
|
||||
}
|
||||
|
||||
/// Consumes `self`, returning a `BeaconChain` if all required parameters have been supplied.
|
||||
///
|
||||
/// An error will be returned at runtime if all required parameters have not been configured.
|
||||
///
|
||||
/// Will also raise ambiguous type errors at compile time if some parameters have not been
|
||||
/// configured.
|
||||
#[allow(clippy::type_complexity)] // I think there's nothing to be gained here from a type alias.
|
||||
pub fn build(
|
||||
self,
|
||||
) -> Result<
|
||||
BeaconChain<Witness<TStore, TSlotClock, TLmdGhost, TEth1Backend, TEthSpec, TEventHandler>>,
|
||||
String,
|
||||
> {
|
||||
let mut canonical_head = self
|
||||
.finalized_checkpoint
|
||||
.ok_or_else(|| "Cannot build without a state".to_string())?;
|
||||
|
||||
canonical_head
|
||||
.beacon_state
|
||||
.build_all_caches(&self.spec)
|
||||
.map_err(|e| format!("Failed to build state caches: {:?}", e))?;
|
||||
|
||||
let log = self
|
||||
.log
|
||||
.ok_or_else(|| "Cannot build without a logger".to_string())?;
|
||||
|
||||
if canonical_head.beacon_block.state_root != canonical_head.beacon_state_root {
|
||||
return Err("beacon_block.state_root != beacon_state".to_string());
|
||||
}
|
||||
|
||||
let beacon_chain = BeaconChain {
|
||||
spec: self.spec,
|
||||
store: self
|
||||
.store
|
||||
.ok_or_else(|| "Cannot build without store".to_string())?,
|
||||
slot_clock: self
|
||||
.slot_clock
|
||||
.ok_or_else(|| "Cannot build without slot clock".to_string())?,
|
||||
op_pool: self
|
||||
.op_pool
|
||||
.ok_or_else(|| "Cannot build without op pool".to_string())?,
|
||||
eth1_chain: self.eth1_chain,
|
||||
canonical_head: RwLock::new(canonical_head),
|
||||
genesis_block_root: self
|
||||
.genesis_block_root
|
||||
.ok_or_else(|| "Cannot build without a genesis block root".to_string())?,
|
||||
fork_choice: self
|
||||
.fork_choice
|
||||
.ok_or_else(|| "Cannot build without a fork choice".to_string())?,
|
||||
event_handler: self
|
||||
.event_handler
|
||||
.ok_or_else(|| "Cannot build without an event handler".to_string())?,
|
||||
log: log.clone(),
|
||||
};
|
||||
|
||||
info!(
|
||||
log,
|
||||
"Beacon chain initialized";
|
||||
"head_state" => format!("{}", beacon_chain.head().beacon_state_root),
|
||||
"head_block" => format!("{}", beacon_chain.head().beacon_block_root),
|
||||
"head_slot" => format!("{}", beacon_chain.head().beacon_block.slot),
|
||||
);
|
||||
|
||||
Ok(beacon_chain)
|
||||
}
|
||||
}
|
||||
|
||||
impl<TStore, TSlotClock, TEth1Backend, TEthSpec, TEventHandler>
|
||||
BeaconChainBuilder<
|
||||
Witness<
|
||||
TStore,
|
||||
TSlotClock,
|
||||
ThreadSafeReducedTree<TStore, TEthSpec>,
|
||||
TEth1Backend,
|
||||
TEthSpec,
|
||||
TEventHandler,
|
||||
>,
|
||||
>
|
||||
where
|
||||
TStore: Store + 'static,
|
||||
TSlotClock: SlotClock + 'static,
|
||||
TEth1Backend: Eth1ChainBackend<TEthSpec> + 'static,
|
||||
TEthSpec: EthSpec + 'static,
|
||||
TEventHandler: EventHandler<TEthSpec> + 'static,
|
||||
{
|
||||
/// Initializes a new, empty (no recorded votes or blocks) fork choice, using the
|
||||
/// `ThreadSafeReducedTree` backend.
|
||||
///
|
||||
/// Requires the store and state to be initialized.
|
||||
pub fn empty_reduced_tree_fork_choice(self) -> Result<Self, String> {
|
||||
let store = self
|
||||
.store
|
||||
.clone()
|
||||
.ok_or_else(|| "reduced_tree_fork_choice requires a store")?;
|
||||
let finalized_checkpoint = &self
|
||||
.finalized_checkpoint
|
||||
.as_ref()
|
||||
.expect("should have finalized checkpoint");
|
||||
|
||||
let backend = ThreadSafeReducedTree::new(
|
||||
store.clone(),
|
||||
&finalized_checkpoint.beacon_block,
|
||||
finalized_checkpoint.beacon_block_root,
|
||||
);
|
||||
|
||||
self.fork_choice_backend(backend)
|
||||
}
|
||||
}
|
||||
|
||||
impl<TStore, TSlotClock, TLmdGhost, TEthSpec, TEventHandler>
|
||||
BeaconChainBuilder<
|
||||
Witness<
|
||||
TStore,
|
||||
TSlotClock,
|
||||
TLmdGhost,
|
||||
CachingEth1Backend<TEthSpec, TStore>,
|
||||
TEthSpec,
|
||||
TEventHandler,
|
||||
>,
|
||||
>
|
||||
where
|
||||
TStore: Store + 'static,
|
||||
TSlotClock: SlotClock + 'static,
|
||||
TLmdGhost: LmdGhost<TStore, TEthSpec> + 'static,
|
||||
TEthSpec: EthSpec + 'static,
|
||||
TEventHandler: EventHandler<TEthSpec> + 'static,
|
||||
{
|
||||
/// Sets the `BeaconChain` eth1 back-end to `CachingEth1Backend`.
|
||||
pub fn caching_eth1_backend(self, backend: CachingEth1Backend<TEthSpec, TStore>) -> Self {
|
||||
self.eth1_backend(Some(backend))
|
||||
}
|
||||
|
||||
/// Do not use any eth1 backend. The client will not be able to produce beacon blocks.
|
||||
pub fn no_eth1_backend(self) -> Self {
|
||||
self.eth1_backend(None)
|
||||
}
|
||||
|
||||
/// Sets the `BeaconChain` eth1 back-end to produce predictably junk data when producing blocks.
|
||||
pub fn dummy_eth1_backend(mut self) -> Result<Self, String> {
|
||||
let log = self
|
||||
.log
|
||||
.as_ref()
|
||||
.ok_or_else(|| "dummy_eth1_backend requires a log".to_string())?;
|
||||
let store = self
|
||||
.store
|
||||
.clone()
|
||||
.ok_or_else(|| "dummy_eth1_backend requires a store.".to_string())?;
|
||||
|
||||
let backend = CachingEth1Backend::new(Eth1Config::default(), log.clone(), store);
|
||||
|
||||
let mut eth1_chain = Eth1Chain::new(backend);
|
||||
eth1_chain.use_dummy_backend = true;
|
||||
|
||||
self.eth1_chain = Some(eth1_chain);
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<TStore, TLmdGhost, TEth1Backend, TEthSpec, TEventHandler>
|
||||
BeaconChainBuilder<
|
||||
Witness<TStore, TestingSlotClock, TLmdGhost, TEth1Backend, TEthSpec, TEventHandler>,
|
||||
>
|
||||
where
|
||||
TStore: Store + 'static,
|
||||
TLmdGhost: LmdGhost<TStore, TEthSpec> + 'static,
|
||||
TEth1Backend: Eth1ChainBackend<TEthSpec> + 'static,
|
||||
TEthSpec: EthSpec + 'static,
|
||||
TEventHandler: EventHandler<TEthSpec> + 'static,
|
||||
{
|
||||
/// Sets the `BeaconChain` slot clock to `TestingSlotClock`.
|
||||
///
|
||||
/// Requires the state to be initialized.
|
||||
pub fn testing_slot_clock(self, slot_duration: Duration) -> Result<Self, String> {
|
||||
let genesis_time = self
|
||||
.finalized_checkpoint
|
||||
.as_ref()
|
||||
.ok_or_else(|| "testing_slot_clock requires an initialized state")?
|
||||
.beacon_state
|
||||
.genesis_time;
|
||||
|
||||
let slot_clock = TestingSlotClock::new(
|
||||
Slot::new(0),
|
||||
Duration::from_secs(genesis_time),
|
||||
slot_duration,
|
||||
);
|
||||
|
||||
Ok(self.slot_clock(slot_clock))
|
||||
}
|
||||
}
|
||||
|
||||
impl<TStore, TSlotClock, TLmdGhost, TEth1Backend, TEthSpec>
|
||||
BeaconChainBuilder<
|
||||
Witness<TStore, TSlotClock, TLmdGhost, TEth1Backend, TEthSpec, NullEventHandler<TEthSpec>>,
|
||||
>
|
||||
where
|
||||
TStore: Store + 'static,
|
||||
TSlotClock: SlotClock + 'static,
|
||||
TLmdGhost: LmdGhost<TStore, TEthSpec> + 'static,
|
||||
TEth1Backend: Eth1ChainBackend<TEthSpec> + 'static,
|
||||
TEthSpec: EthSpec + 'static,
|
||||
{
|
||||
/// Sets the `BeaconChain` event handler to `NullEventHandler`.
|
||||
pub fn null_event_handler(self) -> Self {
|
||||
let handler = NullEventHandler::default();
|
||||
self.event_handler(handler)
|
||||
}
|
||||
}
|
||||
|
||||
fn genesis_block<T: EthSpec>(genesis_state: &BeaconState<T>, spec: &ChainSpec) -> BeaconBlock<T> {
|
||||
let mut genesis_block = BeaconBlock::empty(&spec);
|
||||
|
||||
genesis_block.state_root = genesis_state.canonical_root();
|
||||
|
||||
genesis_block
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use eth2_hashing::hash;
|
||||
use genesis::{generate_deterministic_keypairs, interop_genesis_state};
|
||||
use sloggers::{null::NullLoggerBuilder, Build};
|
||||
use ssz::Encode;
|
||||
use std::time::Duration;
|
||||
use store::MemoryStore;
|
||||
use types::{EthSpec, MinimalEthSpec, Slot};
|
||||
|
||||
type TestEthSpec = MinimalEthSpec;
|
||||
|
||||
fn get_logger() -> Logger {
|
||||
let builder = NullLoggerBuilder;
|
||||
builder.build().expect("should build logger")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recent_genesis() {
|
||||
let validator_count = 8;
|
||||
let genesis_time = 13371337;
|
||||
|
||||
let log = get_logger();
|
||||
let store = Arc::new(MemoryStore::open());
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
|
||||
let genesis_state = interop_genesis_state(
|
||||
&generate_deterministic_keypairs(validator_count),
|
||||
genesis_time,
|
||||
&spec,
|
||||
)
|
||||
.expect("should create interop genesis state");
|
||||
|
||||
let chain = BeaconChainBuilder::new(MinimalEthSpec)
|
||||
.logger(log.clone())
|
||||
.store(store.clone())
|
||||
.genesis_state(genesis_state)
|
||||
.expect("should build state using recent genesis")
|
||||
.dummy_eth1_backend()
|
||||
.expect("should build the dummy eth1 backend")
|
||||
.null_event_handler()
|
||||
.testing_slot_clock(Duration::from_secs(1))
|
||||
.expect("should configure testing slot clock")
|
||||
.empty_reduced_tree_fork_choice()
|
||||
.expect("should add fork choice to builder")
|
||||
.build()
|
||||
.expect("should build");
|
||||
|
||||
let head = chain.head();
|
||||
let state = head.beacon_state;
|
||||
let block = head.beacon_block;
|
||||
|
||||
assert_eq!(state.slot, Slot::new(0), "should start from genesis");
|
||||
assert_eq!(
|
||||
state.genesis_time, 13371337,
|
||||
"should have the correct genesis time"
|
||||
);
|
||||
assert_eq!(
|
||||
block.state_root,
|
||||
state.canonical_root(),
|
||||
"block should have correct state root"
|
||||
);
|
||||
assert_eq!(
|
||||
chain
|
||||
.store
|
||||
.get::<BeaconBlock<_>>(&Hash256::zero())
|
||||
.expect("should read db")
|
||||
.expect("should find genesis block"),
|
||||
block,
|
||||
"should store genesis block under zero hash alias"
|
||||
);
|
||||
assert_eq!(
|
||||
state.validators.len(),
|
||||
validator_count,
|
||||
"should have correct validator count"
|
||||
);
|
||||
assert_eq!(
|
||||
chain.genesis_block_root,
|
||||
block.canonical_root(),
|
||||
"should have correct genesis block root"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interop_state() {
|
||||
let validator_count = 16;
|
||||
let genesis_time = 42;
|
||||
let spec = &TestEthSpec::default_spec();
|
||||
|
||||
let keypairs = generate_deterministic_keypairs(validator_count);
|
||||
|
||||
let state = interop_genesis_state::<TestEthSpec>(&keypairs, genesis_time, spec)
|
||||
.expect("should build state");
|
||||
|
||||
assert_eq!(
|
||||
state.eth1_data.block_hash,
|
||||
Hash256::from_slice(&[0x42; 32]),
|
||||
"eth1 block hash should be co-ordinated junk"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state.genesis_time, genesis_time,
|
||||
"genesis time should be as specified"
|
||||
);
|
||||
|
||||
for b in &state.balances {
|
||||
assert_eq!(
|
||||
*b, spec.max_effective_balance,
|
||||
"validator balances should be max effective balance"
|
||||
);
|
||||
}
|
||||
|
||||
for v in &state.validators {
|
||||
let creds = v.withdrawal_credentials.as_bytes();
|
||||
assert_eq!(
|
||||
creds[0], spec.bls_withdrawal_prefix_byte,
|
||||
"first byte of withdrawal creds should be bls prefix"
|
||||
);
|
||||
assert_eq!(
|
||||
&creds[1..],
|
||||
&hash(&v.pubkey.as_ssz_bytes())[1..],
|
||||
"rest of withdrawal creds should be pubkey hash"
|
||||
)
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
state.balances.len(),
|
||||
validator_count,
|
||||
"validator balances len should be correct"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state.validators.len(),
|
||||
validator_count,
|
||||
"validator count should be correct"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ macro_rules! easy_from_to {
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BeaconChainError {
|
||||
InsufficientValidators,
|
||||
BadRecentBlockRoots,
|
||||
UnableToReadSlot,
|
||||
RevertedFinalizedEpoch {
|
||||
previous_epoch: Epoch,
|
||||
@@ -55,6 +54,9 @@ pub enum BlockProductionError {
|
||||
BlockProcessingError(BlockProcessingError),
|
||||
Eth1ChainError(Eth1ChainError),
|
||||
BeaconStateError(BeaconStateError),
|
||||
/// The `BeaconChain` was explicitly configured _without_ a connection to eth1, therefore it
|
||||
/// cannot produce blocks.
|
||||
NoEth1ChainConnection,
|
||||
}
|
||||
|
||||
easy_from_to!(BlockProcessingError, BlockProductionError);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
use types::{Attestation, BeaconBlock, Epoch, EthSpec, Hash256};
|
||||
pub use websocket_server::WebSocketSender;
|
||||
|
||||
pub trait EventHandler<T: EthSpec>: Sized + Send + Sync {
|
||||
fn register(&self, kind: EventKind<T>) -> Result<(), String>;
|
||||
@@ -8,6 +9,15 @@ pub trait EventHandler<T: EthSpec>: Sized + Send + Sync {
|
||||
|
||||
pub struct NullEventHandler<T: EthSpec>(PhantomData<T>);
|
||||
|
||||
impl<T: EthSpec> EventHandler<T> for WebSocketSender<T> {
|
||||
fn register(&self, kind: EventKind<T>) -> Result<(), String> {
|
||||
self.send_string(
|
||||
serde_json::to_string(&kind)
|
||||
.map_err(|e| format!("Unable to serialize event: {:?}", e))?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> EventHandler<T> for NullEventHandler<T> {
|
||||
fn register(&self, _kind: EventKind<T>) -> Result<(), String> {
|
||||
Ok(())
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::{metrics, BeaconChain, BeaconChainTypes};
|
||||
use crate::{errors::BeaconChainError, metrics, BeaconChain, BeaconChainTypes};
|
||||
use lmd_ghost::LmdGhost;
|
||||
use state_processing::common::get_attesting_indices;
|
||||
use parking_lot::RwLock;
|
||||
use state_processing::{common::get_attesting_indices, per_slot_processing};
|
||||
use std::sync::Arc;
|
||||
use store::{Error as StoreError, Store};
|
||||
use types::{
|
||||
Attestation, BeaconBlock, BeaconState, BeaconStateError, Epoch, EthSpec, Hash256, Slot,
|
||||
Attestation, BeaconBlock, BeaconState, BeaconStateError, Checkpoint, EthSpec, Hash256, Slot,
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -16,6 +17,7 @@ pub enum Error {
|
||||
BackendError(String),
|
||||
BeaconStateError(BeaconStateError),
|
||||
StoreError(StoreError),
|
||||
BeaconChainError(Box<BeaconChainError>),
|
||||
}
|
||||
|
||||
pub struct ForkChoice<T: BeaconChainTypes> {
|
||||
@@ -26,6 +28,10 @@ pub struct ForkChoice<T: BeaconChainTypes> {
|
||||
/// Does not necessarily need to be the _actual_ genesis, it suffices to be the finalized root
|
||||
/// whenever the struct was instantiated.
|
||||
genesis_block_root: Hash256,
|
||||
/// The fork choice rule's current view of the justified checkpoint.
|
||||
justified_checkpoint: RwLock<Checkpoint>,
|
||||
/// The best justified checkpoint we've seen, which may be ahead of `justified_checkpoint`.
|
||||
best_justified_checkpoint: RwLock<Checkpoint>,
|
||||
}
|
||||
|
||||
impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
@@ -35,41 +41,89 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
/// block.
|
||||
pub fn new(
|
||||
store: Arc<T::Store>,
|
||||
genesis_block: &BeaconBlock<T::EthSpec>,
|
||||
backend: T::LmdGhost,
|
||||
genesis_block_root: Hash256,
|
||||
genesis_slot: Slot,
|
||||
) -> Self {
|
||||
let justified_checkpoint = Checkpoint {
|
||||
epoch: genesis_slot.epoch(T::EthSpec::slots_per_epoch()),
|
||||
root: genesis_block_root,
|
||||
};
|
||||
Self {
|
||||
store: store.clone(),
|
||||
backend: T::LmdGhost::new(store, genesis_block, genesis_block_root),
|
||||
backend,
|
||||
genesis_block_root,
|
||||
justified_checkpoint: RwLock::new(justified_checkpoint.clone()),
|
||||
best_justified_checkpoint: RwLock::new(justified_checkpoint),
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine whether the fork choice's view of the justified checkpoint should be updated.
|
||||
///
|
||||
/// To prevent the bouncing attack, an update is allowed only in these conditions:
|
||||
///
|
||||
/// * We're in the first SAFE_SLOTS_TO_UPDATE_JUSTIFIED slots of the epoch, or
|
||||
/// * The new justified checkpoint is a descendant of the current justified checkpoint
|
||||
fn should_update_justified_checkpoint(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
new_justified_checkpoint: &Checkpoint,
|
||||
) -> Result<bool> {
|
||||
if Self::compute_slots_since_epoch_start(chain.slot()?)
|
||||
< chain.spec.safe_slots_to_update_justified
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let justified_checkpoint = self.justified_checkpoint.read().clone();
|
||||
|
||||
let current_justified_block = chain
|
||||
.get_block(&justified_checkpoint.root)?
|
||||
.ok_or_else(|| Error::MissingBlock(justified_checkpoint.root))?;
|
||||
|
||||
let new_justified_block = chain
|
||||
.get_block(&new_justified_checkpoint.root)?
|
||||
.ok_or_else(|| Error::MissingBlock(new_justified_checkpoint.root))?;
|
||||
|
||||
let slots_per_epoch = T::EthSpec::slots_per_epoch();
|
||||
|
||||
Ok(
|
||||
new_justified_block.slot > justified_checkpoint.epoch.start_slot(slots_per_epoch)
|
||||
&& chain.get_ancestor_block_root(
|
||||
new_justified_checkpoint.root,
|
||||
current_justified_block.slot,
|
||||
)? == Some(justified_checkpoint.root),
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculate how far `slot` lies from the start of its epoch.
|
||||
fn compute_slots_since_epoch_start(slot: Slot) -> u64 {
|
||||
let slots_per_epoch = T::EthSpec::slots_per_epoch();
|
||||
(slot - slot.epoch(slots_per_epoch).start_slot(slots_per_epoch)).as_u64()
|
||||
}
|
||||
|
||||
/// Run the fork choice rule to determine the head.
|
||||
pub fn find_head(&self, chain: &BeaconChain<T>) -> Result<Hash256> {
|
||||
let timer = metrics::start_timer(&metrics::FORK_CHOICE_FIND_HEAD_TIMES);
|
||||
|
||||
let start_slot = |epoch: Epoch| epoch.start_slot(T::EthSpec::slots_per_epoch());
|
||||
|
||||
// From the specification:
|
||||
//
|
||||
// Let justified_head be the descendant of finalized_head with the highest epoch that has
|
||||
// been justified for at least 1 epoch ... If no such descendant exists,
|
||||
// set justified_head to finalized_head.
|
||||
let (start_state, start_block_root, start_block_slot) = {
|
||||
let state = &chain.head().beacon_state;
|
||||
// Check if we should update our view of the justified checkpoint.
|
||||
// Doing this check here should be quasi-equivalent to the update in the `on_tick`
|
||||
// function of the spec, so long as `find_head` is called at least once during the first
|
||||
// SAFE_SLOTS_TO_UPDATE_JUSTIFIED slots.
|
||||
let best_justified_checkpoint = self.best_justified_checkpoint.read();
|
||||
if self.should_update_justified_checkpoint(chain, &best_justified_checkpoint)? {
|
||||
*self.justified_checkpoint.write() = best_justified_checkpoint.clone();
|
||||
}
|
||||
|
||||
let (block_root, block_slot) =
|
||||
if state.current_epoch() + 1 > state.current_justified_checkpoint.epoch {
|
||||
(
|
||||
state.current_justified_checkpoint.root,
|
||||
start_slot(state.current_justified_checkpoint.epoch),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
state.finalized_checkpoint.root,
|
||||
start_slot(state.finalized_checkpoint.epoch),
|
||||
)
|
||||
};
|
||||
let current_justified_checkpoint = self.justified_checkpoint.read().clone();
|
||||
|
||||
let (block_root, block_justified_slot) = (
|
||||
current_justified_checkpoint.root,
|
||||
current_justified_checkpoint
|
||||
.epoch
|
||||
.start_slot(T::EthSpec::slots_per_epoch()),
|
||||
);
|
||||
|
||||
let block = chain
|
||||
.store
|
||||
@@ -83,12 +137,17 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
block_root
|
||||
};
|
||||
|
||||
let state = chain
|
||||
.store
|
||||
.get::<BeaconState<T::EthSpec>>(&block.state_root)?
|
||||
let mut state = chain
|
||||
.get_state(&block.state_root)?
|
||||
.ok_or_else(|| Error::MissingState(block.state_root))?;
|
||||
|
||||
(state, block_root, block_slot)
|
||||
// Fast-forward the state to the start slot of the epoch where it was justified.
|
||||
for _ in block.slot.as_u64()..block_justified_slot.as_u64() {
|
||||
per_slot_processing(&mut state, &chain.spec)
|
||||
.map_err(|e| BeaconChainError::SlotProcessingError(e))?
|
||||
}
|
||||
|
||||
(state, block_root, block_justified_slot)
|
||||
};
|
||||
|
||||
// A function that returns the weight for some validator index.
|
||||
@@ -111,10 +170,11 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
|
||||
/// Process all attestations in the given `block`.
|
||||
///
|
||||
/// Assumes the block (and therefore it's attestations) are valid. It is a logic error to
|
||||
/// Assumes the block (and therefore its attestations) are valid. It is a logic error to
|
||||
/// provide an invalid block.
|
||||
pub fn process_block(
|
||||
&self,
|
||||
chain: &BeaconChain<T>,
|
||||
state: &BeaconState<T::EthSpec>,
|
||||
block: &BeaconBlock<T::EthSpec>,
|
||||
block_root: Hash256,
|
||||
@@ -137,6 +197,16 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should update our view of the justified checkpoint
|
||||
if state.current_justified_checkpoint.epoch > self.justified_checkpoint.read().epoch {
|
||||
*self.best_justified_checkpoint.write() = state.current_justified_checkpoint.clone();
|
||||
if self
|
||||
.should_update_justified_checkpoint(chain, &state.current_justified_checkpoint)?
|
||||
{
|
||||
*self.justified_checkpoint.write() = state.current_justified_checkpoint.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// This does not apply a vote to the block, it just makes fork choice aware of the block so
|
||||
// it can still be identified as the head even if it doesn't have any votes.
|
||||
//
|
||||
@@ -228,6 +298,12 @@ impl From<BeaconStateError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BeaconChainError> for Error {
|
||||
fn from(e: BeaconChainError) -> Error {
|
||||
Error::BeaconChainError(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoreError> for Error {
|
||||
fn from(e: StoreError) -> Error {
|
||||
Error::StoreError(e)
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
extern crate lazy_static;
|
||||
|
||||
mod beacon_chain;
|
||||
mod beacon_chain_builder;
|
||||
pub mod builder;
|
||||
mod checkpoint;
|
||||
mod errors;
|
||||
mod eth1_chain;
|
||||
pub mod eth1_chain;
|
||||
pub mod events;
|
||||
mod fork_choice;
|
||||
mod iter;
|
||||
@@ -19,15 +19,16 @@ pub use self::beacon_chain::{
|
||||
};
|
||||
pub use self::checkpoint::CheckPoint;
|
||||
pub use self::errors::{BeaconChainError, BlockProductionError};
|
||||
pub use beacon_chain_builder::BeaconChainBuilder;
|
||||
pub use eth1_chain::{Eth1ChainBackend, InteropEth1ChainBackend};
|
||||
pub use eth1_chain::{Eth1Chain, Eth1ChainBackend};
|
||||
pub use events::EventHandler;
|
||||
pub use fork_choice::ForkChoice;
|
||||
pub use lmd_ghost;
|
||||
pub use metrics::scrape_for_metrics;
|
||||
pub use parking_lot;
|
||||
pub use slot_clock;
|
||||
pub use state_processing::per_block_processing::errors::{
|
||||
AttestationValidationError, AttesterSlashingValidationError, DepositValidationError,
|
||||
ExitValidationError, ProposerSlashingValidationError, TransferValidationError,
|
||||
ExitValidationError, ProposerSlashingValidationError,
|
||||
};
|
||||
pub use store;
|
||||
pub use types;
|
||||
|
||||
@@ -172,8 +172,6 @@ lazy_static! {
|
||||
try_create_int_gauge("beacon_head_state_finalized_root", "Finalized root at the head of the chain");
|
||||
pub static ref HEAD_STATE_FINALIZED_EPOCH: Result<IntGauge> =
|
||||
try_create_int_gauge("beacon_head_state_finalized_epoch", "Finalized epoch at the head of the chain");
|
||||
pub static ref HEAD_STATE_SHARDS: Result<IntGauge> =
|
||||
try_create_int_gauge("beacon_head_state_shard_total", "Count of shards in the beacon chain");
|
||||
pub static ref HEAD_STATE_TOTAL_VALIDATORS: Result<IntGauge> =
|
||||
try_create_int_gauge("beacon_head_state_total_validators_total", "Count of validators at the head of the chain");
|
||||
pub static ref HEAD_STATE_ACTIVE_VALIDATORS: Result<IntGauge> =
|
||||
@@ -226,7 +224,6 @@ fn scrape_head_state<T: BeaconChainTypes>(state: &BeaconState<T::EthSpec>, state
|
||||
&HEAD_STATE_FINALIZED_EPOCH,
|
||||
state.finalized_checkpoint.epoch,
|
||||
);
|
||||
set_gauge_by_usize(&HEAD_STATE_SHARDS, state.previous_crosslinks.len());
|
||||
set_gauge_by_usize(&HEAD_STATE_TOTAL_VALIDATORS, state.validators.len());
|
||||
set_gauge_by_u64(&HEAD_STATE_VALIDATOR_BALANCES, state.balances.iter().sum());
|
||||
set_gauge_by_usize(
|
||||
|
||||
@@ -1,28 +1,38 @@
|
||||
use crate::{
|
||||
events::NullEventHandler, AttestationProcessingOutcome, BeaconChain, BeaconChainBuilder,
|
||||
BeaconChainTypes, BlockProcessingOutcome, InteropEth1ChainBackend,
|
||||
builder::{BeaconChainBuilder, Witness},
|
||||
eth1_chain::CachingEth1Backend,
|
||||
events::NullEventHandler,
|
||||
AttestationProcessingOutcome, BeaconChain, BeaconChainTypes, BlockProcessingOutcome,
|
||||
};
|
||||
use lmd_ghost::LmdGhost;
|
||||
use genesis::interop_genesis_state;
|
||||
use lmd_ghost::ThreadSafeReducedTree;
|
||||
use rayon::prelude::*;
|
||||
use sloggers::{terminal::TerminalLoggerBuilder, types::Severity, Build};
|
||||
use slot_clock::TestingSlotClock;
|
||||
use state_processing::per_slot_processing;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use store::MemoryStore;
|
||||
use tree_hash::{SignedRoot, TreeHash};
|
||||
use types::{
|
||||
AggregateSignature, Attestation, AttestationDataAndCustodyBit, BeaconBlock, BeaconState,
|
||||
BitList, ChainSpec, Domain, EthSpec, Hash256, Keypair, RelativeEpoch, SecretKey, Signature,
|
||||
Slot,
|
||||
AggregateSignature, Attestation, BeaconBlock, BeaconState, BitList, ChainSpec, Domain, EthSpec,
|
||||
Hash256, Keypair, SecretKey, Signature, Slot,
|
||||
};
|
||||
|
||||
pub use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY};
|
||||
pub use types::test_utils::generate_deterministic_keypairs;
|
||||
|
||||
pub use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY};
|
||||
|
||||
pub const HARNESS_GENESIS_TIME: u64 = 1_567_552_690; // 4th September 2019
|
||||
|
||||
pub type HarnessType<E> = Witness<
|
||||
MemoryStore,
|
||||
TestingSlotClock,
|
||||
ThreadSafeReducedTree<MemoryStore, E>,
|
||||
CachingEth1Backend<E, MemoryStore>,
|
||||
E,
|
||||
NullEventHandler<E>,
|
||||
>;
|
||||
|
||||
/// Indicates how the `BeaconChainHarness` should produce blocks.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BlockStrategy {
|
||||
@@ -48,50 +58,19 @@ pub enum AttestationStrategy {
|
||||
SomeValidators(Vec<usize>),
|
||||
}
|
||||
|
||||
/// Used to make the `BeaconChainHarness` generic over some types.
|
||||
pub struct CommonTypes<L, E>
|
||||
where
|
||||
L: LmdGhost<MemoryStore, E>,
|
||||
E: EthSpec,
|
||||
{
|
||||
_phantom_l: PhantomData<L>,
|
||||
_phantom_e: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<L, E> BeaconChainTypes for CommonTypes<L, E>
|
||||
where
|
||||
L: LmdGhost<MemoryStore, E> + 'static,
|
||||
E: EthSpec,
|
||||
{
|
||||
type Store = MemoryStore;
|
||||
type SlotClock = TestingSlotClock;
|
||||
type LmdGhost = L;
|
||||
type Eth1Chain = InteropEth1ChainBackend<E>;
|
||||
type EthSpec = E;
|
||||
type EventHandler = NullEventHandler<E>;
|
||||
}
|
||||
|
||||
/// A testing harness which can instantiate a `BeaconChain` and populate it with blocks and
|
||||
/// attestations.
|
||||
///
|
||||
/// Used for testing.
|
||||
pub struct BeaconChainHarness<L, E>
|
||||
where
|
||||
L: LmdGhost<MemoryStore, E> + 'static,
|
||||
E: EthSpec,
|
||||
{
|
||||
pub chain: BeaconChain<CommonTypes<L, E>>,
|
||||
pub struct BeaconChainHarness<T: BeaconChainTypes> {
|
||||
pub chain: BeaconChain<T>,
|
||||
pub keypairs: Vec<Keypair>,
|
||||
pub spec: ChainSpec,
|
||||
}
|
||||
|
||||
impl<L, E> BeaconChainHarness<L, E>
|
||||
where
|
||||
L: LmdGhost<MemoryStore, E>,
|
||||
E: EthSpec,
|
||||
{
|
||||
impl<E: EthSpec> BeaconChainHarness<HarnessType<E>> {
|
||||
/// Instantiate a new harness with `validator_count` initial validators.
|
||||
pub fn new(keypairs: Vec<Keypair>) -> Self {
|
||||
pub fn new(eth_spec_instance: E, keypairs: Vec<Keypair>) -> Self {
|
||||
let spec = E::default_spec();
|
||||
|
||||
let log = TerminalLoggerBuilder::new()
|
||||
@@ -99,22 +78,29 @@ where
|
||||
.build()
|
||||
.expect("logger should build");
|
||||
|
||||
let store = Arc::new(MemoryStore::open());
|
||||
|
||||
let chain =
|
||||
BeaconChainBuilder::quick_start(HARNESS_GENESIS_TIME, &keypairs, spec.clone(), log)
|
||||
.unwrap_or_else(|e| panic!("Failed to create beacon chain builder: {}", e))
|
||||
.build(
|
||||
store.clone(),
|
||||
InteropEth1ChainBackend::default(),
|
||||
NullEventHandler::default(),
|
||||
)
|
||||
.unwrap_or_else(|e| panic!("Failed to build beacon chain: {}", e));
|
||||
let chain = BeaconChainBuilder::new(eth_spec_instance)
|
||||
.logger(log.clone())
|
||||
.custom_spec(spec.clone())
|
||||
.store(Arc::new(MemoryStore::open()))
|
||||
.genesis_state(
|
||||
interop_genesis_state::<E>(&keypairs, HARNESS_GENESIS_TIME, &spec)
|
||||
.expect("should generate interop state"),
|
||||
)
|
||||
.expect("should build state using recent genesis")
|
||||
.dummy_eth1_backend()
|
||||
.expect("should build dummy backend")
|
||||
.null_event_handler()
|
||||
.testing_slot_clock(Duration::from_secs(1))
|
||||
.expect("should configure testing slot clock")
|
||||
.empty_reduced_tree_fork_choice()
|
||||
.expect("should add fork choice to builder")
|
||||
.build()
|
||||
.expect("should build");
|
||||
|
||||
Self {
|
||||
spec: chain.spec.clone(),
|
||||
chain,
|
||||
keypairs,
|
||||
spec,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +202,7 @@ where
|
||||
.block_proposer(slot)
|
||||
.expect("should get block proposer from chain"),
|
||||
_ => state
|
||||
.get_beacon_proposer_index(slot, RelativeEpoch::Current, &self.spec)
|
||||
.get_beacon_proposer_index(slot, &self.spec)
|
||||
.expect("should get block proposer from state"),
|
||||
};
|
||||
|
||||
@@ -293,13 +279,13 @@ where
|
||||
let mut attestations = vec![];
|
||||
|
||||
state
|
||||
.get_crosslink_committees_at_slot(state.slot)
|
||||
.get_beacon_committees_at_slot(state.slot)
|
||||
.expect("should get committees")
|
||||
.iter()
|
||||
.for_each(|cc| {
|
||||
let committee_size = cc.committee.len();
|
||||
.for_each(|bc| {
|
||||
let committee_size = bc.committee.len();
|
||||
|
||||
let mut local_attestations: Vec<Attestation<E>> = cc
|
||||
let mut local_attestations: Vec<Attestation<E>> = bc
|
||||
.committee
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
@@ -310,7 +296,7 @@ where
|
||||
let data = self
|
||||
.chain
|
||||
.produce_attestation_data_for_block(
|
||||
cc.shard,
|
||||
bc.index,
|
||||
head_block_root,
|
||||
head_block_slot,
|
||||
state,
|
||||
@@ -322,18 +308,15 @@ where
|
||||
aggregation_bits
|
||||
.set(i, true)
|
||||
.expect("should be able to set aggregation bits");
|
||||
let custody_bits = BitList::with_capacity(committee_size)
|
||||
.expect("should make custody bits");
|
||||
|
||||
let signature = {
|
||||
let message = AttestationDataAndCustodyBit {
|
||||
data: data.clone(),
|
||||
custody_bit: false,
|
||||
}
|
||||
.tree_hash_root();
|
||||
let message = data.tree_hash_root();
|
||||
|
||||
let domain =
|
||||
spec.get_domain(data.target.epoch, Domain::Attestation, fork);
|
||||
let domain = spec.get_domain(
|
||||
data.target.epoch,
|
||||
Domain::BeaconAttester,
|
||||
fork,
|
||||
);
|
||||
|
||||
let mut agg_sig = AggregateSignature::new();
|
||||
agg_sig.add(&Signature::new(
|
||||
@@ -348,7 +331,6 @@ where
|
||||
let attestation = Attestation {
|
||||
aggregation_bits,
|
||||
data,
|
||||
custody_bits,
|
||||
signature,
|
||||
};
|
||||
|
||||
|
||||
@@ -6,14 +6,13 @@ extern crate lazy_static;
|
||||
use beacon_chain::AttestationProcessingOutcome;
|
||||
use beacon_chain::{
|
||||
test_utils::{
|
||||
AttestationStrategy, BeaconChainHarness, BlockStrategy, CommonTypes, PersistedBeaconChain,
|
||||
AttestationStrategy, BeaconChainHarness, BlockStrategy, HarnessType, PersistedBeaconChain,
|
||||
BEACON_CHAIN_DB_KEY,
|
||||
},
|
||||
BlockProcessingOutcome,
|
||||
};
|
||||
use lmd_ghost::ThreadSafeReducedTree;
|
||||
use rand::Rng;
|
||||
use store::{MemoryStore, Store};
|
||||
use store::Store;
|
||||
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use types::{Deposit, EthSpec, Hash256, Keypair, MinimalEthSpec, RelativeEpoch, Slot};
|
||||
|
||||
@@ -25,10 +24,8 @@ lazy_static! {
|
||||
static ref KEYPAIRS: Vec<Keypair> = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
|
||||
}
|
||||
|
||||
type TestForkChoice = ThreadSafeReducedTree<MemoryStore, MinimalEthSpec>;
|
||||
|
||||
fn get_harness(validator_count: usize) -> BeaconChainHarness<TestForkChoice, MinimalEthSpec> {
|
||||
let harness = BeaconChainHarness::new(KEYPAIRS[0..validator_count].to_vec());
|
||||
fn get_harness(validator_count: usize) -> BeaconChainHarness<HarnessType<MinimalEthSpec>> {
|
||||
let harness = BeaconChainHarness::new(MinimalEthSpec, KEYPAIRS[0..validator_count].to_vec());
|
||||
|
||||
harness.advance_slot();
|
||||
|
||||
@@ -322,7 +319,7 @@ fn roundtrip_operation_pool() {
|
||||
harness.chain.persist().unwrap();
|
||||
|
||||
let key = Hash256::from_slice(&BEACON_CHAIN_DB_KEY.as_bytes());
|
||||
let p: PersistedBeaconChain<CommonTypes<TestForkChoice, MinimalEthSpec>> =
|
||||
let p: PersistedBeaconChain<HarnessType<MinimalEthSpec>> =
|
||||
harness.chain.store.get(&key).unwrap().unwrap();
|
||||
|
||||
let restored_op_pool = p
|
||||
|
||||
Reference in New Issue
Block a user