mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-19 21:04:41 +00:00
Strip out old code
All of these files have been moved to either: - https://github.com/sigp/lighthouse-beacon - https://github.com/sigp/lighthouse-validator - https://github.com/sigp/lighthouse-common For rationale, see: https://github.com/sigp/lighthouse/issues/197
This commit is contained in:
@@ -1,217 +0,0 @@
|
||||
use state_processing::validate_attestation_without_signature;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use types::{
|
||||
beacon_state::CommitteesError, AggregateSignature, Attestation, AttestationData, BeaconState,
|
||||
Bitfield, ChainSpec, FreeAttestation, Signature,
|
||||
};
|
||||
|
||||
const PHASE_0_CUSTODY_BIT: bool = false;
|
||||
|
||||
/// Provides the functionality to:
|
||||
///
|
||||
/// - Recieve a `FreeAttestation` and aggregate it into an `Attestation` (or create a new if it
|
||||
/// doesn't exist).
|
||||
/// - Store all aggregated or created `Attestation`s.
|
||||
/// - Produce a list of attestations that would be valid for inclusion in some `BeaconState` (and
|
||||
/// therefore valid for inclusion in a `BeaconBlock`.
|
||||
///
|
||||
/// Note: `Attestations` are stored in memory and never deleted. This is not scalable and must be
|
||||
/// rectified in a future revision.
|
||||
#[derive(Default)]
|
||||
pub struct AttestationAggregator {
|
||||
store: HashMap<Vec<u8>, Attestation>,
|
||||
}
|
||||
|
||||
pub struct Outcome {
|
||||
pub valid: bool,
|
||||
pub message: Message,
|
||||
}
|
||||
|
||||
pub enum Message {
|
||||
/// The free attestation was added to an existing attestation.
|
||||
Aggregated,
|
||||
/// The free attestation has already been aggregated to an existing attestation.
|
||||
AggregationNotRequired,
|
||||
/// The free attestation was transformed into a new attestation.
|
||||
NewAttestationCreated,
|
||||
/// The supplied `validator_index` is not in the committee for the given `shard` and `slot`.
|
||||
BadValidatorIndex,
|
||||
/// The given `signature` did not match the `pubkey` in the given
|
||||
/// `state.validator_registry`.
|
||||
BadSignature,
|
||||
/// The given `slot` does not match the validators committee assignment.
|
||||
BadSlot,
|
||||
/// The given `shard` does not match the validators committee assignment.
|
||||
BadShard,
|
||||
}
|
||||
|
||||
macro_rules! some_or_invalid {
|
||||
($expression: expr, $error: expr) => {
|
||||
match $expression {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Ok(Outcome {
|
||||
valid: false,
|
||||
message: $error,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl AttestationAggregator {
|
||||
/// Instantiates a new AttestationAggregator with an empty database.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
store: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Accepts some `FreeAttestation`, validates it and either aggregates it upon some existing
|
||||
/// `Attestation` or produces a new `Attestation`.
|
||||
///
|
||||
/// The "validation" provided is not complete, instead the following points are checked:
|
||||
/// - The given `validator_index` is in the committee for the given `shard` for the given
|
||||
/// `slot`.
|
||||
/// - The signature is verified against that of the validator at `validator_index`.
|
||||
pub fn process_free_attestation(
|
||||
&mut self,
|
||||
state: &BeaconState,
|
||||
free_attestation: &FreeAttestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Outcome, CommitteesError> {
|
||||
let (slot, shard, committee_index) = some_or_invalid!(
|
||||
state.attestation_slot_and_shard_for_validator(
|
||||
free_attestation.validator_index as usize,
|
||||
spec,
|
||||
)?,
|
||||
Message::BadValidatorIndex
|
||||
);
|
||||
|
||||
if free_attestation.data.slot != slot {
|
||||
return Ok(Outcome {
|
||||
valid: false,
|
||||
message: Message::BadSlot,
|
||||
});
|
||||
}
|
||||
if free_attestation.data.shard != shard {
|
||||
return Ok(Outcome {
|
||||
valid: false,
|
||||
message: Message::BadShard,
|
||||
});
|
||||
}
|
||||
|
||||
let signable_message = free_attestation.data.signable_message(PHASE_0_CUSTODY_BIT);
|
||||
|
||||
let validator_record = some_or_invalid!(
|
||||
state
|
||||
.validator_registry
|
||||
.get(free_attestation.validator_index as usize),
|
||||
Message::BadValidatorIndex
|
||||
);
|
||||
|
||||
if !free_attestation
|
||||
.signature
|
||||
.verify(&signable_message, &validator_record.pubkey)
|
||||
{
|
||||
return Ok(Outcome {
|
||||
valid: false,
|
||||
message: Message::BadSignature,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(existing_attestation) = self.store.get(&signable_message) {
|
||||
if let Some(updated_attestation) = aggregate_attestation(
|
||||
existing_attestation,
|
||||
&free_attestation.signature,
|
||||
committee_index as usize,
|
||||
) {
|
||||
self.store.insert(signable_message, updated_attestation);
|
||||
Ok(Outcome {
|
||||
valid: true,
|
||||
message: Message::Aggregated,
|
||||
})
|
||||
} else {
|
||||
Ok(Outcome {
|
||||
valid: true,
|
||||
message: Message::AggregationNotRequired,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let mut aggregate_signature = AggregateSignature::new();
|
||||
aggregate_signature.add(&free_attestation.signature);
|
||||
let mut aggregation_bitfield = Bitfield::new();
|
||||
aggregation_bitfield.set(committee_index as usize, true);
|
||||
let new_attestation = Attestation {
|
||||
data: free_attestation.data.clone(),
|
||||
aggregation_bitfield,
|
||||
custody_bitfield: Bitfield::new(),
|
||||
aggregate_signature,
|
||||
};
|
||||
self.store.insert(signable_message, new_attestation);
|
||||
Ok(Outcome {
|
||||
valid: true,
|
||||
message: Message::NewAttestationCreated,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all known attestations which are:
|
||||
///
|
||||
/// - Valid for the given state
|
||||
/// - Not already in `state.latest_attestations`.
|
||||
pub fn get_attestations_for_state(
|
||||
&self,
|
||||
state: &BeaconState,
|
||||
spec: &ChainSpec,
|
||||
) -> Vec<Attestation> {
|
||||
let mut known_attestation_data: HashSet<AttestationData> = HashSet::new();
|
||||
|
||||
state.latest_attestations.iter().for_each(|attestation| {
|
||||
known_attestation_data.insert(attestation.data.clone());
|
||||
});
|
||||
|
||||
self.store
|
||||
.values()
|
||||
.filter_map(|attestation| {
|
||||
if validate_attestation_without_signature(&state, attestation, spec).is_ok()
|
||||
&& !known_attestation_data.contains(&attestation.data)
|
||||
{
|
||||
Some(attestation.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a new `Attestation` where:
|
||||
///
|
||||
/// - `signature` is added to `Attestation.aggregate_signature`
|
||||
/// - Attestation.aggregation_bitfield[committee_index]` is set to true.
|
||||
fn aggregate_attestation(
|
||||
existing_attestation: &Attestation,
|
||||
signature: &Signature,
|
||||
committee_index: usize,
|
||||
) -> Option<Attestation> {
|
||||
let already_signed = existing_attestation
|
||||
.aggregation_bitfield
|
||||
.get(committee_index)
|
||||
.unwrap_or(false);
|
||||
|
||||
if already_signed {
|
||||
None
|
||||
} else {
|
||||
let mut aggregation_bitfield = existing_attestation.aggregation_bitfield.clone();
|
||||
aggregation_bitfield.set(committee_index, true);
|
||||
let mut aggregate_signature = existing_attestation.aggregate_signature.clone();
|
||||
aggregate_signature.add(&signature);
|
||||
|
||||
Some(Attestation {
|
||||
aggregation_bitfield,
|
||||
aggregate_signature,
|
||||
..existing_attestation.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use types::Hash256;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AttestationTargets {
|
||||
map: HashMap<u64, Hash256>,
|
||||
}
|
||||
|
||||
impl AttestationTargets {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, validator_index: u64) -> Option<&Hash256> {
|
||||
self.map.get(&validator_index)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, validator_index: u64, block_hash: Hash256) -> Option<Hash256> {
|
||||
self.map.insert(validator_index, block_hash)
|
||||
}
|
||||
}
|
||||
@@ -1,564 +0,0 @@
|
||||
use crate::attestation_aggregator::{AttestationAggregator, Outcome as AggregationOutcome};
|
||||
use crate::attestation_targets::AttestationTargets;
|
||||
use crate::block_graph::BlockGraph;
|
||||
use crate::checkpoint::CheckPoint;
|
||||
use db::{
|
||||
stores::{BeaconBlockStore, BeaconStateStore},
|
||||
ClientDB, DBError,
|
||||
};
|
||||
use log::{debug, trace};
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use slot_clock::SlotClock;
|
||||
use ssz::ssz_encode;
|
||||
use state_processing::{
|
||||
BlockProcessable, BlockProcessingError, SlotProcessable, SlotProcessingError,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
beacon_state::CommitteesError,
|
||||
readers::{BeaconBlockReader, BeaconStateReader},
|
||||
AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Crosslink, Deposit,
|
||||
Epoch, Eth1Data, FreeAttestation, Hash256, PublicKey, Signature, Slot,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
InsufficientValidators,
|
||||
BadRecentBlockRoots,
|
||||
CommitteesError(CommitteesError),
|
||||
DBInconsistent(String),
|
||||
DBError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ValidBlock {
|
||||
/// The block was sucessfully processed.
|
||||
Processed,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum InvalidBlock {
|
||||
/// The block slot is greater than the present slot.
|
||||
FutureSlot,
|
||||
/// The block state_root does not match the generated state.
|
||||
StateRootMismatch,
|
||||
/// The blocks parent_root is unknown.
|
||||
ParentUnknown,
|
||||
/// There was an error whilst advancing the parent state to the present slot. This condition
|
||||
/// should not occur, it likely represents an internal error.
|
||||
SlotProcessingError(SlotProcessingError),
|
||||
/// The block could not be applied to the state, it is invalid.
|
||||
PerBlockProcessingError(BlockProcessingError),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BlockProcessingOutcome {
|
||||
/// The block was sucessfully validated.
|
||||
ValidBlock(ValidBlock),
|
||||
/// The block was not sucessfully validated.
|
||||
InvalidBlock(InvalidBlock),
|
||||
}
|
||||
|
||||
pub struct BeaconChain<T: ClientDB + Sized, U: SlotClock> {
|
||||
pub block_store: Arc<BeaconBlockStore<T>>,
|
||||
pub state_store: Arc<BeaconStateStore<T>>,
|
||||
pub slot_clock: U,
|
||||
pub block_graph: BlockGraph,
|
||||
pub attestation_aggregator: RwLock<AttestationAggregator>,
|
||||
canonical_head: RwLock<CheckPoint>,
|
||||
finalized_head: RwLock<CheckPoint>,
|
||||
pub state: RwLock<BeaconState>,
|
||||
pub latest_attestation_targets: RwLock<AttestationTargets>,
|
||||
pub spec: ChainSpec,
|
||||
}
|
||||
|
||||
impl<T, U> BeaconChain<T, U>
|
||||
where
|
||||
T: ClientDB,
|
||||
U: SlotClock,
|
||||
{
|
||||
/// Instantiate a new Beacon Chain, from genesis.
|
||||
pub fn genesis(
|
||||
state_store: Arc<BeaconStateStore<T>>,
|
||||
block_store: Arc<BeaconBlockStore<T>>,
|
||||
slot_clock: U,
|
||||
genesis_time: u64,
|
||||
latest_eth1_data: Eth1Data,
|
||||
initial_validator_deposits: Vec<Deposit>,
|
||||
spec: ChainSpec,
|
||||
) -> Result<Self, Error> {
|
||||
if initial_validator_deposits.is_empty() {
|
||||
return Err(Error::InsufficientValidators);
|
||||
}
|
||||
|
||||
let genesis_state = BeaconState::genesis(
|
||||
genesis_time,
|
||||
initial_validator_deposits,
|
||||
latest_eth1_data,
|
||||
&spec,
|
||||
);
|
||||
let state_root = genesis_state.canonical_root();
|
||||
state_store.put(&state_root, &ssz_encode(&genesis_state)[..])?;
|
||||
|
||||
let genesis_block = BeaconBlock::genesis(state_root, &spec);
|
||||
let block_root = genesis_block.canonical_root();
|
||||
block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?;
|
||||
|
||||
let block_graph = BlockGraph::new();
|
||||
block_graph.add_leaf(&Hash256::zero(), block_root);
|
||||
|
||||
let finalized_head = RwLock::new(CheckPoint::new(
|
||||
genesis_block.clone(),
|
||||
block_root,
|
||||
genesis_state.clone(),
|
||||
state_root,
|
||||
));
|
||||
let canonical_head = RwLock::new(CheckPoint::new(
|
||||
genesis_block.clone(),
|
||||
block_root,
|
||||
genesis_state.clone(),
|
||||
state_root,
|
||||
));
|
||||
let attestation_aggregator = RwLock::new(AttestationAggregator::new());
|
||||
|
||||
let latest_attestation_targets = RwLock::new(AttestationTargets::new());
|
||||
|
||||
Ok(Self {
|
||||
block_store,
|
||||
state_store,
|
||||
slot_clock,
|
||||
block_graph,
|
||||
attestation_aggregator,
|
||||
state: RwLock::new(genesis_state.clone()),
|
||||
finalized_head,
|
||||
canonical_head,
|
||||
latest_attestation_targets,
|
||||
spec,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update the canonical head to some new values.
|
||||
pub fn update_canonical_head(
|
||||
&self,
|
||||
new_beacon_block: BeaconBlock,
|
||||
new_beacon_block_root: Hash256,
|
||||
new_beacon_state: BeaconState,
|
||||
new_beacon_state_root: Hash256,
|
||||
) {
|
||||
let mut head = self.canonical_head.write();
|
||||
head.update(
|
||||
new_beacon_block,
|
||||
new_beacon_block_root,
|
||||
new_beacon_state,
|
||||
new_beacon_state_root,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a read-lock guarded `CheckPoint` struct for reading the head (as chosen by the
|
||||
/// fork-choice rule).
|
||||
///
|
||||
/// It is important to note that the `beacon_state` returned may not match the present slot. It
|
||||
/// is the state as it was when the head block was recieved, which could be some slots prior to
|
||||
/// now.
|
||||
pub fn head(&self) -> RwLockReadGuard<CheckPoint> {
|
||||
self.canonical_head.read()
|
||||
}
|
||||
|
||||
/// Update the justified head to some new values.
|
||||
pub fn update_finalized_head(
|
||||
&self,
|
||||
new_beacon_block: BeaconBlock,
|
||||
new_beacon_block_root: Hash256,
|
||||
new_beacon_state: BeaconState,
|
||||
new_beacon_state_root: Hash256,
|
||||
) {
|
||||
let mut finalized_head = self.finalized_head.write();
|
||||
finalized_head.update(
|
||||
new_beacon_block,
|
||||
new_beacon_block_root,
|
||||
new_beacon_state,
|
||||
new_beacon_state_root,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a read-lock guarded `CheckPoint` struct for reading the justified head (as chosen,
|
||||
/// indirectly, by the fork-choice rule).
|
||||
pub fn finalized_head(&self) -> RwLockReadGuard<CheckPoint> {
|
||||
self.finalized_head.read()
|
||||
}
|
||||
|
||||
/// Advance the `self.state` `BeaconState` to the supplied slot.
|
||||
///
|
||||
/// This will perform per_slot and per_epoch processing as required.
|
||||
///
|
||||
/// The `previous_block_root` will be set to the root of the current head block (as determined
|
||||
/// by the fork-choice rule).
|
||||
///
|
||||
/// It is important to note that this is _not_ the state corresponding to the canonical head
|
||||
/// block, instead it is that state which may or may not have had additional per slot/epoch
|
||||
/// processing applied to it.
|
||||
pub fn advance_state(&self, slot: Slot) -> Result<(), SlotProcessingError> {
|
||||
let state_slot = self.state.read().slot;
|
||||
let head_block_root = self.head().beacon_block_root;
|
||||
for _ in state_slot.as_u64()..slot.as_u64() {
|
||||
self.state
|
||||
.write()
|
||||
.per_slot_processing(head_block_root, &self.spec)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the the validator index (if any) for the given public key.
|
||||
///
|
||||
/// Information is retrieved from the present `beacon_state.validator_registry`.
|
||||
pub fn validator_index(&self, pubkey: &PublicKey) -> Option<usize> {
|
||||
for (i, validator) in self
|
||||
.head()
|
||||
.beacon_state
|
||||
.validator_registry
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if validator.pubkey == *pubkey {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Reads the slot clock, returns `None` if the slot is unavailable.
|
||||
///
|
||||
/// The slot might be unavailable due to an error with the system clock, or if the present time
|
||||
/// is before genesis (i.e., a negative slot).
|
||||
///
|
||||
/// This is distinct to `present_slot`, which simply reads the latest state. If a
|
||||
/// call to `read_slot_clock` results in a higher slot than a call to `present_slot`,
|
||||
/// `self.state` should undergo per slot processing.
|
||||
pub fn read_slot_clock(&self) -> Option<Slot> {
|
||||
match self.slot_clock.present_slot() {
|
||||
Ok(Some(some_slot)) => Some(some_slot),
|
||||
Ok(None) => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns slot of the present state.
|
||||
///
|
||||
/// This is distinct to `read_slot_clock`, which reads from the actual system clock. If
|
||||
/// `self.state` has not been transitioned it is possible for the system clock to be on a
|
||||
/// different slot to what is returned from this call.
|
||||
pub fn present_slot(&self) -> Slot {
|
||||
self.state.read().slot
|
||||
}
|
||||
|
||||
/// Returns the block proposer for a given slot.
|
||||
///
|
||||
/// Information is read from the present `beacon_state` shuffling, so only information from the
|
||||
/// present and prior epoch is available.
|
||||
pub fn block_proposer(&self, slot: Slot) -> Result<usize, CommitteesError> {
|
||||
let index = self
|
||||
.state
|
||||
.read()
|
||||
.get_beacon_proposer_index(slot, &self.spec)?;
|
||||
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
/// Returns the justified slot for the present state.
|
||||
pub fn justified_epoch(&self) -> Epoch {
|
||||
self.state.read().justified_epoch
|
||||
}
|
||||
|
||||
/// Returns the attestation slot and shard 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_attestion_slot_and_shard(
|
||||
&self,
|
||||
validator_index: usize,
|
||||
) -> Result<Option<(Slot, u64)>, CommitteesError> {
|
||||
if let Some((slot, shard, _committee)) = self
|
||||
.state
|
||||
.read()
|
||||
.attestation_slot_and_shard_for_validator(validator_index, &self.spec)?
|
||||
{
|
||||
Ok(Some((slot, shard)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Produce an `AttestationData` that is valid for the present `slot` and given `shard`.
|
||||
pub fn produce_attestation_data(&self, shard: u64) -> Result<AttestationData, Error> {
|
||||
let justified_epoch = self.justified_epoch();
|
||||
let justified_block_root = *self
|
||||
.state
|
||||
.read()
|
||||
.get_block_root(
|
||||
justified_epoch.start_slot(self.spec.epoch_length),
|
||||
&self.spec,
|
||||
)
|
||||
.ok_or_else(|| Error::BadRecentBlockRoots)?;
|
||||
|
||||
let epoch_boundary_root = *self
|
||||
.state
|
||||
.read()
|
||||
.get_block_root(
|
||||
self.state.read().current_epoch_start_slot(&self.spec),
|
||||
&self.spec,
|
||||
)
|
||||
.ok_or_else(|| Error::BadRecentBlockRoots)?;
|
||||
|
||||
Ok(AttestationData {
|
||||
slot: self.state.read().slot,
|
||||
shard,
|
||||
beacon_block_root: self.head().beacon_block_root,
|
||||
epoch_boundary_root,
|
||||
shard_block_root: Hash256::zero(),
|
||||
latest_crosslink: Crosslink {
|
||||
epoch: self.state.read().slot.epoch(self.spec.epoch_length),
|
||||
shard_block_root: Hash256::zero(),
|
||||
},
|
||||
justified_epoch,
|
||||
justified_block_root,
|
||||
})
|
||||
}
|
||||
|
||||
/// Validate a `FreeAttestation` and either:
|
||||
///
|
||||
/// - Create a new `Attestation`.
|
||||
/// - Aggregate it to an existing `Attestation`.
|
||||
pub fn process_free_attestation(
|
||||
&self,
|
||||
free_attestation: FreeAttestation,
|
||||
) -> Result<AggregationOutcome, Error> {
|
||||
self.attestation_aggregator
|
||||
.write()
|
||||
.process_free_attestation(&self.state.read(), &free_attestation, &self.spec)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Set the latest attestation target for some validator.
|
||||
pub fn insert_latest_attestation_target(&self, validator_index: u64, block_root: Hash256) {
|
||||
let mut targets = self.latest_attestation_targets.write();
|
||||
targets.insert(validator_index, block_root);
|
||||
}
|
||||
|
||||
/// Get the latest attestation target for some validator.
|
||||
pub fn get_latest_attestation_target(&self, validator_index: u64) -> Option<Hash256> {
|
||||
let targets = self.latest_attestation_targets.read();
|
||||
|
||||
match targets.get(validator_index) {
|
||||
Some(hash) => Some(*hash),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Dumps the entire canonical chain, from the head to genesis to a vector for analysis.
|
||||
///
|
||||
/// This could be a very expensive operation and should only be done in testing/analysis
|
||||
/// activities.
|
||||
pub fn chain_dump(&self) -> Result<Vec<CheckPoint>, Error> {
|
||||
let mut dump = vec![];
|
||||
|
||||
let mut last_slot = CheckPoint {
|
||||
beacon_block: self.head().beacon_block.clone(),
|
||||
beacon_block_root: self.head().beacon_block_root,
|
||||
beacon_state: self.head().beacon_state.clone(),
|
||||
beacon_state_root: self.head().beacon_state_root,
|
||||
};
|
||||
|
||||
dump.push(last_slot.clone());
|
||||
|
||||
loop {
|
||||
let beacon_block_root = last_slot.beacon_block.parent_root;
|
||||
|
||||
if beacon_block_root == self.spec.zero_hash {
|
||||
break; // Genesis has been reached.
|
||||
}
|
||||
|
||||
let beacon_block = self
|
||||
.block_store
|
||||
.get_deserialized(&beacon_block_root)?
|
||||
.ok_or_else(|| {
|
||||
Error::DBInconsistent(format!("Missing block {}", beacon_block_root))
|
||||
})?;
|
||||
let beacon_state_root = beacon_block.state_root;
|
||||
let beacon_state = self
|
||||
.state_store
|
||||
.get_deserialized(&beacon_state_root)?
|
||||
.ok_or_else(|| {
|
||||
Error::DBInconsistent(format!("Missing state {}", beacon_state_root))
|
||||
})?;
|
||||
|
||||
let slot = CheckPoint {
|
||||
beacon_block,
|
||||
beacon_block_root,
|
||||
beacon_state,
|
||||
beacon_state_root,
|
||||
};
|
||||
|
||||
dump.push(slot.clone());
|
||||
last_slot = slot;
|
||||
}
|
||||
|
||||
Ok(dump)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
debug!("Processing block with slot {}...", block.slot());
|
||||
|
||||
let block_root = block.canonical_root();
|
||||
|
||||
let present_slot = self.present_slot();
|
||||
|
||||
if block.slot > present_slot {
|
||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
||||
InvalidBlock::FutureSlot,
|
||||
));
|
||||
}
|
||||
|
||||
// Load the blocks parent block from the database, returning invalid if that block is not
|
||||
// found.
|
||||
let parent_block_root = block.parent_root;
|
||||
let parent_block = match self.block_store.get_reader(&parent_block_root)? {
|
||||
Some(parent_root) => parent_root,
|
||||
None => {
|
||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
||||
InvalidBlock::ParentUnknown,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Load the parent blocks state from the database, returning an error if it is not found.
|
||||
// It is an error because if know the parent block we should also know the parent state.
|
||||
let parent_state_root = parent_block.state_root();
|
||||
let parent_state = self
|
||||
.state_store
|
||||
.get_reader(&parent_state_root)?
|
||||
.ok_or_else(|| Error::DBInconsistent(format!("Missing state {}", parent_state_root)))?
|
||||
.into_beacon_state()
|
||||
.ok_or_else(|| {
|
||||
Error::DBInconsistent(format!("State SSZ invalid {}", parent_state_root))
|
||||
})?;
|
||||
|
||||
// TODO: check the block proposer signature BEFORE doing a state transition. This will
|
||||
// significantly lower exposure surface to DoS attacks.
|
||||
|
||||
// Transition the parent state to the present slot.
|
||||
let mut state = parent_state;
|
||||
for _ in state.slot.as_u64()..present_slot.as_u64() {
|
||||
if let Err(e) = state.per_slot_processing(parent_block_root, &self.spec) {
|
||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
||||
InvalidBlock::SlotProcessingError(e),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the recieved block to its parent state (which has been transitioned into this
|
||||
// slot).
|
||||
if let Err(e) = state.per_block_processing(&block, &self.spec) {
|
||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
||||
InvalidBlock::PerBlockProcessingError(e),
|
||||
));
|
||||
}
|
||||
|
||||
let state_root = state.canonical_root();
|
||||
|
||||
if block.state_root != state_root {
|
||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
||||
InvalidBlock::StateRootMismatch,
|
||||
));
|
||||
}
|
||||
|
||||
// Store the block and state.
|
||||
self.block_store.put(&block_root, &ssz_encode(&block)[..])?;
|
||||
self.state_store.put(&state_root, &ssz_encode(&state)[..])?;
|
||||
|
||||
// Update the block DAG.
|
||||
self.block_graph.add_leaf(&parent_block_root, block_root);
|
||||
|
||||
// If the parent block was the parent_block, automatically update the canonical head.
|
||||
//
|
||||
// TODO: this is a first-in-best-dressed scenario that is not ideal; fork_choice should be
|
||||
// run instead.
|
||||
if self.head().beacon_block_root == parent_block_root {
|
||||
self.update_canonical_head(block.clone(), block_root, state.clone(), state_root);
|
||||
// Update the local state variable.
|
||||
*self.state.write() = state.clone();
|
||||
}
|
||||
|
||||
Ok(BlockProcessingOutcome::ValidBlock(ValidBlock::Processed))
|
||||
}
|
||||
|
||||
/// Produce a new block at the present slot.
|
||||
///
|
||||
/// The produced block will not be inheriently valid, it must be signed by a block producer.
|
||||
/// Block signing is out of the scope of this function and should be done by a separate program.
|
||||
pub fn produce_block(&self, randao_reveal: Signature) -> Option<(BeaconBlock, BeaconState)> {
|
||||
debug!("Producing block at slot {}...", self.state.read().slot);
|
||||
|
||||
let mut state = self.state.read().clone();
|
||||
|
||||
trace!("Finding attestations for new block...");
|
||||
|
||||
let attestations = self
|
||||
.attestation_aggregator
|
||||
.read()
|
||||
.get_attestations_for_state(&state, &self.spec);
|
||||
|
||||
trace!(
|
||||
"Inserting {} attestation(s) into new block.",
|
||||
attestations.len()
|
||||
);
|
||||
|
||||
let parent_root = *state.get_block_root(state.slot.saturating_sub(1_u64), &self.spec)?;
|
||||
|
||||
let mut block = BeaconBlock {
|
||||
slot: state.slot,
|
||||
parent_root,
|
||||
state_root: Hash256::zero(), // Updated after the state is calculated.
|
||||
randao_reveal,
|
||||
eth1_data: Eth1Data {
|
||||
// TODO: replace with real data
|
||||
deposit_root: Hash256::zero(),
|
||||
block_hash: Hash256::zero(),
|
||||
},
|
||||
signature: self.spec.empty_signature.clone(), // To be completed by a validator.
|
||||
body: BeaconBlockBody {
|
||||
proposer_slashings: vec![],
|
||||
attester_slashings: vec![],
|
||||
attestations,
|
||||
deposits: vec![],
|
||||
exits: vec![],
|
||||
},
|
||||
};
|
||||
|
||||
state
|
||||
.per_block_processing_without_verifying_block_signature(&block, &self.spec)
|
||||
.ok()?;
|
||||
|
||||
let state_root = state.canonical_root();
|
||||
|
||||
block.state_root = state_root;
|
||||
|
||||
trace!("Block produced.");
|
||||
|
||||
Some((block, state))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DBError> for Error {
|
||||
fn from(e: DBError) -> Error {
|
||||
Error::DBError(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CommitteesError> for Error {
|
||||
fn from(e: CommitteesError) -> Error {
|
||||
Error::CommitteesError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use std::collections::HashSet;
|
||||
use types::Hash256;
|
||||
|
||||
/// Maintains a view of the block DAG, also known as the "blockchain" (except, it tracks multiple
|
||||
/// chains eminating from a single root instead of just the head of some canonical chain).
|
||||
///
|
||||
/// The BlockGraph does not store the blocks, instead it tracks the block hashes of blocks at the
|
||||
/// tip of the DAG. It is out of the scope of the object to retrieve blocks.
|
||||
///
|
||||
/// Presently, the DAG root (genesis block) is not tracked.
|
||||
///
|
||||
/// The BlogGraph is thread-safe due to internal RwLocks.
|
||||
#[derive(Default)]
|
||||
pub struct BlockGraph {
|
||||
pub leaves: RwLock<HashSet<Hash256>>,
|
||||
}
|
||||
|
||||
impl BlockGraph {
|
||||
/// Create a new block graph without any leaves.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
leaves: RwLock::new(HashSet::new()),
|
||||
}
|
||||
}
|
||||
/// Add a new leaf to the block hash graph. Returns `true` if the leaf was built upon another
|
||||
/// leaf.
|
||||
pub fn add_leaf(&self, parent: &Hash256, leaf: Hash256) -> bool {
|
||||
let mut leaves = self.leaves.write();
|
||||
|
||||
if leaves.contains(parent) {
|
||||
leaves.remove(parent);
|
||||
leaves.insert(leaf);
|
||||
true
|
||||
} else {
|
||||
leaves.insert(leaf);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a read-guarded HashSet of all leaf blocks.
|
||||
pub fn leaves(&self) -> RwLockReadGuard<HashSet<Hash256>> {
|
||||
self.leaves.read()
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
use serde_derive::Serialize;
|
||||
use types::{BeaconBlock, BeaconState, Hash256};
|
||||
|
||||
/// Represents some block and it's associated state. Generally, this will be used for tracking the
|
||||
/// head, justified head and finalized head.
|
||||
#[derive(PartialEq, Clone, Serialize)]
|
||||
pub struct CheckPoint {
|
||||
pub beacon_block: BeaconBlock,
|
||||
pub beacon_block_root: Hash256,
|
||||
pub beacon_state: BeaconState,
|
||||
pub beacon_state_root: Hash256,
|
||||
}
|
||||
|
||||
impl CheckPoint {
|
||||
/// Create a new checkpoint.
|
||||
pub fn new(
|
||||
beacon_block: BeaconBlock,
|
||||
beacon_block_root: Hash256,
|
||||
beacon_state: BeaconState,
|
||||
beacon_state_root: Hash256,
|
||||
) -> Self {
|
||||
Self {
|
||||
beacon_block,
|
||||
beacon_block_root,
|
||||
beacon_state,
|
||||
beacon_state_root,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update all fields of the checkpoint.
|
||||
pub fn update(
|
||||
&mut self,
|
||||
beacon_block: BeaconBlock,
|
||||
beacon_block_root: Hash256,
|
||||
beacon_state: BeaconState,
|
||||
beacon_state_root: Hash256,
|
||||
) {
|
||||
self.beacon_block = beacon_block;
|
||||
self.beacon_block_root = beacon_block_root;
|
||||
self.beacon_state = beacon_state;
|
||||
self.beacon_state_root = beacon_state_root;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
mod attestation_aggregator;
|
||||
mod attestation_targets;
|
||||
mod beacon_chain;
|
||||
mod block_graph;
|
||||
mod checkpoint;
|
||||
mod lmd_ghost;
|
||||
|
||||
pub use self::beacon_chain::{BeaconChain, Error};
|
||||
pub use self::checkpoint::CheckPoint;
|
||||
@@ -1,198 +0,0 @@
|
||||
use crate::BeaconChain;
|
||||
use db::{
|
||||
stores::{BeaconBlockAtSlotError, BeaconBlockStore},
|
||||
ClientDB, DBError,
|
||||
};
|
||||
use slot_clock::{SlotClock, TestingSlotClockError};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
readers::{BeaconBlockReader, BeaconStateReader},
|
||||
validator_registry::get_active_validator_indices,
|
||||
Hash256, Slot,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
DBError(String),
|
||||
MissingBeaconState(Hash256),
|
||||
InvalidBeaconState(Hash256),
|
||||
MissingBeaconBlock(Hash256),
|
||||
InvalidBeaconBlock(Hash256),
|
||||
}
|
||||
|
||||
impl<T, U> BeaconChain<T, U>
|
||||
where
|
||||
T: ClientDB,
|
||||
U: SlotClock,
|
||||
Error: From<<U as SlotClock>::Error>,
|
||||
{
|
||||
/// Run the fork-choice rule on the current chain, updating the canonical head, if required.
|
||||
pub fn fork_choice(&self) -> Result<(), Error> {
|
||||
let present_head = &self.finalized_head().beacon_block_root;
|
||||
|
||||
let new_head = self.slow_lmd_ghost(&self.finalized_head().beacon_block_root)?;
|
||||
|
||||
if new_head != *present_head {
|
||||
let block = self
|
||||
.block_store
|
||||
.get_deserialized(&new_head)?
|
||||
.ok_or_else(|| Error::MissingBeaconBlock(new_head))?;
|
||||
let block_root = block.canonical_root();
|
||||
|
||||
let state = self
|
||||
.state_store
|
||||
.get_deserialized(&block.state_root)?
|
||||
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
|
||||
let state_root = state.canonical_root();
|
||||
|
||||
self.update_canonical_head(block, block_root, state, state_root);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A very inefficient implementation of LMD ghost.
|
||||
pub fn slow_lmd_ghost(&self, start_hash: &Hash256) -> Result<Hash256, Error> {
|
||||
let start = self
|
||||
.block_store
|
||||
.get_reader(&start_hash)?
|
||||
.ok_or_else(|| Error::MissingBeaconBlock(*start_hash))?;
|
||||
|
||||
let start_state_root = start.state_root();
|
||||
|
||||
let state = self
|
||||
.state_store
|
||||
.get_reader(&start_state_root)?
|
||||
.ok_or_else(|| Error::MissingBeaconState(start_state_root))?
|
||||
.into_beacon_state()
|
||||
.ok_or_else(|| Error::InvalidBeaconState(start_state_root))?;
|
||||
|
||||
let active_validator_indices = get_active_validator_indices(
|
||||
&state.validator_registry,
|
||||
start.slot().epoch(self.spec.epoch_length),
|
||||
);
|
||||
|
||||
let mut attestation_targets = Vec::with_capacity(active_validator_indices.len());
|
||||
for i in active_validator_indices {
|
||||
if let Some(target) = self.get_latest_attestation_target(i as u64) {
|
||||
attestation_targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
let mut head_hash = Hash256::zero();
|
||||
let mut head_vote_count = 0;
|
||||
|
||||
loop {
|
||||
let child_hashes_and_slots = get_child_hashes_and_slots(
|
||||
&self.block_store,
|
||||
&head_hash,
|
||||
&self.block_graph.leaves(),
|
||||
)?;
|
||||
|
||||
if child_hashes_and_slots.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
for (child_hash, child_slot) in child_hashes_and_slots {
|
||||
let vote_count = get_vote_count(
|
||||
&self.block_store,
|
||||
&attestation_targets[..],
|
||||
&child_hash,
|
||||
child_slot,
|
||||
)?;
|
||||
|
||||
if vote_count > head_vote_count {
|
||||
head_hash = child_hash;
|
||||
head_vote_count = vote_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(head_hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the total number of votes for some given block root.
|
||||
///
|
||||
/// The vote count is incrememented each time an attestation target votes for a block root.
|
||||
fn get_vote_count<T: ClientDB>(
|
||||
block_store: &Arc<BeaconBlockStore<T>>,
|
||||
attestation_targets: &[Hash256],
|
||||
block_root: &Hash256,
|
||||
slot: Slot,
|
||||
) -> Result<u64, Error> {
|
||||
let mut count = 0;
|
||||
for target in attestation_targets {
|
||||
let (root_at_slot, _) = block_store
|
||||
.block_at_slot(&block_root, slot)?
|
||||
.ok_or_else(|| Error::MissingBeaconBlock(*block_root))?;
|
||||
if root_at_slot == *target {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Starting from some `leaf_hashes`, recurse back down each branch until the `root_hash`, adding
|
||||
/// each `block_root` and `slot` to a HashSet.
|
||||
fn get_child_hashes_and_slots<T: ClientDB>(
|
||||
block_store: &Arc<BeaconBlockStore<T>>,
|
||||
root_hash: &Hash256,
|
||||
leaf_hashes: &HashSet<Hash256>,
|
||||
) -> Result<HashSet<(Hash256, Slot)>, Error> {
|
||||
let mut hash_set = HashSet::new();
|
||||
|
||||
for leaf_hash in leaf_hashes {
|
||||
let mut current_hash = *leaf_hash;
|
||||
|
||||
loop {
|
||||
if let Some(block_reader) = block_store.get_reader(¤t_hash)? {
|
||||
let parent_root = block_reader.parent_root();
|
||||
|
||||
let new_hash = hash_set.insert((current_hash, block_reader.slot()));
|
||||
|
||||
// If the hash just added was already in the set, break the loop.
|
||||
//
|
||||
// In such a case, the present branch has merged with a branch that is already in
|
||||
// the set.
|
||||
if !new_hash {
|
||||
break;
|
||||
}
|
||||
|
||||
// The branch is exhausted if the parent of this block is the root_hash.
|
||||
if parent_root == *root_hash {
|
||||
break;
|
||||
}
|
||||
|
||||
current_hash = parent_root;
|
||||
} else {
|
||||
return Err(Error::MissingBeaconBlock(current_hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hash_set)
|
||||
}
|
||||
|
||||
impl From<DBError> for Error {
|
||||
fn from(e: DBError) -> Error {
|
||||
Error::DBError(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BeaconBlockAtSlotError> for Error {
|
||||
fn from(e: BeaconBlockAtSlotError) -> Error {
|
||||
match e {
|
||||
BeaconBlockAtSlotError::UnknownBeaconBlock(h) => Error::MissingBeaconBlock(h),
|
||||
BeaconBlockAtSlotError::InvalidBeaconBlock(h) => Error::InvalidBeaconBlock(h),
|
||||
BeaconBlockAtSlotError::DBError(msg) => Error::DBError(msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TestingSlotClockError> for Error {
|
||||
fn from(_: TestingSlotClockError) -> Error {
|
||||
unreachable!(); // Testing clock never throws an error.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user