Merge branch 'master' into process-free-attestation

This commit is contained in:
Grant Wuerker
2019-08-06 12:28:30 +02:00
259 changed files with 7589 additions and 6458 deletions

View File

@@ -5,20 +5,15 @@ authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com
edition = "2018"
[dependencies]
bls = { path = "../../eth2/utils/bls" }
boolean-bitfield = { path = "../../eth2/utils/boolean-bitfield" }
store = { path = "../store" }
failure = "0.1"
failure_derive = "0.1"
hashing = { path = "../../eth2/utils/hashing" }
parking_lot = "0.7"
prometheus = "^0.6"
log = "0.4"
operation_pool = { path = "../../eth2/operation_pool" }
env_logger = "0.6"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
slog = { version = "^2.2.3" , features = ["max_level_trace"] }
sloggers = { version = "^0.3" }
slot_clock = { path = "../../eth2/utils/slot_clock" }
eth2_ssz = { path = "../../eth2/utils/ssz" }
eth2_ssz_derive = { path = "../../eth2/utils/ssz_derive" }

View File

@@ -8,6 +8,7 @@ use log::trace;
use operation_pool::DepositInsertStatus;
use operation_pool::{OperationPool, PersistedOperationPool};
use parking_lot::{RwLock, RwLockReadGuard};
use slog::{error, info, warn, Logger};
use slot_clock::SlotClock;
use state_processing::per_block_processing::errors::{
AttesterSlashingValidationError, DepositValidationError,
@@ -71,19 +72,21 @@ pub struct BeaconChain<T: BeaconChainTypes> {
/// Stores all operations (e.g., `Attestation`, `Deposit`, etc) that are candidates for
/// inclusion in a block.
pub op_pool: OperationPool<T::EthSpec>,
/// Stores a "snapshot" of the chain at the time the head-of-the-chain block was recieved.
/// Stores a "snapshot" of the chain at the time the head-of-the-chain block was received.
canonical_head: RwLock<CheckPoint<T::EthSpec>>,
/// The same state from `self.canonical_head`, but updated at the start of each slot with a
/// skip slot if no block is recieved. This is effectively a cache that avoids repeating calls
/// skip slot if no block is received. This is effectively a cache that avoids repeating calls
/// to `per_slot_processing`.
state: RwLock<BeaconState<T::EthSpec>>,
/// The root of the genesis block.
genesis_block_root: Hash256,
pub genesis_block_root: Hash256,
/// A state-machine that is updated with information from the network and chooses a canonical
/// head block.
pub fork_choice: ForkChoice<T>,
/// Stores metrics about this `BeaconChain`.
pub metrics: Metrics,
/// Logging to CLI, etc.
log: Logger,
}
impl<T: BeaconChainTypes> BeaconChain<T> {
@@ -92,28 +95,37 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
store: Arc<T::Store>,
slot_clock: T::SlotClock,
mut genesis_state: BeaconState<T::EthSpec>,
genesis_block: BeaconBlock,
mut genesis_block: BeaconBlock<T::EthSpec>,
spec: ChainSpec,
log: Logger,
) -> Result<Self, Error> {
genesis_state.build_all_caches(&spec)?;
let state_root = genesis_state.canonical_root();
store.put(&state_root, &genesis_state)?;
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.block_header().canonical_root();
store.put(&spec.zero_hash, &genesis_block)?;
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(),
state_root,
genesis_state_root,
));
info!(log, "BeaconChain init";
"genesis_validator_count" => genesis_state.validators.len(),
"genesis_state_root" => format!("{}", genesis_state_root),
"genesis_block_root" => format!("{}", genesis_block_root),
);
Ok(Self {
spec,
slot_clock,
@@ -124,6 +136,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
fork_choice: ForkChoice::new(store.clone(), &genesis_block, genesis_block_root),
metrics: Metrics::new()?,
store,
log,
})
}
@@ -131,6 +144,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn from_store(
store: Arc<T::Store>,
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) {
@@ -145,7 +159,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
spec.seconds_per_slot,
);
let last_finalized_root = p.canonical_head.beacon_state.finalized_root;
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(&p.state, &spec);
@@ -160,6 +174,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
genesis_block_root: p.genesis_block_root,
metrics: Metrics::new()?,
store,
log,
}))
}
@@ -181,8 +196,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Returns the beacon block body for each beacon block root in `roots`.
///
/// Fails if any root in `roots` does not have a corresponding block.
pub fn get_block_bodies(&self, roots: &[Hash256]) -> Result<Vec<BeaconBlockBody>, Error> {
let bodies: Result<Vec<BeaconBlockBody>, _> = roots
pub fn get_block_bodies(
&self,
roots: &[Hash256],
) -> Result<Vec<BeaconBlockBody<T::EthSpec>>, Error> {
let bodies: Result<Vec<_>, _> = roots
.iter()
.map(|root| match self.get_block(root)? {
Some(block) => Ok(block.body),
@@ -253,7 +271,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// ## Errors
///
/// May return a database error.
pub fn get_block(&self, block_root: &Hash256) -> Result<Option<BeaconBlock>, Error> {
pub fn get_block(
&self,
block_root: &Hash256,
) -> Result<Option<BeaconBlock<T::EthSpec>>, Error> {
Ok(self.store.get(block_root)?)
}
@@ -315,15 +336,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Returns the validator index (if any) for the given public key.
///
/// Information is retrieved from the present `beacon_state.validator_registry`.
/// Information is retrieved from the present `beacon_state.validators`.
pub fn validator_index(&self, pubkey: &PublicKey) -> Option<usize> {
for (i, validator) in self
.head()
.beacon_state
.validator_registry
.iter()
.enumerate()
{
for (i, validator) in self.head().beacon_state.validators.iter().enumerate() {
if validator.pubkey == *pubkey {
return Some(i);
}
@@ -392,12 +407,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
///
/// Information is read from the current state, so only information from the present and prior
/// epoch is available.
pub fn validator_attestion_slot_and_shard(
pub fn validator_attestation_slot_and_shard(
&self,
validator_index: usize,
) -> Result<Option<(Slot, u64)>, BeaconStateError> {
trace!(
"BeaconChain::validator_attestion_slot_and_shard: validator_index: {}",
"BeaconChain::validator_attestation_slot_and_shard: validator_index: {}",
validator_index
);
if let Some(attestation_duty) = self
@@ -463,9 +478,22 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
} else {
*state.get_block_root(current_epoch_start_slot)?
};
let target = Checkpoint {
epoch: state.current_epoch(),
root: target_root,
};
let previous_crosslink_root =
Hash256::from_slice(&state.get_current_crosslink(shard)?.tree_hash_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.
self.metrics.attestation_production_successes.inc();
@@ -473,13 +501,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(AttestationData {
beacon_block_root: head_block_root,
source_epoch: state.current_justified_epoch,
source_root: state.current_justified_root,
target_epoch: state.current_epoch(),
target_root,
shard,
previous_crosslink_root,
crosslink_data_root: Hash256::zero(),
source: state.current_justified_checkpoint.clone(),
target,
crosslink,
})
}
@@ -489,7 +513,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// if possible.
pub fn process_attestation(
&self,
attestation: Attestation,
attestation: Attestation<T::EthSpec>,
) -> Result<(), Error> {
self.metrics.attestation_processing_requests.inc();
let timer = self.metrics.attestation_processing_times.start_timer();
@@ -545,9 +569,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Accept some deposit and queue it for inclusion in an appropriate block.
pub fn process_deposit(
&self,
index: u64,
deposit: Deposit,
) -> Result<DepositInsertStatus, DepositValidationError> {
self.op_pool.insert_deposit(deposit)
self.op_pool.insert_deposit(index, deposit)
}
/// Accept some exit and queue it for inclusion in an appropriate block.
@@ -574,7 +599,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Accept some attester slashing and queue it for inclusion in an appropriate block.
pub fn process_attester_slashing(
&self,
attester_slashing: AttesterSlashing,
attester_slashing: AttesterSlashing<T::EthSpec>,
) -> Result<(), AttesterSlashingValidationError> {
self.op_pool
.insert_attester_slashing(attester_slashing, &*self.state.read(), &self.spec)
@@ -583,14 +608,18 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Accept some block and attempt to add it to block DAG.
///
/// Will accept blocks from prior slots, however it will reject any block from a future slot.
pub fn process_block(&self, block: BeaconBlock) -> Result<BlockProcessingOutcome, Error> {
pub fn process_block(
&self,
block: BeaconBlock<T::EthSpec>,
) -> Result<BlockProcessingOutcome, Error> {
self.metrics.block_processing_requests.inc();
let timer = self.metrics.block_processing_times.start_timer();
let finalized_slot = self
.state
.read()
.finalized_epoch
.finalized_checkpoint
.epoch
.start_slot(T::EthSpec::slots_per_epoch());
if block.slot <= finalized_slot {
@@ -618,18 +647,17 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
});
}
if self.store.exists::<BeaconBlock>(&block_root)? {
if self.store.exists::<BeaconBlock<T::EthSpec>>(&block_root)? {
return Ok(BlockProcessingOutcome::BlockIsAlreadyKnown);
}
// Load the blocks parent block from the database, returning invalid if that block is not
// found.
let parent_block_root = block.previous_block_root;
let parent_block: BeaconBlock = match self.store.get(&parent_block_root)? {
Some(previous_block_root) => previous_block_root,
let parent_block: BeaconBlock<T::EthSpec> = match self.store.get(&block.parent_root)? {
Some(block) => block,
None => {
return Ok(BlockProcessingOutcome::ParentUnknown {
parent: parent_block_root,
parent: block.parent_root,
});
}
};
@@ -671,13 +699,27 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.store.put(&state_root, &state)?;
// Register the new block with the fork choice service.
self.fork_choice.process_block(&state, &block, block_root)?;
if let Err(e) = self.fork_choice.process_block(&state, &block, block_root) {
error!(
self.log,
"fork choice failed to process_block";
"error" => format!("{:?}", e),
"block_root" => format!("{}", block_root),
"block_slot" => format!("{}", block.slot)
)
}
// Execute the fork choice algorithm, enthroning a new head if discovered.
//
// Note: in the future we may choose to run fork-choice less often, potentially based upon
// some heuristic around number of attestations seen for the block.
self.fork_choice()?;
if let Err(e) = self.fork_choice() {
error!(
self.log,
"fork choice failed to find head";
"error" => format!("{:?}", e)
)
};
self.metrics.block_processing_successes.inc();
self.metrics
@@ -695,7 +737,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn produce_block(
&self,
randao_reveal: Signature,
) -> Result<(BeaconBlock, BeaconState<T::EthSpec>), BlockProductionError> {
) -> Result<(BeaconBlock<T::EthSpec>, BeaconState<T::EthSpec>), BlockProductionError> {
let state = self.state.read().clone();
let slot = self
.read_slot_clock()
@@ -717,7 +759,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
mut state: BeaconState<T::EthSpec>,
produce_at_slot: Slot,
randao_reveal: Signature,
) -> Result<(BeaconBlock, BeaconState<T::EthSpec>), BlockProductionError> {
) -> Result<(BeaconBlock<T::EthSpec>, BeaconState<T::EthSpec>), BlockProductionError> {
self.metrics.block_production_requests.inc();
let timer = self.metrics.block_production_times.start_timer();
@@ -728,7 +770,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
let previous_block_root = if state.slot > 0 {
let parent_root = if state.slot > 0 {
*state
.get_block_root(state.slot - 1)
.map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)?
@@ -744,24 +786,24 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let mut block = BeaconBlock {
slot: state.slot,
previous_block_root,
parent_root,
state_root: Hash256::zero(), // Updated after the state is calculated.
signature: Signature::empty_signature(), // To be completed by a validator.
body: BeaconBlockBody {
randao_reveal,
// TODO: replace with real data.
eth1_data: Eth1Data {
deposit_count: 0,
deposit_count: state.eth1_data.deposit_count,
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
},
graffiti,
proposer_slashings,
attester_slashings,
attestations: self.op_pool.get_attestations(&state, &self.spec),
deposits: self.op_pool.get_deposits(&state, &self.spec),
voluntary_exits: self.op_pool.get_voluntary_exits(&state, &self.spec),
transfers: self.op_pool.get_transfers(&state, &self.spec),
proposer_slashings: proposer_slashings.into(),
attester_slashings: attester_slashings.into(),
attestations: self.op_pool.get_attestations(&state, &self.spec).into(),
deposits: self.op_pool.get_deposits(&state).into(),
voluntary_exits: self.op_pool.get_voluntary_exits(&state, &self.spec).into(),
transfers: self.op_pool.get_transfers(&state, &self.spec).into(),
},
};
@@ -794,7 +836,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
if beacon_block_root != self.head().beacon_block_root {
self.metrics.fork_choice_changed_head.inc();
let beacon_block: BeaconBlock = self
let beacon_block: BeaconBlock<T::EthSpec> = self
.store
.get(&beacon_block_root)?
.ok_or_else(|| Error::MissingBeaconBlock(beacon_block_root))?;
@@ -805,14 +847,32 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.get(&beacon_state_root)?
.ok_or_else(|| Error::MissingBeaconState(beacon_state_root))?;
let previous_slot = self.head().beacon_block.slot;
let new_slot = beacon_block.slot;
// If we switched to a new chain (instead of building atop the present chain).
if self.head().beacon_block_root != beacon_block.previous_block_root {
if self.head().beacon_block_root != beacon_block.parent_root {
self.metrics.fork_choice_reorg_count.inc();
warn!(
self.log,
"Beacon chain re-org";
"previous_slot" => previous_slot,
"new_slot" => new_slot
);
} else {
info!(
self.log,
"new head block";
"justified_root" => format!("{}", beacon_state.current_justified_checkpoint.root),
"finalized_root" => format!("{}", beacon_state.finalized_checkpoint.root),
"root" => format!("{}", beacon_block_root),
"slot" => new_slot,
);
};
let old_finalized_epoch = self.head().beacon_state.finalized_epoch;
let new_finalized_epoch = beacon_state.finalized_epoch;
let finalized_root = beacon_state.finalized_root;
let old_finalized_epoch = self.head().beacon_state.finalized_checkpoint.epoch;
let new_finalized_epoch = beacon_state.finalized_checkpoint.epoch;
let finalized_root = beacon_state.finalized_checkpoint.root;
// Never revert back past a finalized epoch.
if new_finalized_epoch < old_finalized_epoch {
@@ -822,7 +882,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
})
} else {
self.update_canonical_head(CheckPoint {
beacon_block: beacon_block,
beacon_block,
beacon_block_root,
beacon_state,
beacon_state_root,
@@ -880,7 +940,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
) -> Result<(), Error> {
let finalized_block = self
.store
.get::<BeaconBlock>(&finalized_block_root)?
.get::<BeaconBlock<T::EthSpec>>(&finalized_block_root)?
.ok_or_else(|| Error::MissingBeaconBlock(finalized_block_root))?;
let new_finalized_epoch = finalized_block.slot.epoch(T::EthSpec::slots_per_epoch());
@@ -900,7 +960,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Returns `true` if the given block root has not been processed.
pub fn is_new_block_root(&self, beacon_block_root: &Hash256) -> Result<bool, Error> {
Ok(!self.store.exists::<BeaconBlock>(beacon_block_root)?)
Ok(!self
.store
.exists::<BeaconBlock<T::EthSpec>>(beacon_block_root)?)
}
/// Dumps the entire canonical chain, from the head to genesis to a vector for analysis.
@@ -920,13 +982,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
dump.push(last_slot.clone());
loop {
let beacon_block_root = last_slot.beacon_block.previous_block_root;
let beacon_block_root = last_slot.beacon_block.parent_root;
if beacon_block_root == self.spec.zero_hash {
if beacon_block_root == Hash256::zero() {
break; // Genesis has been reached.
}
let beacon_block: BeaconBlock =
let beacon_block: BeaconBlock<T::EthSpec> =
self.store.get(&beacon_block_root)?.ok_or_else(|| {
Error::DBInconsistent(format!("Missing block {}", beacon_block_root))
})?;

View File

@@ -6,7 +6,7 @@ use types::{BeaconBlock, BeaconState, EthSpec, Hash256};
/// head, justified head and finalized head.
#[derive(Clone, Serialize, PartialEq, Debug, Encode, Decode)]
pub struct CheckPoint<E: EthSpec> {
pub beacon_block: BeaconBlock,
pub beacon_block: BeaconBlock<E>,
pub beacon_block_root: Hash256,
pub beacon_state: BeaconState<E>,
pub beacon_state_root: Hash256,
@@ -15,7 +15,7 @@ pub struct CheckPoint<E: EthSpec> {
impl<E: EthSpec> CheckPoint<E> {
/// Create a new checkpoint.
pub fn new(
beacon_block: BeaconBlock,
beacon_block: BeaconBlock<E>,
beacon_block_root: Hash256,
beacon_state: BeaconState<E>,
beacon_state_root: Hash256,
@@ -31,7 +31,7 @@ impl<E: EthSpec> CheckPoint<E> {
/// Update all fields of the checkpoint.
pub fn update(
&mut self,
beacon_block: BeaconBlock,
beacon_block: BeaconBlock<E>,
beacon_block_root: Hash256,
beacon_state: BeaconState<E>,
beacon_state_root: Hash256,

View File

@@ -1,6 +1,6 @@
use crate::{BeaconChain, BeaconChainTypes};
use lmd_ghost::LmdGhost;
use state_processing::common::get_attesting_indices_unsorted;
use state_processing::common::get_attesting_indices;
use std::sync::Arc;
use store::{Error as StoreError, Store};
use types::{Attestation, BeaconBlock, BeaconState, BeaconStateError, Epoch, EthSpec, Hash256, Slot};
@@ -19,6 +19,7 @@ pub enum Error {
pub struct ForkChoice<T: BeaconChainTypes> {
backend: T::LmdGhost,
store: Arc<T::Store>,
/// Used for resolving the `0x00..00` alias back to genesis.
///
/// Does not necessarily need to be the _actual_ genesis, it suffices to be the finalized root
@@ -33,10 +34,11 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
/// block.
pub fn new(
store: Arc<T::Store>,
genesis_block: &BeaconBlock,
genesis_block: &BeaconBlock<T::EthSpec>,
genesis_block_root: Hash256,
) -> Self {
Self {
store: store.clone(),
backend: T::LmdGhost::new(store, genesis_block, genesis_block_root),
genesis_block_root,
}
@@ -54,18 +56,21 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
let state = chain.current_state();
let (block_root, block_slot) =
if state.current_epoch() + 1 > state.current_justified_epoch {
if state.current_epoch() + 1 > state.current_justified_checkpoint.epoch {
(
state.current_justified_root,
start_slot(state.current_justified_epoch),
state.current_justified_checkpoint.root,
start_slot(state.current_justified_checkpoint.epoch),
)
} else {
(state.finalized_root, start_slot(state.finalized_epoch))
(
state.finalized_checkpoint.root,
start_slot(state.finalized_checkpoint.epoch),
)
};
let block = chain
.store
.get::<BeaconBlock>(&block_root)?
.get::<BeaconBlock<T::EthSpec>>(&block_root)?
.ok_or_else(|| Error::MissingBlock(block_root))?;
// Resolve the `0x00.. 00` alias back to genesis
@@ -86,7 +91,7 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
// A function that returns the weight for some validator index.
let weight = |validator_index: usize| -> Option<u64> {
start_state
.validator_registry
.validators
.get(validator_index)
.map(|v| v.effective_balance)
};
@@ -103,7 +108,7 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
pub fn process_block(
&self,
state: &BeaconState<T::EthSpec>,
block: &BeaconBlock,
block: &BeaconBlock<T::EthSpec>,
block_root: Hash256,
) -> Result<()> {
// Note: we never count the block as a latest message, only attestations.
@@ -127,16 +132,9 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
pub fn process_attestation(
&self,
state: &BeaconState<T::EthSpec>,
attestation: &Attestation,
attestation: &Attestation<T::EthSpec>,
) -> Result<()> {
// Note: `get_attesting_indices_unsorted` requires that the beacon state caches be built.
let validator_indices = get_attesting_indices_unsorted(
state,
&attestation.data,
&attestation.aggregation_bitfield,
)?;
let block_hash = attestation.data.target_root;
let block_hash = attestation.data.beacon_block_root;
// Ignore any attestations to the zero hash.
//
@@ -149,13 +147,20 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
// 2. Ignore all attestations to the zero hash.
//
// (1) becomes weird once we hit finality and fork choice drops the genesis block. (2) is
// fine becuase votes to the genesis block are not useful; all validators implicitly attest
// fine because votes to the genesis block are not useful; all validators implicitly attest
// to genesis just by being present in the chain.
if block_hash != Hash256::zero() {
let block_slot = attestation
.data
.target_epoch
.start_slot(T::EthSpec::slots_per_epoch());
//
// Additionally, don't add any block hash to fork choice unless we have imported the block.
if block_hash != Hash256::zero()
&& self
.store
.exists::<BeaconBlock<T::EthSpec>>(&block_hash)
.unwrap_or(false)
{
let validator_indices =
get_attesting_indices(state, &attestation.data, &attestation.aggregation_bits)?;
let block_slot = state.get_attestation_data_slot(&attestation.data)?;
for validator_index in validator_indices {
self.backend
@@ -197,7 +202,7 @@ impl<T: BeaconChainTypes> ForkChoice<T> {
/// `finalized_block_root` must be the root of `finalized_block`.
pub fn process_finalization(
&self,
finalized_block: &BeaconBlock,
finalized_block: &BeaconBlock<T::EthSpec>,
finalized_block_root: Hash256,
) -> Result<()> {
self.backend

View File

@@ -11,7 +11,7 @@ pub const BEACON_CHAIN_DB_KEY: &str = "PERSISTEDBEACONCHAINPERSISTEDBEA";
#[derive(Encode, Decode)]
pub struct PersistedBeaconChain<T: BeaconChainTypes> {
pub canonical_head: CheckPoint<T::EthSpec>,
pub op_pool: PersistedOperationPool,
pub op_pool: PersistedOperationPool<T::EthSpec>,
pub genesis_block_root: Hash256,
pub state: BeaconState<T::EthSpec>,
}

View File

@@ -1,5 +1,6 @@
use crate::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome};
use lmd_ghost::LmdGhost;
use sloggers::{null::NullLoggerBuilder, Build};
use slot_clock::SlotClock;
use slot_clock::TestingSlotClock;
use state_processing::per_slot_processing;
@@ -10,7 +11,7 @@ use store::Store;
use tree_hash::{SignedRoot, TreeHash};
use types::{
test_utils::TestingBeaconStateBuilder, AggregateSignature, Attestation,
AttestationDataAndCustodyBit, BeaconBlock, BeaconState, Bitfield, ChainSpec, Domain, EthSpec,
AttestationDataAndCustodyBit, BeaconBlock, BeaconState, BitList, ChainSpec, Domain, EthSpec,
Hash256, Keypair, RelativeEpoch, SecretKey, Signature, Slot,
};
@@ -64,6 +65,8 @@ where
/// 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>,
@@ -92,6 +95,9 @@ where
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");
// Slot clock
let slot_clock = TestingSlotClock::new(
spec.genesis_slot,
@@ -105,6 +111,7 @@ where
genesis_state,
genesis_block,
spec.clone(),
log,
)
.expect("Terminate if beacon chain generation fails");
@@ -209,7 +216,7 @@ where
mut state: BeaconState<E>,
slot: Slot,
block_strategy: BlockStrategy,
) -> (BeaconBlock, BeaconState<E>) {
) -> (BeaconBlock<E>, BeaconState<E>) {
if slot < state.slot {
panic!("produce slot cannot be prior to the state slot");
}
@@ -295,12 +302,9 @@ where
)
.expect("should produce attestation data");
let mut aggregation_bitfield = Bitfield::new();
aggregation_bitfield.set(i, true);
aggregation_bitfield.set(committee_size, false);
let mut custody_bitfield = Bitfield::new();
custody_bitfield.set(committee_size, false);
let mut aggregation_bits = BitList::with_capacity(committee_size).unwrap();
aggregation_bits.set(i, true).unwrap();
let custody_bits = BitList::with_capacity(committee_size).unwrap();
let signature = {
let message = AttestationDataAndCustodyBit {
@@ -310,7 +314,7 @@ where
.tree_hash_root();
let domain =
spec.get_domain(data.target_epoch, Domain::Attestation, fork);
spec.get_domain(data.target.epoch, Domain::Attestation, fork);
let mut agg_sig = AggregateSignature::new();
agg_sig.add(&Signature::new(
@@ -323,9 +327,9 @@ where
};
let attestation = Attestation {
aggregation_bitfield,
aggregation_bits,
data,
custody_bitfield,
custody_bits,
signature,
};
@@ -337,6 +341,50 @@ where
});
}
/// Creates two forks:
///
/// - The "honest" fork: created by the `honest_validators` who have built `honest_fork_blocks`
/// on the head
/// - The "faulty" fork: created by the `faulty_validators` who skipped a slot and
/// then built `faulty_fork_blocks`.
///
/// Returns `(honest_head, faulty_head)`, the roots of the blocks at the top of each chain.
pub fn generate_two_forks_by_skipping_a_block(
&self,
honest_validators: &[usize],
faulty_validators: &[usize],
honest_fork_blocks: usize,
faulty_fork_blocks: usize,
) -> (Hash256, Hash256) {
let initial_head_slot = self.chain.head().beacon_block.slot;
// Move to the next slot so we may produce some more blocks on the head.
self.advance_slot();
// Extend the chain with blocks where only honest validators agree.
let honest_head = self.extend_chain(
honest_fork_blocks,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::SomeValidators(honest_validators.to_vec()),
);
// Go back to the last block where all agreed, and build blocks upon it where only faulty nodes
// agree.
let faulty_head = self.extend_chain(
faulty_fork_blocks,
BlockStrategy::ForkCanonicalChainAt {
previous_slot: initial_head_slot,
// `initial_head_slot + 2` means one slot is skipped.
first_slot: initial_head_slot + 2,
},
AttestationStrategy::SomeValidators(faulty_validators.to_vec()),
);
assert!(honest_head != faulty_head, "forks should be distinct");
(honest_head, faulty_head)
}
/// Returns the secret key for the given validator index.
fn get_sk(&self, validator_index: usize) -> &SecretKey {
&self.keypairs[validator_index].sk

View File

@@ -24,7 +24,7 @@ fn get_harness(validator_count: usize) -> BeaconChainHarness<TestForkChoice, Min
}
#[test]
fn fork() {
fn chooses_fork() {
let harness = get_harness(VALIDATOR_COUNT);
let two_thirds = (VALIDATOR_COUNT / 3) * 2;
@@ -44,25 +44,11 @@ fn fork() {
AttestationStrategy::AllValidators,
);
// Move to the next slot so we may produce some more blocks on the head.
harness.advance_slot();
// Extend the chain with blocks where only honest validators agree.
let honest_head = harness.extend_chain(
let (honest_head, faulty_head) = harness.generate_two_forks_by_skipping_a_block(
&honest_validators,
&faulty_validators,
honest_fork_blocks,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::SomeValidators(honest_validators.clone()),
);
// Go back to the last block where all agreed, and build blocks upon it where only faulty nodes
// agree.
let faulty_head = harness.extend_chain(
faulty_fork_blocks,
BlockStrategy::ForkCanonicalChainAt {
previous_slot: Slot::from(initial_blocks),
first_slot: Slot::from(initial_blocks + 2),
},
AttestationStrategy::SomeValidators(faulty_validators.clone()),
);
assert!(honest_head != faulty_head, "forks should be distinct");
@@ -106,12 +92,12 @@ fn finalizes_with_full_participation() {
"head should be at the expected epoch"
);
assert_eq!(
state.current_justified_epoch,
state.current_justified_checkpoint.epoch,
state.current_epoch() - 1,
"the head should be justified one behind the current epoch"
);
assert_eq!(
state.finalized_epoch,
state.finalized_checkpoint.epoch,
state.current_epoch() - 2,
"the head should be finalized two behind the current epoch"
);
@@ -149,12 +135,12 @@ fn finalizes_with_two_thirds_participation() {
// included in blocks during that epoch.
assert_eq!(
state.current_justified_epoch,
state.current_justified_checkpoint.epoch,
state.current_epoch() - 2,
"the head should be justified two behind the current epoch"
);
assert_eq!(
state.finalized_epoch,
state.finalized_checkpoint.epoch,
state.current_epoch() - 4,
"the head should be finalized three behind the current epoch"
);
@@ -188,11 +174,11 @@ fn does_not_finalize_with_less_than_two_thirds_participation() {
"head should be at the expected epoch"
);
assert_eq!(
state.current_justified_epoch, 0,
state.current_justified_checkpoint.epoch, 0,
"no epoch should have been justified"
);
assert_eq!(
state.finalized_epoch, 0,
state.finalized_checkpoint.epoch, 0,
"no epoch should have been finalized"
);
}
@@ -221,11 +207,11 @@ fn does_not_finalize_without_attestation() {
"head should be at the expected epoch"
);
assert_eq!(
state.current_justified_epoch, 0,
state.current_justified_checkpoint.epoch, 0,
"no epoch should have been justified"
);
assert_eq!(
state.finalized_epoch, 0,
state.finalized_checkpoint.epoch, 0,
"no epoch should have been finalized"
);
}
@@ -246,10 +232,10 @@ fn roundtrip_operation_pool() {
// Add some deposits
let rng = &mut XorShiftRng::from_seed([66; 16]);
for _ in 0..rng.gen_range(1, VALIDATOR_COUNT) {
for i in 0..rng.gen_range(1, VALIDATOR_COUNT) {
harness
.chain
.process_deposit(Deposit::random_for_test(rng))
.process_deposit(i as u64, Deposit::random_for_test(rng))
.unwrap();
}