mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-07 00:42:42 +00:00
Realized unrealized experimentation (#3322)
## Issue Addressed Add a flag that optionally enables unrealized vote tracking. Would like to test out on testnets and benchmark differences in methods of vote tracking. This PR includes a DB schema upgrade to enable to new vote tracking style. Co-authored-by: realbigsean <sean@sigmaprime.io> Co-authored-by: Paul Hauner <paul@paulhauner.com> Co-authored-by: sean <seananderson33@gmail.com> Co-authored-by: Mac L <mjladson@pm.me>
This commit is contained in:
@@ -8,6 +8,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
types = { path = "../types" }
|
||||
state_processing = { path = "../state_processing" }
|
||||
proto_array = { path = "../proto_array" }
|
||||
eth2_ssz = "0.4.1"
|
||||
eth2_ssz_derive = "0.3.0"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{ForkChoiceStore, InvalidationOperation};
|
||||
use proto_array::{Block as ProtoBlock, ExecutionStatus, ProtoArrayForkChoice};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use state_processing::per_epoch_processing;
|
||||
use std::cmp::Ordering;
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Duration;
|
||||
@@ -51,6 +52,9 @@ pub enum Error<T> {
|
||||
MissingFinalizedBlock {
|
||||
finalized_checkpoint: Checkpoint,
|
||||
},
|
||||
UnrealizedVoteProcessing(state_processing::EpochProcessingError),
|
||||
ParticipationCacheBuild(BeaconStateError),
|
||||
ValidatorStatuses(BeaconStateError),
|
||||
}
|
||||
|
||||
impl<T> From<InvalidAttestation> for Error<T> {
|
||||
@@ -59,6 +63,12 @@ impl<T> From<InvalidAttestation> for Error<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<state_processing::EpochProcessingError> for Error<T> {
|
||||
fn from(e: state_processing::EpochProcessingError) -> Self {
|
||||
Error::UnrealizedVoteProcessing(e)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InvalidBlock {
|
||||
UnknownParent(Hash256),
|
||||
@@ -114,6 +124,66 @@ impl<T> From<String> for Error<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates whether the unrealized justification of a block should be calculated and tracked.
|
||||
/// If a block has been finalized, this can be set to false. This is useful when syncing finalized
|
||||
/// portions of the chain. Otherwise this should always be set to true.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum CountUnrealized {
|
||||
True,
|
||||
False,
|
||||
}
|
||||
|
||||
impl CountUnrealized {
|
||||
pub fn is_true(&self) -> bool {
|
||||
matches!(self, CountUnrealized::True)
|
||||
}
|
||||
|
||||
pub fn and(&self, other: CountUnrealized) -> CountUnrealized {
|
||||
if self.is_true() && other.is_true() {
|
||||
CountUnrealized::True
|
||||
} else {
|
||||
CountUnrealized::False
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for CountUnrealized {
|
||||
fn from(count_unrealized: bool) -> Self {
|
||||
if count_unrealized {
|
||||
CountUnrealized::True
|
||||
} else {
|
||||
CountUnrealized::False
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum UpdateJustifiedCheckpointSlots {
|
||||
OnTick {
|
||||
current_slot: Slot,
|
||||
},
|
||||
OnBlock {
|
||||
state_slot: Slot,
|
||||
current_slot: Slot,
|
||||
},
|
||||
}
|
||||
|
||||
impl UpdateJustifiedCheckpointSlots {
|
||||
fn current_slot(&self) -> Slot {
|
||||
match self {
|
||||
UpdateJustifiedCheckpointSlots::OnTick { current_slot } => *current_slot,
|
||||
UpdateJustifiedCheckpointSlots::OnBlock { current_slot, .. } => *current_slot,
|
||||
}
|
||||
}
|
||||
|
||||
fn state_slot(&self) -> Option<Slot> {
|
||||
match self {
|
||||
UpdateJustifiedCheckpointSlots::OnTick { .. } => None,
|
||||
UpdateJustifiedCheckpointSlots::OnBlock { state_slot, .. } => Some(*state_slot),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -162,51 +232,6 @@ fn compute_start_slot_at_epoch<E: EthSpec>(epoch: Epoch) -> Slot {
|
||||
epoch.start_slot(E::slots_per_epoch())
|
||||
}
|
||||
|
||||
/// Called whenever the current time increases.
|
||||
///
|
||||
/// ## Specification
|
||||
///
|
||||
/// Equivalent to:
|
||||
///
|
||||
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#on_tick
|
||||
fn on_tick<T, E>(store: &mut T, time: Slot) -> Result<(), Error<T::Error>>
|
||||
where
|
||||
T: ForkChoiceStore<E>,
|
||||
E: EthSpec,
|
||||
{
|
||||
let previous_slot = store.get_current_slot();
|
||||
|
||||
if time > previous_slot + 1 {
|
||||
return Err(Error::InconsistentOnTick {
|
||||
previous_slot,
|
||||
time,
|
||||
});
|
||||
}
|
||||
|
||||
// Update store time.
|
||||
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(());
|
||||
}
|
||||
|
||||
if store.best_justified_checkpoint().epoch > store.justified_checkpoint().epoch {
|
||||
store
|
||||
.set_justified_checkpoint(*store.best_justified_checkpoint())
|
||||
.map_err(Error::ForkChoiceStoreError)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Used for queuing attestations from the current slot. Only contains the minimum necessary
|
||||
/// information about the attestation.
|
||||
#[derive(Clone, PartialEq, Encode, Decode)]
|
||||
@@ -356,7 +381,7 @@ where
|
||||
// If the current slot is not provided, use the value that was last provided to the store.
|
||||
let current_slot = current_slot.unwrap_or_else(|| fc_store.get_current_slot());
|
||||
|
||||
let proto_array = ProtoArrayForkChoice::new(
|
||||
let proto_array = ProtoArrayForkChoice::new::<E>(
|
||||
finalized_block_slot,
|
||||
finalized_block_state_root,
|
||||
*fc_store.justified_checkpoint(),
|
||||
@@ -473,7 +498,7 @@ where
|
||||
current_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Hash256, Error<T::Error>> {
|
||||
self.update_time(current_slot)?;
|
||||
self.update_time(current_slot, spec)?;
|
||||
|
||||
let store = &mut self.fc_store;
|
||||
|
||||
@@ -482,6 +507,7 @@ where
|
||||
*store.finalized_checkpoint(),
|
||||
store.justified_balances(),
|
||||
store.proposer_boost_root(),
|
||||
current_slot,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
@@ -539,13 +565,11 @@ where
|
||||
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#should_update_justified_checkpoint
|
||||
fn should_update_justified_checkpoint(
|
||||
&mut self,
|
||||
current_slot: Slot,
|
||||
state: &BeaconState<E>,
|
||||
new_justified_checkpoint: Checkpoint,
|
||||
slots: UpdateJustifiedCheckpointSlots,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<bool, Error<T::Error>> {
|
||||
self.update_time(current_slot)?;
|
||||
|
||||
let new_justified_checkpoint = &state.current_justified_checkpoint();
|
||||
self.update_time(slots.current_slot(), spec)?;
|
||||
|
||||
if compute_slots_since_epoch_start::<E>(self.fc_store.get_current_slot())
|
||||
< spec.safe_slots_to_update_justified
|
||||
@@ -557,11 +581,13 @@ where
|
||||
compute_start_slot_at_epoch::<E>(self.fc_store.justified_checkpoint().epoch);
|
||||
|
||||
// This sanity check is not in the spec, but the invariant is implied.
|
||||
if justified_slot >= state.slot() {
|
||||
return Err(Error::AttemptToRevertJustification {
|
||||
store: justified_slot,
|
||||
state: state.slot(),
|
||||
});
|
||||
if let Some(state_slot) = slots.state_slot() {
|
||||
if justified_slot >= state_slot {
|
||||
return Err(Error::AttemptToRevertJustification {
|
||||
store: justified_slot,
|
||||
state: state_slot,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// We know that the slot for `new_justified_checkpoint.root` is not greater than
|
||||
@@ -629,15 +655,15 @@ where
|
||||
state: &BeaconState<E>,
|
||||
payload_verification_status: PayloadVerificationStatus,
|
||||
spec: &ChainSpec,
|
||||
count_unrealized: CountUnrealized,
|
||||
) -> Result<(), Error<T::Error>> {
|
||||
let current_slot = self.update_time(current_slot)?;
|
||||
let current_slot = self.update_time(current_slot, spec)?;
|
||||
|
||||
// Parent block must be known.
|
||||
if !self.proto_array.contains_block(&block.parent_root()) {
|
||||
return Err(Error::InvalidBlock(InvalidBlock::UnknownParent(
|
||||
block.parent_root(),
|
||||
)));
|
||||
}
|
||||
let parent_block = self
|
||||
.proto_array
|
||||
.get_block(&block.parent_root())
|
||||
.ok_or_else(|| Error::InvalidBlock(InvalidBlock::UnknownParent(block.parent_root())))?;
|
||||
|
||||
// Blocks cannot be in the future. If they are, their consideration must be delayed until
|
||||
// the are in the past.
|
||||
@@ -686,29 +712,110 @@ where
|
||||
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
|
||||
> self.fc_store.best_justified_checkpoint().epoch
|
||||
let update_justified_checkpoint_slots = UpdateJustifiedCheckpointSlots::OnBlock {
|
||||
state_slot: state.slot(),
|
||||
current_slot,
|
||||
};
|
||||
|
||||
// Update store with checkpoints if necessary
|
||||
self.update_checkpoints(
|
||||
state.current_justified_checkpoint(),
|
||||
state.finalized_checkpoint(),
|
||||
update_justified_checkpoint_slots,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
// Update unrealized justified/finalized checkpoints.
|
||||
let (unrealized_justified_checkpoint, unrealized_finalized_checkpoint) = if count_unrealized
|
||||
.is_true()
|
||||
{
|
||||
let block_epoch = block.slot().epoch(E::slots_per_epoch());
|
||||
|
||||
// If the parent checkpoints are already at the same epoch as the block being imported,
|
||||
// it's impossible for the unrealized checkpoints to differ from the parent's. This
|
||||
// holds true because:
|
||||
//
|
||||
// 1. A child block cannot have lower FFG checkpoints than its parent.
|
||||
// 2. A block in epoch `N` cannot contain attestations which would justify an epoch higher than `N`.
|
||||
// 3. A block in epoch `N` cannot contain attestations which would finalize an epoch higher than `N - 1`.
|
||||
//
|
||||
// This is an optimization. It should reduce the amount of times we run
|
||||
// `process_justification_and_finalization` by approximately 1/3rd when the chain is
|
||||
// performing optimally.
|
||||
let parent_checkpoints = parent_block
|
||||
.unrealized_justified_checkpoint
|
||||
.zip(parent_block.unrealized_finalized_checkpoint)
|
||||
.filter(|(parent_justified, parent_finalized)| {
|
||||
parent_justified.epoch == block_epoch
|
||||
&& parent_finalized.epoch + 1 >= block_epoch
|
||||
});
|
||||
|
||||
let (unrealized_justified_checkpoint, unrealized_finalized_checkpoint) =
|
||||
if let Some((parent_justified, parent_finalized)) = parent_checkpoints {
|
||||
(parent_justified, parent_finalized)
|
||||
} else {
|
||||
let justification_and_finalization_state = match block {
|
||||
BeaconBlockRef::Merge(_) | BeaconBlockRef::Altair(_) => {
|
||||
let participation_cache =
|
||||
per_epoch_processing::altair::ParticipationCache::new(state, spec)
|
||||
.map_err(Error::ParticipationCacheBuild)?;
|
||||
per_epoch_processing::altair::process_justification_and_finalization(
|
||||
state,
|
||||
&participation_cache,
|
||||
)?
|
||||
}
|
||||
BeaconBlockRef::Base(_) => {
|
||||
let mut validator_statuses =
|
||||
per_epoch_processing::base::ValidatorStatuses::new(state, spec)
|
||||
.map_err(Error::ValidatorStatuses)?;
|
||||
validator_statuses
|
||||
.process_attestations(state)
|
||||
.map_err(Error::ValidatorStatuses)?;
|
||||
per_epoch_processing::base::process_justification_and_finalization(
|
||||
state,
|
||||
&validator_statuses.total_balances,
|
||||
spec,
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
justification_and_finalization_state.current_justified_checkpoint(),
|
||||
justification_and_finalization_state.finalized_checkpoint(),
|
||||
)
|
||||
};
|
||||
|
||||
// Update best known unrealized justified & finalized checkpoints
|
||||
if unrealized_justified_checkpoint.epoch
|
||||
> self.fc_store.unrealized_justified_checkpoint().epoch
|
||||
{
|
||||
self.fc_store
|
||||
.set_best_justified_checkpoint(state.current_justified_checkpoint());
|
||||
.set_unrealized_justified_checkpoint(unrealized_justified_checkpoint);
|
||||
}
|
||||
if self.should_update_justified_checkpoint(current_slot, state, spec)? {
|
||||
if unrealized_finalized_checkpoint.epoch
|
||||
> self.fc_store.unrealized_finalized_checkpoint().epoch
|
||||
{
|
||||
self.fc_store
|
||||
.set_justified_checkpoint(state.current_justified_checkpoint())
|
||||
.map_err(Error::UnableToSetJustifiedCheckpoint)?;
|
||||
.set_unrealized_finalized_checkpoint(unrealized_finalized_checkpoint);
|
||||
}
|
||||
}
|
||||
|
||||
// Update finalized checkpoint.
|
||||
if state.finalized_checkpoint().epoch > self.fc_store.finalized_checkpoint().epoch {
|
||||
self.fc_store
|
||||
.set_finalized_checkpoint(state.finalized_checkpoint());
|
||||
self.fc_store
|
||||
.set_justified_checkpoint(state.current_justified_checkpoint())
|
||||
.map_err(Error::UnableToSetJustifiedCheckpoint)?;
|
||||
}
|
||||
// If block is from past epochs, try to update store's justified & finalized checkpoints right away
|
||||
if block.slot().epoch(E::slots_per_epoch()) < current_slot.epoch(E::slots_per_epoch()) {
|
||||
self.update_checkpoints(
|
||||
unrealized_justified_checkpoint,
|
||||
unrealized_finalized_checkpoint,
|
||||
update_justified_checkpoint_slots,
|
||||
spec,
|
||||
)?;
|
||||
}
|
||||
|
||||
(
|
||||
Some(unrealized_justified_checkpoint),
|
||||
Some(unrealized_finalized_checkpoint),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let target_slot = block
|
||||
.slot()
|
||||
@@ -757,32 +864,68 @@ where
|
||||
|
||||
// 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 {
|
||||
slot: block.slot(),
|
||||
root: block_root,
|
||||
parent_root: Some(block.parent_root()),
|
||||
target_root,
|
||||
current_epoch_shuffling_id: AttestationShufflingId::new(
|
||||
block_root,
|
||||
state,
|
||||
RelativeEpoch::Current,
|
||||
)
|
||||
.map_err(Error::BeaconStateError)?,
|
||||
next_epoch_shuffling_id: AttestationShufflingId::new(
|
||||
block_root,
|
||||
state,
|
||||
RelativeEpoch::Next,
|
||||
)
|
||||
.map_err(Error::BeaconStateError)?,
|
||||
state_root: block.state_root(),
|
||||
justified_checkpoint: state.current_justified_checkpoint(),
|
||||
finalized_checkpoint: state.finalized_checkpoint(),
|
||||
execution_status,
|
||||
})?;
|
||||
self.proto_array.process_block::<E>(
|
||||
ProtoBlock {
|
||||
slot: block.slot(),
|
||||
root: block_root,
|
||||
parent_root: Some(block.parent_root()),
|
||||
target_root,
|
||||
current_epoch_shuffling_id: AttestationShufflingId::new(
|
||||
block_root,
|
||||
state,
|
||||
RelativeEpoch::Current,
|
||||
)
|
||||
.map_err(Error::BeaconStateError)?,
|
||||
next_epoch_shuffling_id: AttestationShufflingId::new(
|
||||
block_root,
|
||||
state,
|
||||
RelativeEpoch::Next,
|
||||
)
|
||||
.map_err(Error::BeaconStateError)?,
|
||||
state_root: block.state_root(),
|
||||
justified_checkpoint: state.current_justified_checkpoint(),
|
||||
finalized_checkpoint: state.finalized_checkpoint(),
|
||||
execution_status,
|
||||
unrealized_justified_checkpoint,
|
||||
unrealized_finalized_checkpoint,
|
||||
},
|
||||
current_slot,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update checkpoints in store if necessary
|
||||
fn update_checkpoints(
|
||||
&mut self,
|
||||
justified_checkpoint: Checkpoint,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
slots: UpdateJustifiedCheckpointSlots,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error<T::Error>> {
|
||||
// Update justified checkpoint.
|
||||
if justified_checkpoint.epoch > self.fc_store.justified_checkpoint().epoch {
|
||||
if justified_checkpoint.epoch > self.fc_store.best_justified_checkpoint().epoch {
|
||||
self.fc_store
|
||||
.set_best_justified_checkpoint(justified_checkpoint);
|
||||
}
|
||||
if self.should_update_justified_checkpoint(justified_checkpoint, slots, spec)? {
|
||||
self.fc_store
|
||||
.set_justified_checkpoint(justified_checkpoint)
|
||||
.map_err(Error::UnableToSetJustifiedCheckpoint)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Update finalized checkpoint.
|
||||
if finalized_checkpoint.epoch > self.fc_store.finalized_checkpoint().epoch {
|
||||
self.fc_store.set_finalized_checkpoint(finalized_checkpoint);
|
||||
self.fc_store
|
||||
.set_justified_checkpoint(justified_checkpoint)
|
||||
.map_err(Error::UnableToSetJustifiedCheckpoint)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates the `epoch` against the current time according to the fork choice store.
|
||||
///
|
||||
/// ## Specification
|
||||
@@ -920,9 +1063,10 @@ where
|
||||
current_slot: Slot,
|
||||
attestation: &IndexedAttestation<E>,
|
||||
is_from_block: AttestationFromBlock,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error<T::Error>> {
|
||||
// Ensure the store is up-to-date.
|
||||
self.update_time(current_slot)?;
|
||||
self.update_time(current_slot, spec)?;
|
||||
|
||||
// Ignore any attestations to the zero hash.
|
||||
//
|
||||
@@ -967,12 +1111,16 @@ where
|
||||
|
||||
/// Call `on_tick` for all slots between `fc_store.get_current_slot()` and the provided
|
||||
/// `current_slot`. Returns the value of `self.fc_store.get_current_slot`.
|
||||
pub fn update_time(&mut self, current_slot: Slot) -> Result<Slot, Error<T::Error>> {
|
||||
pub fn update_time(
|
||||
&mut self,
|
||||
current_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Slot, Error<T::Error>> {
|
||||
while self.fc_store.get_current_slot() < current_slot {
|
||||
let previous_slot = self.fc_store.get_current_slot();
|
||||
// Note: we are relying upon `on_tick` to update `fc_store.time` to ensure we don't
|
||||
// get stuck in a loop.
|
||||
on_tick(&mut self.fc_store, previous_slot + 1)?
|
||||
self.on_tick(previous_slot + 1, spec)?
|
||||
}
|
||||
|
||||
// Process any attestations that might now be eligible.
|
||||
@@ -981,6 +1129,63 @@ where
|
||||
Ok(self.fc_store.get_current_slot())
|
||||
}
|
||||
|
||||
/// Called whenever the current time increases.
|
||||
///
|
||||
/// ## Specification
|
||||
///
|
||||
/// Equivalent to:
|
||||
///
|
||||
/// https://github.com/ethereum/eth2.0-specs/blob/v0.12.1/specs/phase0/fork-choice.md#on_tick
|
||||
fn on_tick(&mut self, time: Slot, spec: &ChainSpec) -> Result<(), Error<T::Error>> {
|
||||
let store = &mut self.fc_store;
|
||||
let previous_slot = store.get_current_slot();
|
||||
|
||||
if time > previous_slot + 1 {
|
||||
return Err(Error::InconsistentOnTick {
|
||||
previous_slot,
|
||||
time,
|
||||
});
|
||||
}
|
||||
|
||||
// Update store time.
|
||||
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(());
|
||||
}
|
||||
|
||||
if store.best_justified_checkpoint().epoch > store.justified_checkpoint().epoch {
|
||||
let store = &self.fc_store;
|
||||
if self.is_descendant_of_finalized(store.best_justified_checkpoint().root) {
|
||||
let store = &mut self.fc_store;
|
||||
store
|
||||
.set_justified_checkpoint(*store.best_justified_checkpoint())
|
||||
.map_err(Error::ForkChoiceStoreError)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Update store.justified_checkpoint if a better unrealized justified checkpoint is known
|
||||
let unrealized_justified_checkpoint = *self.fc_store.unrealized_justified_checkpoint();
|
||||
let unrealized_finalized_checkpoint = *self.fc_store.unrealized_finalized_checkpoint();
|
||||
self.update_checkpoints(
|
||||
unrealized_justified_checkpoint,
|
||||
unrealized_finalized_checkpoint,
|
||||
UpdateJustifiedCheckpointSlots::OnTick { current_slot },
|
||||
spec,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Processes and removes from the queue any queued attestations which may now be eligible for
|
||||
/// processing due to the slot clock incrementing.
|
||||
fn process_attestation_queue(&mut self) -> Result<(), Error<T::Error>> {
|
||||
@@ -1158,6 +1363,14 @@ where
|
||||
*self.fc_store.best_justified_checkpoint()
|
||||
}
|
||||
|
||||
pub fn unrealized_justified_checkpoint(&self) -> Checkpoint {
|
||||
*self.fc_store.unrealized_justified_checkpoint()
|
||||
}
|
||||
|
||||
pub fn unrealized_finalized_checkpoint(&self) -> Checkpoint {
|
||||
*self.fc_store.unrealized_finalized_checkpoint()
|
||||
}
|
||||
|
||||
/// Returns the latest message for a given validator, if any.
|
||||
///
|
||||
/// Returns `(block_root, block_slot)`.
|
||||
|
||||
@@ -50,6 +50,12 @@ pub trait ForkChoiceStore<T: EthSpec>: Sized {
|
||||
/// Returns the `finalized_checkpoint`.
|
||||
fn finalized_checkpoint(&self) -> &Checkpoint;
|
||||
|
||||
/// Returns the `unrealized_justified_checkpoint`.
|
||||
fn unrealized_justified_checkpoint(&self) -> &Checkpoint;
|
||||
|
||||
/// Returns the `unrealized_finalized_checkpoint`.
|
||||
fn unrealized_finalized_checkpoint(&self) -> &Checkpoint;
|
||||
|
||||
/// Returns the `proposer_boost_root`.
|
||||
fn proposer_boost_root(&self) -> Hash256;
|
||||
|
||||
@@ -62,6 +68,12 @@ pub trait ForkChoiceStore<T: EthSpec>: Sized {
|
||||
/// Sets the `best_justified_checkpoint`.
|
||||
fn set_best_justified_checkpoint(&mut self, checkpoint: Checkpoint);
|
||||
|
||||
/// Sets the `unrealized_justified_checkpoint`.
|
||||
fn set_unrealized_justified_checkpoint(&mut self, checkpoint: Checkpoint);
|
||||
|
||||
/// Sets the `unrealized_finalized_checkpoint`.
|
||||
fn set_unrealized_finalized_checkpoint(&mut self, checkpoint: Checkpoint);
|
||||
|
||||
/// Sets the proposer boost root.
|
||||
fn set_proposer_boost_root(&mut self, proposer_boost_root: Hash256);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ mod fork_choice;
|
||||
mod fork_choice_store;
|
||||
|
||||
pub use crate::fork_choice::{
|
||||
AttestationFromBlock, Error, ForkChoice, ForkChoiceView, ForkchoiceUpdateParameters,
|
||||
InvalidAttestation, InvalidBlock, PayloadVerificationStatus, PersistedForkChoice,
|
||||
QueuedAttestation,
|
||||
AttestationFromBlock, CountUnrealized, Error, ForkChoice, ForkChoiceView,
|
||||
ForkchoiceUpdateParameters, InvalidAttestation, InvalidBlock, PayloadVerificationStatus,
|
||||
PersistedForkChoice, QueuedAttestation,
|
||||
};
|
||||
pub use fork_choice_store::ForkChoiceStore;
|
||||
pub use proto_array::{Block as ProtoBlock, ExecutionStatus, InvalidationOperation};
|
||||
|
||||
@@ -12,7 +12,8 @@ use beacon_chain::{
|
||||
StateSkipConfig, WhenSlotSkipped,
|
||||
};
|
||||
use fork_choice::{
|
||||
ForkChoiceStore, InvalidAttestation, InvalidBlock, PayloadVerificationStatus, QueuedAttestation,
|
||||
CountUnrealized, ForkChoiceStore, InvalidAttestation, InvalidBlock, PayloadVerificationStatus,
|
||||
QueuedAttestation,
|
||||
};
|
||||
use store::MemoryStore;
|
||||
use types::{
|
||||
@@ -150,7 +151,7 @@ impl ForkChoiceTest {
|
||||
.chain
|
||||
.canonical_head
|
||||
.fork_choice_write_lock()
|
||||
.update_time(self.harness.chain.slot().unwrap())
|
||||
.update_time(self.harness.chain.slot().unwrap(), &self.harness.spec)
|
||||
.unwrap();
|
||||
func(
|
||||
self.harness
|
||||
@@ -292,6 +293,7 @@ impl ForkChoiceTest {
|
||||
&state,
|
||||
PayloadVerificationStatus::Verified,
|
||||
&self.harness.chain.spec,
|
||||
CountUnrealized::True,
|
||||
)
|
||||
.unwrap();
|
||||
self
|
||||
@@ -334,6 +336,7 @@ impl ForkChoiceTest {
|
||||
&state,
|
||||
PayloadVerificationStatus::Verified,
|
||||
&self.harness.chain.spec,
|
||||
CountUnrealized::True,
|
||||
)
|
||||
.err()
|
||||
.expect("on_block did not return an error");
|
||||
|
||||
@@ -78,7 +78,7 @@ impl ForkChoiceTestDefinition {
|
||||
|
||||
let junk_shuffling_id =
|
||||
AttestationShufflingId::from_components(Epoch::new(0), Hash256::zero());
|
||||
let mut fork_choice = ProtoArrayForkChoice::new(
|
||||
let mut fork_choice = ProtoArrayForkChoice::new::<MainnetEthSpec>(
|
||||
self.finalized_block_slot,
|
||||
Hash256::zero(),
|
||||
self.justified_checkpoint,
|
||||
@@ -103,6 +103,7 @@ impl ForkChoiceTestDefinition {
|
||||
finalized_checkpoint,
|
||||
&justified_state_balances,
|
||||
Hash256::zero(),
|
||||
Slot::new(0),
|
||||
&spec,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
@@ -129,6 +130,7 @@ impl ForkChoiceTestDefinition {
|
||||
finalized_checkpoint,
|
||||
&justified_state_balances,
|
||||
proposer_boost_root,
|
||||
Slot::new(0),
|
||||
&spec,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
@@ -152,6 +154,7 @@ impl ForkChoiceTestDefinition {
|
||||
finalized_checkpoint,
|
||||
&justified_state_balances,
|
||||
Hash256::zero(),
|
||||
Slot::new(0),
|
||||
&spec,
|
||||
);
|
||||
|
||||
@@ -190,13 +193,17 @@ impl ForkChoiceTestDefinition {
|
||||
execution_status: ExecutionStatus::Optimistic(
|
||||
ExecutionBlockHash::from_root(root),
|
||||
),
|
||||
unrealized_justified_checkpoint: None,
|
||||
unrealized_finalized_checkpoint: None,
|
||||
};
|
||||
fork_choice.process_block(block).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"process_block op at index {} returned error: {:?}",
|
||||
op_index, e
|
||||
)
|
||||
});
|
||||
fork_choice
|
||||
.process_block::<MainnetEthSpec>(block, slot)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"process_block op at index {} returned error: {:?}",
|
||||
op_index, e
|
||||
)
|
||||
});
|
||||
check_bytes_round_trip(&fork_choice);
|
||||
}
|
||||
Operation::ProcessAttestation {
|
||||
|
||||
@@ -97,6 +97,10 @@ pub struct ProtoNode {
|
||||
/// Indicates if an execution node has marked this block as valid. Also contains the execution
|
||||
/// block hash.
|
||||
pub execution_status: ExecutionStatus,
|
||||
#[ssz(with = "four_byte_option_checkpoint")]
|
||||
pub unrealized_justified_checkpoint: Option<Checkpoint>,
|
||||
#[ssz(with = "four_byte_option_checkpoint")]
|
||||
pub unrealized_finalized_checkpoint: Option<Checkpoint>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Encode, Decode, Serialize, Deserialize, Copy, Clone)]
|
||||
@@ -140,6 +144,7 @@ impl ProtoArray {
|
||||
/// - Compare the current node with the parents best-child, updating it if the current node
|
||||
/// should become the best child.
|
||||
/// - If required, update the parents best-descendant with the current node or its best-descendant.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn apply_score_changes<E: EthSpec>(
|
||||
&mut self,
|
||||
mut deltas: Vec<i64>,
|
||||
@@ -147,6 +152,7 @@ impl ProtoArray {
|
||||
finalized_checkpoint: Checkpoint,
|
||||
new_balances: &[u64],
|
||||
proposer_boost_root: Hash256,
|
||||
current_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if deltas.len() != self.indices.len() {
|
||||
@@ -280,7 +286,11 @@ impl ProtoArray {
|
||||
|
||||
// If the node has a parent, try to update its best-child and best-descendant.
|
||||
if let Some(parent_index) = node.parent {
|
||||
self.maybe_update_best_child_and_descendant(parent_index, node_index)?;
|
||||
self.maybe_update_best_child_and_descendant::<E>(
|
||||
parent_index,
|
||||
node_index,
|
||||
current_slot,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +300,7 @@ impl ProtoArray {
|
||||
/// Register a block with the fork choice.
|
||||
///
|
||||
/// It is only sane to supply a `None` parent for the genesis block.
|
||||
pub fn on_block(&mut self, block: Block) -> Result<(), Error> {
|
||||
pub fn on_block<E: EthSpec>(&mut self, block: Block, current_slot: Slot) -> Result<(), Error> {
|
||||
// If the block is already known, simply ignore it.
|
||||
if self.indices.contains_key(&block.root) {
|
||||
return Ok(());
|
||||
@@ -314,6 +324,8 @@ impl ProtoArray {
|
||||
best_child: None,
|
||||
best_descendant: None,
|
||||
execution_status: block.execution_status,
|
||||
unrealized_justified_checkpoint: block.unrealized_justified_checkpoint,
|
||||
unrealized_finalized_checkpoint: block.unrealized_finalized_checkpoint,
|
||||
};
|
||||
|
||||
// If the parent has an invalid execution status, return an error before adding the block to
|
||||
@@ -335,7 +347,11 @@ impl ProtoArray {
|
||||
self.nodes.push(node.clone());
|
||||
|
||||
if let Some(parent_index) = node.parent {
|
||||
self.maybe_update_best_child_and_descendant(parent_index, node_index)?;
|
||||
self.maybe_update_best_child_and_descendant::<E>(
|
||||
parent_index,
|
||||
node_index,
|
||||
current_slot,
|
||||
)?;
|
||||
|
||||
if matches!(block.execution_status, ExecutionStatus::Valid(_)) {
|
||||
self.propagate_execution_payload_validation_by_index(parent_index)?;
|
||||
@@ -604,7 +620,11 @@ impl ProtoArray {
|
||||
/// been called without a subsequent `Self::apply_score_changes` call. This is because
|
||||
/// `on_new_block` does not attempt to walk backwards through the tree and update the
|
||||
/// best-child/best-descendant links.
|
||||
pub fn find_head(&self, justified_root: &Hash256) -> Result<Hash256, Error> {
|
||||
pub fn find_head<E: EthSpec>(
|
||||
&self,
|
||||
justified_root: &Hash256,
|
||||
current_slot: Slot,
|
||||
) -> Result<Hash256, Error> {
|
||||
let justified_index = self
|
||||
.indices
|
||||
.get(justified_root)
|
||||
@@ -637,7 +657,7 @@ impl ProtoArray {
|
||||
.ok_or(Error::InvalidBestDescendant(best_descendant_index))?;
|
||||
|
||||
// Perform a sanity check that the node is indeed valid to be the head.
|
||||
if !self.node_is_viable_for_head(best_node) {
|
||||
if !self.node_is_viable_for_head::<E>(best_node, current_slot) {
|
||||
return Err(Error::InvalidBestNode(Box::new(InvalidBestNodeInfo {
|
||||
start_root: *justified_root,
|
||||
justified_checkpoint: self.justified_checkpoint,
|
||||
@@ -733,10 +753,11 @@ impl ProtoArray {
|
||||
/// best-descendant.
|
||||
/// - The child is not the best child but becomes the best child.
|
||||
/// - The child is not the best child and does not become the best child.
|
||||
fn maybe_update_best_child_and_descendant(
|
||||
fn maybe_update_best_child_and_descendant<E: EthSpec>(
|
||||
&mut self,
|
||||
parent_index: usize,
|
||||
child_index: usize,
|
||||
current_slot: Slot,
|
||||
) -> Result<(), Error> {
|
||||
let child = self
|
||||
.nodes
|
||||
@@ -748,7 +769,8 @@ impl ProtoArray {
|
||||
.get(parent_index)
|
||||
.ok_or(Error::InvalidNodeIndex(parent_index))?;
|
||||
|
||||
let child_leads_to_viable_head = self.node_leads_to_viable_head(child)?;
|
||||
let child_leads_to_viable_head =
|
||||
self.node_leads_to_viable_head::<E>(child, current_slot)?;
|
||||
|
||||
// These three variables are aliases to the three options that we may set the
|
||||
// `parent.best_child` and `parent.best_descendant` to.
|
||||
@@ -761,54 +783,54 @@ impl ProtoArray {
|
||||
);
|
||||
let no_change = (parent.best_child, parent.best_descendant);
|
||||
|
||||
let (new_best_child, new_best_descendant) = if let Some(best_child_index) =
|
||||
parent.best_child
|
||||
{
|
||||
if best_child_index == child_index && !child_leads_to_viable_head {
|
||||
// If the child is already the best-child of the parent but it's not viable for
|
||||
// the head, remove it.
|
||||
change_to_none
|
||||
} else if best_child_index == child_index {
|
||||
// If the child is the best-child already, set it again to ensure that the
|
||||
// best-descendant of the parent is updated.
|
||||
change_to_child
|
||||
} else {
|
||||
let best_child = self
|
||||
.nodes
|
||||
.get(best_child_index)
|
||||
.ok_or(Error::InvalidBestDescendant(best_child_index))?;
|
||||
|
||||
let best_child_leads_to_viable_head = self.node_leads_to_viable_head(best_child)?;
|
||||
|
||||
if child_leads_to_viable_head && !best_child_leads_to_viable_head {
|
||||
// The child leads to a viable head, but the current best-child doesn't.
|
||||
let (new_best_child, new_best_descendant) =
|
||||
if let Some(best_child_index) = parent.best_child {
|
||||
if best_child_index == child_index && !child_leads_to_viable_head {
|
||||
// If the child is already the best-child of the parent but it's not viable for
|
||||
// the head, remove it.
|
||||
change_to_none
|
||||
} else if best_child_index == child_index {
|
||||
// If the child is the best-child already, set it again to ensure that the
|
||||
// best-descendant of the parent is updated.
|
||||
change_to_child
|
||||
} else if !child_leads_to_viable_head && best_child_leads_to_viable_head {
|
||||
// The best child leads to a viable head, but the child doesn't.
|
||||
no_change
|
||||
} else if child.weight == best_child.weight {
|
||||
// Tie-breaker of equal weights by root.
|
||||
if child.root >= best_child.root {
|
||||
change_to_child
|
||||
} else {
|
||||
no_change
|
||||
}
|
||||
} else {
|
||||
// Choose the winner by weight.
|
||||
if child.weight >= best_child.weight {
|
||||
let best_child = self
|
||||
.nodes
|
||||
.get(best_child_index)
|
||||
.ok_or(Error::InvalidBestDescendant(best_child_index))?;
|
||||
|
||||
let best_child_leads_to_viable_head =
|
||||
self.node_leads_to_viable_head::<E>(best_child, current_slot)?;
|
||||
|
||||
if child_leads_to_viable_head && !best_child_leads_to_viable_head {
|
||||
// The child leads to a viable head, but the current best-child doesn't.
|
||||
change_to_child
|
||||
} else {
|
||||
} else if !child_leads_to_viable_head && best_child_leads_to_viable_head {
|
||||
// The best child leads to a viable head, but the child doesn't.
|
||||
no_change
|
||||
} else if child.weight == best_child.weight {
|
||||
// Tie-breaker of equal weights by root.
|
||||
if child.root >= best_child.root {
|
||||
change_to_child
|
||||
} else {
|
||||
no_change
|
||||
}
|
||||
} else {
|
||||
// Choose the winner by weight.
|
||||
if child.weight >= best_child.weight {
|
||||
change_to_child
|
||||
} else {
|
||||
no_change
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if child_leads_to_viable_head {
|
||||
// There is no current best-child and the child is viable.
|
||||
change_to_child
|
||||
} else {
|
||||
// There is no current best-child but the child is not viable.
|
||||
no_change
|
||||
};
|
||||
} else if child_leads_to_viable_head {
|
||||
// There is no current best-child and the child is viable.
|
||||
change_to_child
|
||||
} else {
|
||||
// There is no current best-child but the child is not viable.
|
||||
no_change
|
||||
};
|
||||
|
||||
let parent = self
|
||||
.nodes
|
||||
@@ -823,7 +845,11 @@ impl ProtoArray {
|
||||
|
||||
/// Indicates if the node itself is viable for the head, or if it's best descendant is viable
|
||||
/// for the head.
|
||||
fn node_leads_to_viable_head(&self, node: &ProtoNode) -> Result<bool, Error> {
|
||||
fn node_leads_to_viable_head<E: EthSpec>(
|
||||
&self,
|
||||
node: &ProtoNode,
|
||||
current_slot: Slot,
|
||||
) -> Result<bool, Error> {
|
||||
let best_descendant_is_viable_for_head =
|
||||
if let Some(best_descendant_index) = node.best_descendant {
|
||||
let best_descendant = self
|
||||
@@ -831,12 +857,13 @@ impl ProtoArray {
|
||||
.get(best_descendant_index)
|
||||
.ok_or(Error::InvalidBestDescendant(best_descendant_index))?;
|
||||
|
||||
self.node_is_viable_for_head(best_descendant)
|
||||
self.node_is_viable_for_head::<E>(best_descendant, current_slot)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Ok(best_descendant_is_viable_for_head || self.node_is_viable_for_head(node))
|
||||
Ok(best_descendant_is_viable_for_head
|
||||
|| self.node_is_viable_for_head::<E>(node, current_slot))
|
||||
}
|
||||
|
||||
/// This is the equivalent to the `filter_block_tree` function in the eth2 spec:
|
||||
@@ -845,18 +872,43 @@ impl ProtoArray {
|
||||
///
|
||||
/// Any node that has a different finalized or justified epoch should not be viable for the
|
||||
/// head.
|
||||
fn node_is_viable_for_head(&self, node: &ProtoNode) -> bool {
|
||||
fn node_is_viable_for_head<E: EthSpec>(&self, node: &ProtoNode, current_slot: Slot) -> bool {
|
||||
if node.execution_status.is_invalid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let (Some(node_justified_checkpoint), Some(node_finalized_checkpoint)) =
|
||||
let checkpoint_match_predicate =
|
||||
|node_justified_checkpoint: Checkpoint, node_finalized_checkpoint: Checkpoint| {
|
||||
let correct_justified = node_justified_checkpoint == self.justified_checkpoint
|
||||
|| self.justified_checkpoint.epoch == Epoch::new(0);
|
||||
let correct_finalized = node_finalized_checkpoint == self.finalized_checkpoint
|
||||
|| self.finalized_checkpoint.epoch == Epoch::new(0);
|
||||
correct_justified && correct_finalized
|
||||
};
|
||||
|
||||
if let (
|
||||
Some(unrealized_justified_checkpoint),
|
||||
Some(unrealized_finalized_checkpoint),
|
||||
Some(justified_checkpoint),
|
||||
Some(finalized_checkpoint),
|
||||
) = (
|
||||
node.unrealized_justified_checkpoint,
|
||||
node.unrealized_finalized_checkpoint,
|
||||
node.justified_checkpoint,
|
||||
node.finalized_checkpoint,
|
||||
) {
|
||||
if node.slot.epoch(E::slots_per_epoch()) < current_slot.epoch(E::slots_per_epoch()) {
|
||||
checkpoint_match_predicate(
|
||||
unrealized_justified_checkpoint,
|
||||
unrealized_finalized_checkpoint,
|
||||
)
|
||||
} else {
|
||||
checkpoint_match_predicate(justified_checkpoint, finalized_checkpoint)
|
||||
}
|
||||
} else if let (Some(justified_checkpoint), Some(finalized_checkpoint)) =
|
||||
(node.justified_checkpoint, node.finalized_checkpoint)
|
||||
{
|
||||
(node_justified_checkpoint == self.justified_checkpoint
|
||||
|| self.justified_checkpoint.epoch == Epoch::new(0))
|
||||
&& (node_finalized_checkpoint == self.finalized_checkpoint
|
||||
|| self.finalized_checkpoint.epoch == Epoch::new(0))
|
||||
checkpoint_match_predicate(justified_checkpoint, finalized_checkpoint)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -124,6 +124,8 @@ pub struct Block {
|
||||
/// Indicates if an execution node has marked this block as valid. Also contains the execution
|
||||
/// block hash.
|
||||
pub execution_status: ExecutionStatus,
|
||||
pub unrealized_justified_checkpoint: Option<Checkpoint>,
|
||||
pub unrealized_finalized_checkpoint: Option<Checkpoint>,
|
||||
}
|
||||
|
||||
/// A Vec-wrapper which will grow to match any request.
|
||||
@@ -162,7 +164,7 @@ pub struct ProtoArrayForkChoice {
|
||||
|
||||
impl ProtoArrayForkChoice {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
pub fn new<E: EthSpec>(
|
||||
finalized_block_slot: Slot,
|
||||
finalized_block_state_root: Hash256,
|
||||
justified_checkpoint: Checkpoint,
|
||||
@@ -193,10 +195,12 @@ impl ProtoArrayForkChoice {
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
execution_status,
|
||||
unrealized_justified_checkpoint: Some(justified_checkpoint),
|
||||
unrealized_finalized_checkpoint: Some(finalized_checkpoint),
|
||||
};
|
||||
|
||||
proto_array
|
||||
.on_block(block)
|
||||
.on_block::<E>(block, finalized_block_slot)
|
||||
.map_err(|e| format!("Failed to add finalized block to proto_array: {:?}", e))?;
|
||||
|
||||
Ok(Self {
|
||||
@@ -242,13 +246,17 @@ impl ProtoArrayForkChoice {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_block(&mut self, block: Block) -> Result<(), String> {
|
||||
pub fn process_block<E: EthSpec>(
|
||||
&mut self,
|
||||
block: Block,
|
||||
current_slot: Slot,
|
||||
) -> Result<(), String> {
|
||||
if block.parent_root.is_none() {
|
||||
return Err("Missing parent root".to_string());
|
||||
}
|
||||
|
||||
self.proto_array
|
||||
.on_block(block)
|
||||
.on_block::<E>(block, current_slot)
|
||||
.map_err(|e| format!("process_block_error: {:?}", e))
|
||||
}
|
||||
|
||||
@@ -258,6 +266,7 @@ impl ProtoArrayForkChoice {
|
||||
finalized_checkpoint: Checkpoint,
|
||||
justified_state_balances: &[u64],
|
||||
proposer_boost_root: Hash256,
|
||||
current_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Hash256, String> {
|
||||
let old_balances = &mut self.balances;
|
||||
@@ -279,6 +288,7 @@ impl ProtoArrayForkChoice {
|
||||
finalized_checkpoint,
|
||||
new_balances,
|
||||
proposer_boost_root,
|
||||
current_slot,
|
||||
spec,
|
||||
)
|
||||
.map_err(|e| format!("find_head apply_score_changes failed: {:?}", e))?;
|
||||
@@ -286,7 +296,7 @@ impl ProtoArrayForkChoice {
|
||||
*old_balances = new_balances.to_vec();
|
||||
|
||||
self.proto_array
|
||||
.find_head(&justified_checkpoint.root)
|
||||
.find_head::<E>(&justified_checkpoint.root, current_slot)
|
||||
.map_err(|e| format!("find_head failed: {:?}", e))
|
||||
}
|
||||
|
||||
@@ -341,6 +351,8 @@ impl ProtoArrayForkChoice {
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
execution_status: block.execution_status,
|
||||
unrealized_justified_checkpoint: block.unrealized_justified_checkpoint,
|
||||
unrealized_finalized_checkpoint: block.unrealized_finalized_checkpoint,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -485,6 +497,7 @@ fn compute_deltas(
|
||||
#[cfg(test)]
|
||||
mod test_compute_deltas {
|
||||
use super::*;
|
||||
use types::MainnetEthSpec;
|
||||
|
||||
/// Gives a hash that is not the zero hash (unless i is `usize::max_value)`.
|
||||
fn hash_from_index(i: usize) -> Hash256 {
|
||||
@@ -510,7 +523,7 @@ mod test_compute_deltas {
|
||||
root: finalized_root,
|
||||
};
|
||||
|
||||
let mut fc = ProtoArrayForkChoice::new(
|
||||
let mut fc = ProtoArrayForkChoice::new::<MainnetEthSpec>(
|
||||
genesis_slot,
|
||||
state_root,
|
||||
genesis_checkpoint,
|
||||
@@ -523,34 +536,44 @@ mod test_compute_deltas {
|
||||
|
||||
// Add block that is a finalized descendant.
|
||||
fc.proto_array
|
||||
.on_block(Block {
|
||||
slot: genesis_slot + 1,
|
||||
root: finalized_desc,
|
||||
parent_root: Some(finalized_root),
|
||||
state_root,
|
||||
target_root: finalized_root,
|
||||
current_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
next_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
justified_checkpoint: genesis_checkpoint,
|
||||
finalized_checkpoint: genesis_checkpoint,
|
||||
execution_status,
|
||||
})
|
||||
.on_block::<MainnetEthSpec>(
|
||||
Block {
|
||||
slot: genesis_slot + 1,
|
||||
root: finalized_desc,
|
||||
parent_root: Some(finalized_root),
|
||||
state_root,
|
||||
target_root: finalized_root,
|
||||
current_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
next_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
justified_checkpoint: genesis_checkpoint,
|
||||
finalized_checkpoint: genesis_checkpoint,
|
||||
execution_status,
|
||||
unrealized_justified_checkpoint: Some(genesis_checkpoint),
|
||||
unrealized_finalized_checkpoint: Some(genesis_checkpoint),
|
||||
},
|
||||
genesis_slot + 1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Add block that is *not* a finalized descendant.
|
||||
fc.proto_array
|
||||
.on_block(Block {
|
||||
slot: genesis_slot + 1,
|
||||
root: not_finalized_desc,
|
||||
parent_root: None,
|
||||
state_root,
|
||||
target_root: finalized_root,
|
||||
current_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
next_epoch_shuffling_id: junk_shuffling_id,
|
||||
justified_checkpoint: genesis_checkpoint,
|
||||
finalized_checkpoint: genesis_checkpoint,
|
||||
execution_status,
|
||||
})
|
||||
.on_block::<MainnetEthSpec>(
|
||||
Block {
|
||||
slot: genesis_slot + 1,
|
||||
root: not_finalized_desc,
|
||||
parent_root: None,
|
||||
state_root,
|
||||
target_root: finalized_root,
|
||||
current_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
next_epoch_shuffling_id: junk_shuffling_id,
|
||||
justified_checkpoint: genesis_checkpoint,
|
||||
finalized_checkpoint: genesis_checkpoint,
|
||||
execution_status,
|
||||
unrealized_justified_checkpoint: None,
|
||||
unrealized_finalized_checkpoint: None,
|
||||
},
|
||||
genesis_slot + 1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(!fc.is_descendant(unknown, unknown));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
pub use epoch_processing_summary::EpochProcessingSummary;
|
||||
use errors::EpochProcessingError as Error;
|
||||
pub use justification_and_finalization_state::JustificationAndFinalizationState;
|
||||
pub use registry_updates::process_registry_updates;
|
||||
use safe_arith::SafeArith;
|
||||
pub use slashings::process_slashings;
|
||||
@@ -14,6 +15,7 @@ pub mod effective_balance_updates;
|
||||
pub mod epoch_processing_summary;
|
||||
pub mod errors;
|
||||
pub mod historical_roots_update;
|
||||
pub mod justification_and_finalization_state;
|
||||
pub mod registry_updates;
|
||||
pub mod resets;
|
||||
pub mod slashings;
|
||||
|
||||
@@ -33,7 +33,9 @@ pub fn process_epoch<T: EthSpec>(
|
||||
let sync_committee = state.current_sync_committee()?.clone();
|
||||
|
||||
// Justification and finalization.
|
||||
process_justification_and_finalization(state, &participation_cache)?;
|
||||
let justification_and_finalization_state =
|
||||
process_justification_and_finalization(state, &participation_cache)?;
|
||||
justification_and_finalization_state.apply_changes_to_state(state);
|
||||
|
||||
process_inactivity_updates(state, &participation_cache, spec)?;
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
use super::ParticipationCache;
|
||||
use crate::per_epoch_processing::weigh_justification_and_finalization;
|
||||
use crate::per_epoch_processing::Error;
|
||||
use crate::per_epoch_processing::{
|
||||
weigh_justification_and_finalization, JustificationAndFinalizationState,
|
||||
};
|
||||
use safe_arith::SafeArith;
|
||||
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
|
||||
use types::{BeaconState, EthSpec};
|
||||
|
||||
/// Update the justified and finalized checkpoints for matching target attestations.
|
||||
pub fn process_justification_and_finalization<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
state: &BeaconState<T>,
|
||||
participation_cache: &ParticipationCache,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<JustificationAndFinalizationState<T>, Error> {
|
||||
let justification_and_finalization_state = JustificationAndFinalizationState::new(state);
|
||||
|
||||
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
|
||||
return Ok(());
|
||||
return Ok(justification_and_finalization_state);
|
||||
}
|
||||
|
||||
let previous_epoch = state.previous_epoch();
|
||||
@@ -24,7 +28,7 @@ pub fn process_justification_and_finalization<T: EthSpec>(
|
||||
let previous_target_balance = previous_indices.total_balance()?;
|
||||
let current_target_balance = current_indices.total_balance()?;
|
||||
weigh_justification_and_finalization(
|
||||
state,
|
||||
justification_and_finalization_state,
|
||||
total_active_balance,
|
||||
previous_target_balance,
|
||||
current_target_balance,
|
||||
|
||||
@@ -31,7 +31,9 @@ pub fn process_epoch<T: EthSpec>(
|
||||
validator_statuses.process_attestations(state)?;
|
||||
|
||||
// Justification and finalization.
|
||||
process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
|
||||
let justification_and_finalization_state =
|
||||
process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
|
||||
justification_and_finalization_state.apply_changes_to_state(state);
|
||||
|
||||
// Rewards and Penalties.
|
||||
process_rewards_and_penalties(state, &mut validator_statuses, spec)?;
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
use crate::per_epoch_processing::base::TotalBalances;
|
||||
use crate::per_epoch_processing::weigh_justification_and_finalization;
|
||||
use crate::per_epoch_processing::Error;
|
||||
use crate::per_epoch_processing::{
|
||||
weigh_justification_and_finalization, JustificationAndFinalizationState,
|
||||
};
|
||||
use safe_arith::SafeArith;
|
||||
use types::{BeaconState, ChainSpec, EthSpec};
|
||||
|
||||
/// Update the justified and finalized checkpoints for matching target attestations.
|
||||
pub fn process_justification_and_finalization<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
state: &BeaconState<T>,
|
||||
total_balances: &TotalBalances,
|
||||
_spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<JustificationAndFinalizationState<T>, Error> {
|
||||
let justification_and_finalization_state = JustificationAndFinalizationState::new(state);
|
||||
|
||||
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
|
||||
return Ok(());
|
||||
return Ok(justification_and_finalization_state);
|
||||
}
|
||||
|
||||
weigh_justification_and_finalization(
|
||||
state,
|
||||
justification_and_finalization_state,
|
||||
total_balances.current_epoch(),
|
||||
total_balances.previous_epoch_target_attesters(),
|
||||
total_balances.current_epoch_target_attesters(),
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
use types::{BeaconState, BeaconStateError, BitVector, Checkpoint, Epoch, EthSpec, Hash256};
|
||||
|
||||
/// This is a subset of the `BeaconState` which is used to compute justification and finality
|
||||
/// without modifying the `BeaconState`.
|
||||
///
|
||||
/// A `JustificationAndFinalizationState` can be created from a `BeaconState` to compute
|
||||
/// justification/finality changes and then applied to a `BeaconState` to enshrine those changes.
|
||||
#[must_use = "this value must be applied to a state or explicitly dropped"]
|
||||
pub struct JustificationAndFinalizationState<T: EthSpec> {
|
||||
/*
|
||||
* Immutable fields.
|
||||
*/
|
||||
previous_epoch: Epoch,
|
||||
previous_epoch_target_root: Result<Hash256, BeaconStateError>,
|
||||
current_epoch: Epoch,
|
||||
current_epoch_target_root: Result<Hash256, BeaconStateError>,
|
||||
/*
|
||||
* Mutable fields.
|
||||
*/
|
||||
previous_justified_checkpoint: Checkpoint,
|
||||
current_justified_checkpoint: Checkpoint,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
justification_bits: BitVector<T::JustificationBitsLength>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> JustificationAndFinalizationState<T> {
|
||||
pub fn new(state: &BeaconState<T>) -> Self {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let current_epoch = state.current_epoch();
|
||||
Self {
|
||||
previous_epoch,
|
||||
previous_epoch_target_root: state.get_block_root_at_epoch(previous_epoch).copied(),
|
||||
current_epoch,
|
||||
current_epoch_target_root: state.get_block_root_at_epoch(current_epoch).copied(),
|
||||
previous_justified_checkpoint: state.previous_justified_checkpoint(),
|
||||
current_justified_checkpoint: state.current_justified_checkpoint(),
|
||||
finalized_checkpoint: state.finalized_checkpoint(),
|
||||
justification_bits: state.justification_bits().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_changes_to_state(self, state: &mut BeaconState<T>) {
|
||||
let Self {
|
||||
/*
|
||||
* Immutable fields do not need to be used.
|
||||
*/
|
||||
previous_epoch: _,
|
||||
previous_epoch_target_root: _,
|
||||
current_epoch: _,
|
||||
current_epoch_target_root: _,
|
||||
/*
|
||||
* Mutable fields *must* be used.
|
||||
*/
|
||||
previous_justified_checkpoint,
|
||||
current_justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
justification_bits,
|
||||
} = self;
|
||||
|
||||
*state.previous_justified_checkpoint_mut() = previous_justified_checkpoint;
|
||||
*state.current_justified_checkpoint_mut() = current_justified_checkpoint;
|
||||
*state.finalized_checkpoint_mut() = finalized_checkpoint;
|
||||
*state.justification_bits_mut() = justification_bits;
|
||||
}
|
||||
|
||||
pub fn previous_epoch(&self) -> Epoch {
|
||||
self.previous_epoch
|
||||
}
|
||||
|
||||
pub fn current_epoch(&self) -> Epoch {
|
||||
self.current_epoch
|
||||
}
|
||||
|
||||
pub fn get_block_root_at_epoch(&self, epoch: Epoch) -> Result<Hash256, BeaconStateError> {
|
||||
if epoch == self.previous_epoch {
|
||||
self.previous_epoch_target_root.clone()
|
||||
} else if epoch == self.current_epoch {
|
||||
self.current_epoch_target_root.clone()
|
||||
} else {
|
||||
Err(BeaconStateError::SlotOutOfBounds)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous_justified_checkpoint(&self) -> Checkpoint {
|
||||
self.previous_justified_checkpoint
|
||||
}
|
||||
|
||||
pub fn previous_justified_checkpoint_mut(&mut self) -> &mut Checkpoint {
|
||||
&mut self.previous_justified_checkpoint
|
||||
}
|
||||
|
||||
pub fn current_justified_checkpoint_mut(&mut self) -> &mut Checkpoint {
|
||||
&mut self.current_justified_checkpoint
|
||||
}
|
||||
|
||||
pub fn current_justified_checkpoint(&self) -> Checkpoint {
|
||||
self.current_justified_checkpoint
|
||||
}
|
||||
|
||||
pub fn finalized_checkpoint(&self) -> Checkpoint {
|
||||
self.finalized_checkpoint
|
||||
}
|
||||
|
||||
pub fn finalized_checkpoint_mut(&mut self) -> &mut Checkpoint {
|
||||
&mut self.finalized_checkpoint
|
||||
}
|
||||
|
||||
pub fn justification_bits(&self) -> &BitVector<T::JustificationBitsLength> {
|
||||
&self.justification_bits
|
||||
}
|
||||
|
||||
pub fn justification_bits_mut(&mut self) -> &mut BitVector<T::JustificationBitsLength> {
|
||||
&mut self.justification_bits
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
use crate::per_epoch_processing::Error;
|
||||
use crate::per_epoch_processing::{Error, JustificationAndFinalizationState};
|
||||
use safe_arith::SafeArith;
|
||||
use std::ops::Range;
|
||||
use types::{BeaconState, Checkpoint, EthSpec};
|
||||
use types::{Checkpoint, EthSpec};
|
||||
|
||||
/// Update the justified and finalized checkpoints for matching target attestations.
|
||||
#[allow(clippy::if_same_then_else)] // For readability and consistency with spec.
|
||||
pub fn weigh_justification_and_finalization<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
mut state: JustificationAndFinalizationState<T>,
|
||||
total_active_balance: u64,
|
||||
previous_target_balance: u64,
|
||||
current_target_balance: u64,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<JustificationAndFinalizationState<T>, Error> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let current_epoch = state.current_epoch();
|
||||
|
||||
@@ -24,7 +24,7 @@ pub fn weigh_justification_and_finalization<T: EthSpec>(
|
||||
if previous_target_balance.safe_mul(3)? >= total_active_balance.safe_mul(2)? {
|
||||
*state.current_justified_checkpoint_mut() = Checkpoint {
|
||||
epoch: previous_epoch,
|
||||
root: *state.get_block_root_at_epoch(previous_epoch)?,
|
||||
root: state.get_block_root_at_epoch(previous_epoch)?,
|
||||
};
|
||||
state.justification_bits_mut().set(1, true)?;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ pub fn weigh_justification_and_finalization<T: EthSpec>(
|
||||
if current_target_balance.safe_mul(3)? >= total_active_balance.safe_mul(2)? {
|
||||
*state.current_justified_checkpoint_mut() = Checkpoint {
|
||||
epoch: current_epoch,
|
||||
root: *state.get_block_root_at_epoch(current_epoch)?,
|
||||
root: state.get_block_root_at_epoch(current_epoch)?,
|
||||
};
|
||||
state.justification_bits_mut().set(0, true)?;
|
||||
}
|
||||
@@ -66,5 +66,5 @@ pub fn weigh_justification_and_finalization<T: EthSpec>(
|
||||
*state.finalized_checkpoint_mut() = old_current_justified_checkpoint;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@ macro_rules! impl_test_random_for_u8_array {
|
||||
};
|
||||
}
|
||||
|
||||
impl_test_random_for_u8_array!(3);
|
||||
impl_test_random_for_u8_array!(4);
|
||||
impl_test_random_for_u8_array!(32);
|
||||
impl_test_random_for_u8_array!(48);
|
||||
|
||||
Reference in New Issue
Block a user