mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-08 17:26:04 +00:00
Merge branch 'master' into process-free-attestation
This commit is contained in:
@@ -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" }
|
||||
|
||||
@@ -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))
|
||||
})?;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user