mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-22 14:24:44 +00:00
Merge remote-tracking branch 'origin/unstable' into tree-states
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use proto_array::{Block as ProtoBlock, ProtoArrayForkChoice};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use types::{
|
||||
AttestationShufflingId, BeaconBlock, BeaconState, BeaconStateError, ChainSpec, Checkpoint,
|
||||
Epoch, EthSpec, Hash256, IndexedAttestation, RelativeEpoch, SignedBeaconBlock, Slot,
|
||||
};
|
||||
|
||||
use crate::ForkChoiceStore;
|
||||
use proto_array::{Block as ProtoBlock, ExecutionStatus, ProtoArrayForkChoice};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::cmp::Ordering;
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Duration;
|
||||
use types::{
|
||||
consts::merge::INTERVALS_PER_SLOT, AttestationShufflingId, BeaconBlock, BeaconState,
|
||||
BeaconStateError, ChainSpec, Checkpoint, Epoch, EthSpec, Hash256, IndexedAttestation,
|
||||
RelativeEpoch, SignedBeaconBlock, Slot,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error<T> {
|
||||
@@ -16,6 +16,7 @@ pub enum Error<T> {
|
||||
InvalidBlock(InvalidBlock),
|
||||
ProtoArrayError(String),
|
||||
InvalidProtoArrayBytes(String),
|
||||
InvalidLegacyProtoArrayBytes(String),
|
||||
MissingProtoArrayBlock(Hash256),
|
||||
UnknownAncestor {
|
||||
ancestor_slot: Slot,
|
||||
@@ -37,6 +38,11 @@ pub enum Error<T> {
|
||||
block_slot: Slot,
|
||||
state_slot: Slot,
|
||||
},
|
||||
InvalidPayloadStatus {
|
||||
block_slot: Slot,
|
||||
block_root: Hash256,
|
||||
payload_verification_status: PayloadVerificationStatus,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T> From<InvalidAttestation> for Error<T> {
|
||||
@@ -100,6 +106,19 @@ impl<T> From<String> for Error<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates if a block has been verified by an execution payload.
|
||||
///
|
||||
/// There is no variant for "invalid", since such a block should never be added to fork choice.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum PayloadVerificationStatus {
|
||||
/// An EL has declared the execution payload to be valid.
|
||||
Verified,
|
||||
/// An EL has not yet made a determination about the execution payload.
|
||||
NotVerified,
|
||||
/// The block is either pre-merge-fork, or prior to the terminal PoW block.
|
||||
Irrelevant,
|
||||
}
|
||||
|
||||
/// Calculate how far `slot` lies from the start of its epoch.
|
||||
///
|
||||
/// ## Specification
|
||||
@@ -149,6 +168,13 @@ where
|
||||
store.set_current_slot(time);
|
||||
|
||||
let current_slot = store.get_current_slot();
|
||||
|
||||
// Reset proposer boost if this is a new slot.
|
||||
if current_slot > previous_slot {
|
||||
store.set_proposer_boost_root(Hash256::zero());
|
||||
}
|
||||
|
||||
// Not a new epoch, return.
|
||||
if !(current_slot > previous_slot && compute_slots_since_epoch_start::<E>(current_slot) == 0) {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -199,6 +225,15 @@ fn dequeue_attestations(
|
||||
std::mem::replace(queued_attestations, remaining)
|
||||
}
|
||||
|
||||
/// Denotes whether an attestation we are processing was received from a block or from gossip.
|
||||
/// Equivalent to the `is_from_block` `bool` in:
|
||||
///
|
||||
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/fork-choice.md#validate_on_attestation
|
||||
pub enum AttestationFromBlock {
|
||||
True,
|
||||
False,
|
||||
}
|
||||
|
||||
/// Provides an implementation of "Ethereum 2.0 Phase 0 -- Beacon Chain Fork Choice":
|
||||
///
|
||||
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#ethereum-20-phase-0----beacon-chain-fork-choice
|
||||
@@ -260,14 +295,24 @@ where
|
||||
AttestationShufflingId::new(anchor_block_root, anchor_state, RelativeEpoch::Next)
|
||||
.map_err(Error::BeaconStateError)?;
|
||||
|
||||
// Default any non-merge execution block hashes to 0x000..000.
|
||||
let execution_status = anchor_block.message_merge().map_or_else(
|
||||
|()| ExecutionStatus::irrelevant(),
|
||||
|message| {
|
||||
// Assume that this payload is valid, since the anchor should be a trusted block and
|
||||
// state.
|
||||
ExecutionStatus::Valid(message.body.execution_payload.block_hash)
|
||||
},
|
||||
);
|
||||
|
||||
let proto_array = ProtoArrayForkChoice::new(
|
||||
finalized_block_slot,
|
||||
finalized_block_state_root,
|
||||
fc_store.justified_checkpoint().epoch,
|
||||
fc_store.finalized_checkpoint().epoch,
|
||||
fc_store.finalized_checkpoint().root,
|
||||
*fc_store.justified_checkpoint(),
|
||||
*fc_store.finalized_checkpoint(),
|
||||
current_epoch_shuffling_id,
|
||||
next_epoch_shuffling_id,
|
||||
execution_status,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
@@ -347,7 +392,11 @@ where
|
||||
/// Is equivalent to:
|
||||
///
|
||||
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#get_head
|
||||
pub fn get_head(&mut self, current_slot: Slot) -> Result<Hash256, Error<T::Error>> {
|
||||
pub fn get_head(
|
||||
&mut self,
|
||||
current_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Hash256, Error<T::Error>> {
|
||||
self.update_time(current_slot)?;
|
||||
|
||||
let store = &mut self.fc_store;
|
||||
@@ -356,11 +405,12 @@ where
|
||||
let justified_balances = store.justified_balances().to_vec();
|
||||
|
||||
self.proto_array
|
||||
.find_head(
|
||||
store.justified_checkpoint().epoch,
|
||||
store.justified_checkpoint().root,
|
||||
store.finalized_checkpoint().epoch,
|
||||
&justified_balances,
|
||||
.find_head::<E>(
|
||||
*store.justified_checkpoint(),
|
||||
*store.finalized_checkpoint(),
|
||||
store.justified_balances(),
|
||||
store.proposer_boost_root(),
|
||||
spec,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
@@ -435,12 +485,15 @@ where
|
||||
///
|
||||
/// The supplied block **must** pass the `state_transition` function as it will not be run
|
||||
/// here.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn on_block(
|
||||
&mut self,
|
||||
current_slot: Slot,
|
||||
block: &BeaconBlock<E>,
|
||||
block_root: Hash256,
|
||||
block_delay: Duration,
|
||||
state: &BeaconState<E>,
|
||||
payload_verification_status: PayloadVerificationStatus,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error<T::Error>> {
|
||||
let current_slot = self.update_time(current_slot)?;
|
||||
@@ -492,6 +545,13 @@ where
|
||||
}));
|
||||
}
|
||||
|
||||
// Add proposer score boost if the block is timely.
|
||||
let is_before_attesting_interval =
|
||||
block_delay < Duration::from_secs(spec.seconds_per_slot / INTERVALS_PER_SLOT);
|
||||
if current_slot == block.slot() && is_before_attesting_interval {
|
||||
self.fc_store.set_proposer_boost_root(block_root);
|
||||
}
|
||||
|
||||
// Update justified checkpoint.
|
||||
if state.current_justified_checkpoint().epoch > self.fc_store.justified_checkpoint().epoch {
|
||||
if state.current_justified_checkpoint().epoch
|
||||
@@ -511,25 +571,9 @@ where
|
||||
if state.finalized_checkpoint().epoch > self.fc_store.finalized_checkpoint().epoch {
|
||||
self.fc_store
|
||||
.set_finalized_checkpoint(state.finalized_checkpoint());
|
||||
let finalized_slot =
|
||||
compute_start_slot_at_epoch::<E>(self.fc_store.finalized_checkpoint().epoch);
|
||||
|
||||
// Note: the `if` statement here is not part of the specification, but I claim that it
|
||||
// is an optimization and equivalent to the specification. See this PR for more
|
||||
// information:
|
||||
//
|
||||
// https://github.com/ethereum/eth2.0-specs/pull/1880
|
||||
if *self.fc_store.justified_checkpoint() != state.current_justified_checkpoint()
|
||||
&& (state.current_justified_checkpoint().epoch
|
||||
> self.fc_store.justified_checkpoint().epoch
|
||||
|| self
|
||||
.get_ancestor(self.fc_store.justified_checkpoint().root, finalized_slot)?
|
||||
!= Some(self.fc_store.finalized_checkpoint().root))
|
||||
{
|
||||
self.fc_store
|
||||
.set_justified_checkpoint(state.current_justified_checkpoint())
|
||||
.map_err(Error::UnableToSetJustifiedCheckpoint)?;
|
||||
}
|
||||
self.fc_store
|
||||
.set_justified_checkpoint(state.current_justified_checkpoint())
|
||||
.map_err(Error::UnableToSetJustifiedCheckpoint)?;
|
||||
}
|
||||
|
||||
let target_slot = block
|
||||
@@ -548,6 +592,33 @@ where
|
||||
.on_verified_block(block, block_root, state)
|
||||
.map_err(Error::AfterBlockFailed)?;
|
||||
|
||||
let execution_status = if let Ok(execution_payload) = block.body().execution_payload() {
|
||||
let block_hash = execution_payload.block_hash;
|
||||
|
||||
if block_hash == Hash256::zero() {
|
||||
// The block is post-merge-fork, but pre-terminal-PoW block. We don't need to verify
|
||||
// the payload.
|
||||
ExecutionStatus::irrelevant()
|
||||
} else {
|
||||
match payload_verification_status {
|
||||
PayloadVerificationStatus::Verified => ExecutionStatus::Valid(block_hash),
|
||||
PayloadVerificationStatus::NotVerified => ExecutionStatus::Unknown(block_hash),
|
||||
// It would be a logic error to declare a block irrelevant if it has an
|
||||
// execution payload with a non-zero block hash.
|
||||
PayloadVerificationStatus::Irrelevant => {
|
||||
return Err(Error::InvalidPayloadStatus {
|
||||
block_slot: block.slot(),
|
||||
block_root,
|
||||
payload_verification_status,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// There is no payload to verify.
|
||||
ExecutionStatus::irrelevant()
|
||||
};
|
||||
|
||||
// This does not apply a vote to the block, it just makes fork choice aware of the block so
|
||||
// it can still be identified as the head even if it doesn't have any votes.
|
||||
self.proto_array.process_block(ProtoBlock {
|
||||
@@ -568,13 +639,43 @@ where
|
||||
)
|
||||
.map_err(Error::BeaconStateError)?,
|
||||
state_root: block.state_root(),
|
||||
justified_epoch: state.current_justified_checkpoint().epoch,
|
||||
finalized_epoch: state.finalized_checkpoint().epoch,
|
||||
justified_checkpoint: state.current_justified_checkpoint(),
|
||||
finalized_checkpoint: state.finalized_checkpoint(),
|
||||
execution_status,
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates the `epoch` against the current time according to the fork choice store.
|
||||
///
|
||||
/// ## Specification
|
||||
///
|
||||
/// Equivalent to:
|
||||
///
|
||||
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/fork-choice.md#validate_target_epoch_against_current_time
|
||||
fn validate_target_epoch_against_current_time(
|
||||
&self,
|
||||
target_epoch: Epoch,
|
||||
) -> Result<(), InvalidAttestation> {
|
||||
let slot_now = self.fc_store.get_current_slot();
|
||||
let epoch_now = slot_now.epoch(E::slots_per_epoch());
|
||||
|
||||
// Attestation must be from the current or previous epoch.
|
||||
if target_epoch > epoch_now {
|
||||
return Err(InvalidAttestation::FutureEpoch {
|
||||
attestation_epoch: target_epoch,
|
||||
current_epoch: epoch_now,
|
||||
});
|
||||
} else if target_epoch + 1 < epoch_now {
|
||||
return Err(InvalidAttestation::PastEpoch {
|
||||
attestation_epoch: target_epoch,
|
||||
current_epoch: epoch_now,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates the `indexed_attestation` for application to fork choice.
|
||||
///
|
||||
/// ## Specification
|
||||
@@ -585,6 +686,7 @@ where
|
||||
fn validate_on_attestation(
|
||||
&self,
|
||||
indexed_attestation: &IndexedAttestation<E>,
|
||||
is_from_block: AttestationFromBlock,
|
||||
) -> Result<(), InvalidAttestation> {
|
||||
// There is no point in processing an attestation with an empty bitfield. Reject
|
||||
// it immediately.
|
||||
@@ -595,21 +697,10 @@ where
|
||||
return Err(InvalidAttestation::EmptyAggregationBitfield);
|
||||
}
|
||||
|
||||
let slot_now = self.fc_store.get_current_slot();
|
||||
let epoch_now = slot_now.epoch(E::slots_per_epoch());
|
||||
let target = indexed_attestation.data.target;
|
||||
|
||||
// Attestation must be from the current or previous epoch.
|
||||
if target.epoch > epoch_now {
|
||||
return Err(InvalidAttestation::FutureEpoch {
|
||||
attestation_epoch: target.epoch,
|
||||
current_epoch: epoch_now,
|
||||
});
|
||||
} else if target.epoch + 1 < epoch_now {
|
||||
return Err(InvalidAttestation::PastEpoch {
|
||||
attestation_epoch: target.epoch,
|
||||
current_epoch: epoch_now,
|
||||
});
|
||||
if matches!(is_from_block, AttestationFromBlock::False) {
|
||||
self.validate_target_epoch_against_current_time(target.epoch)?;
|
||||
}
|
||||
|
||||
if target.epoch != indexed_attestation.data.slot.epoch(E::slots_per_epoch()) {
|
||||
@@ -692,6 +783,7 @@ where
|
||||
&mut self,
|
||||
current_slot: Slot,
|
||||
attestation: &IndexedAttestation<E>,
|
||||
is_from_block: AttestationFromBlock,
|
||||
) -> Result<(), Error<T::Error>> {
|
||||
// Ensure the store is up-to-date.
|
||||
self.update_time(current_slot)?;
|
||||
@@ -713,7 +805,7 @@ where
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.validate_on_attestation(attestation)?;
|
||||
self.validate_on_attestation(attestation, is_from_block)?;
|
||||
|
||||
if attestation.data.slot < self.fc_store.get_current_slot() {
|
||||
for validator_index in attestation.attesting_indices.iter() {
|
||||
@@ -839,6 +931,11 @@ where
|
||||
&self.queued_attestations
|
||||
}
|
||||
|
||||
/// Returns the store's `proposer_boost_root`.
|
||||
pub fn proposer_boost_root(&self) -> Hash256 {
|
||||
self.fc_store.proposer_boost_root()
|
||||
}
|
||||
|
||||
/// Prunes the underlying fork choice DAG.
|
||||
pub fn prune(&mut self) -> Result<(), Error<T::Error>> {
|
||||
let finalized_root = self.fc_store.finalized_checkpoint().root;
|
||||
@@ -880,7 +977,7 @@ where
|
||||
/// This is used when persisting the state of the fork choice to disk.
|
||||
#[derive(Encode, Decode, Clone)]
|
||||
pub struct PersistedForkChoice {
|
||||
proto_array_bytes: Vec<u8>,
|
||||
pub proto_array_bytes: Vec<u8>,
|
||||
queued_attestations: Vec<QueuedAttestation>,
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, Hash256, Slot};
|
||||
pub trait ForkChoiceStore<T: EthSpec>: Sized {
|
||||
type Error;
|
||||
|
||||
/// Returns the last value passed to `Self::update_time`.
|
||||
/// Returns the last value passed to `Self::set_current_slot`.
|
||||
fn get_current_slot(&self) -> Slot;
|
||||
|
||||
/// Set the value to be returned by `Self::get_current_slot`.
|
||||
@@ -50,6 +50,9 @@ pub trait ForkChoiceStore<T: EthSpec>: Sized {
|
||||
/// Returns the `finalized_checkpoint`.
|
||||
fn finalized_checkpoint(&self) -> &Checkpoint;
|
||||
|
||||
/// Returns the `proposer_boost_root`.
|
||||
fn proposer_boost_root(&self) -> Hash256;
|
||||
|
||||
/// Sets `finalized_checkpoint`.
|
||||
fn set_finalized_checkpoint(&mut self, checkpoint: Checkpoint);
|
||||
|
||||
@@ -58,4 +61,7 @@ pub trait ForkChoiceStore<T: EthSpec>: Sized {
|
||||
|
||||
/// Sets the `best_justified_checkpoint`.
|
||||
fn set_best_justified_checkpoint(&mut self, checkpoint: Checkpoint);
|
||||
|
||||
/// Sets the proposer boost root.
|
||||
fn set_proposer_boost_root(&mut self, proposer_boost_root: Hash256);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ mod fork_choice;
|
||||
mod fork_choice_store;
|
||||
|
||||
pub use crate::fork_choice::{
|
||||
Error, ForkChoice, InvalidAttestation, InvalidBlock, PersistedForkChoice, QueuedAttestation,
|
||||
AttestationFromBlock, Error, ForkChoice, InvalidAttestation, InvalidBlock,
|
||||
PayloadVerificationStatus, PersistedForkChoice, QueuedAttestation,
|
||||
};
|
||||
pub use fork_choice_store::ForkChoiceStore;
|
||||
pub use proto_array::Block as ProtoBlock;
|
||||
|
||||
Reference in New Issue
Block a user