mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 20:57:10 +00:00
resolve merge conflicts
This commit is contained in:
@@ -19,5 +19,6 @@ types = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
beacon_chain = { workspace = true }
|
||||
bls = { workspace = true }
|
||||
store = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
@@ -3,10 +3,9 @@ use crate::{ForkChoiceStore, InvalidationOperation};
|
||||
use fixed_bytes::FixedBytesExtended;
|
||||
use logging::crit;
|
||||
use proto_array::{
|
||||
Block as ProtoBlock, DisallowedReOrgOffsets, ExecutionStatus, JustifiedBalances,
|
||||
ProposerHeadError, ProposerHeadInfo, ProtoArrayForkChoice, ReOrgThreshold,
|
||||
Block as ProtoBlock, DisallowedReOrgOffsets, ExecutionStatus, JustifiedBalances, LatestMessage,
|
||||
PayloadStatus, ProposerHeadError, ProposerHeadInfo, ProtoArrayForkChoice, ReOrgThreshold,
|
||||
};
|
||||
use ssz::{Decode, Encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use state_processing::{
|
||||
per_block_processing::errors::AttesterSlashingValidationError, per_epoch_processing,
|
||||
@@ -20,13 +19,14 @@ use tracing::{debug, instrument, warn};
|
||||
use types::{
|
||||
AbstractExecPayload, AttestationShufflingId, AttesterSlashingRef, BeaconBlockRef, BeaconState,
|
||||
BeaconStateError, ChainSpec, Checkpoint, Epoch, EthSpec, ExecPayload, ExecutionBlockHash,
|
||||
Hash256, IndexedAttestationRef, RelativeEpoch, SignedBeaconBlock, Slot,
|
||||
consts::bellatrix::INTERVALS_PER_SLOT,
|
||||
Hash256, IndexedAttestationRef, IndexedPayloadAttestation, RelativeEpoch, SignedBeaconBlock,
|
||||
Slot,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error<T> {
|
||||
InvalidAttestation(InvalidAttestation),
|
||||
InvalidPayloadAttestation(InvalidPayloadAttestation),
|
||||
InvalidAttesterSlashing(AttesterSlashingValidationError),
|
||||
InvalidBlock(InvalidBlock),
|
||||
ProtoArrayStringError(String),
|
||||
@@ -77,6 +77,8 @@ pub enum Error<T> {
|
||||
},
|
||||
UnrealizedVoteProcessing(state_processing::EpochProcessingError),
|
||||
ValidatorStatuses(BeaconStateError),
|
||||
ChainSpecError(String),
|
||||
DoesNotDescendFromFinalizedCheckpoint,
|
||||
}
|
||||
|
||||
impl<T> From<InvalidAttestation> for Error<T> {
|
||||
@@ -85,6 +87,12 @@ impl<T> From<InvalidAttestation> for Error<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<InvalidPayloadAttestation> for Error<T> {
|
||||
fn from(e: InvalidPayloadAttestation) -> Self {
|
||||
Error::InvalidPayloadAttestation(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<AttesterSlashingValidationError> for Error<T> {
|
||||
fn from(e: AttesterSlashingValidationError) -> Self {
|
||||
Error::InvalidAttesterSlashing(e)
|
||||
@@ -170,6 +178,33 @@ pub enum InvalidAttestation {
|
||||
/// The attestation is attesting to a state that is later than itself. (Viz., attesting to the
|
||||
/// future).
|
||||
AttestsToFutureBlock { block: Slot, attestation: Slot },
|
||||
/// Post-Gloas: attestation index must be 0 or 1.
|
||||
InvalidAttestationIndex { index: u64 },
|
||||
/// A same-slot attestation has a non-zero index, which is invalid post-Gloas.
|
||||
InvalidSameSlotAttestationIndex { slot: Slot },
|
||||
/// Post-Gloas: attestation with index == 1 (payload_present) requires the block's
|
||||
/// payload to have been received (`root in store.payload_states`).
|
||||
PayloadNotReceived { beacon_block_root: Hash256 },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum InvalidPayloadAttestation {
|
||||
/// The payload attestation's attesting indices were empty.
|
||||
EmptyAggregationBitfield,
|
||||
/// The `payload_attestation.data.beacon_block_root` block is unknown.
|
||||
UnknownHeadBlock { beacon_block_root: Hash256 },
|
||||
/// The payload attestation is attesting to a block that is later than itself.
|
||||
AttestsToFutureBlock { block: Slot, attestation: Slot },
|
||||
/// A gossip payload attestation must be for the current slot.
|
||||
PayloadAttestationNotCurrentSlot {
|
||||
attestation_slot: Slot,
|
||||
current_slot: Slot,
|
||||
},
|
||||
/// One or more payload attesters are not part of the PTC.
|
||||
PayloadAttestationAttestersNotInPtc {
|
||||
attesting_indices_len: usize,
|
||||
attesting_indices_in_ptc: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T> From<String> for Error<T> {
|
||||
@@ -241,6 +276,17 @@ pub struct QueuedAttestation {
|
||||
attesting_indices: Vec<u64>,
|
||||
block_root: Hash256,
|
||||
target_epoch: Epoch,
|
||||
/// Per Gloas spec: `payload_present = attestation.data.index == 1`.
|
||||
payload_present: bool,
|
||||
}
|
||||
|
||||
/// Legacy queued attestation without payload_present (pre-Gloas, schema V28).
|
||||
#[derive(Clone, PartialEq, Encode, Decode)]
|
||||
pub struct QueuedAttestationV28 {
|
||||
slot: Slot,
|
||||
attesting_indices: Vec<u64>,
|
||||
block_root: Hash256,
|
||||
target_epoch: Epoch,
|
||||
}
|
||||
|
||||
impl<'a, E: EthSpec> From<IndexedAttestationRef<'a, E>> for QueuedAttestation {
|
||||
@@ -250,6 +296,7 @@ impl<'a, E: EthSpec> From<IndexedAttestationRef<'a, E>> for QueuedAttestation {
|
||||
attesting_indices: a.attesting_indices_to_vec(),
|
||||
block_root: a.data().beacon_block_root,
|
||||
target_epoch: a.data().target.epoch,
|
||||
payload_present: a.data().index == 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,21 +414,32 @@ where
|
||||
AttestationShufflingId::new(anchor_block_root, anchor_state, RelativeEpoch::Next)
|
||||
.map_err(Error::BeaconStateError)?;
|
||||
|
||||
let execution_status = anchor_block.message().execution_payload().map_or_else(
|
||||
// If the block doesn't have an execution payload then it can't have
|
||||
// execution enabled.
|
||||
|_| ExecutionStatus::irrelevant(),
|
||||
|execution_payload| {
|
||||
let (execution_status, execution_payload_parent_hash, execution_payload_block_hash) =
|
||||
if let Ok(signed_bid) = anchor_block.message().body().signed_execution_payload_bid() {
|
||||
// Gloas: execution status is irrelevant post-Gloas; payload validation
|
||||
// is decoupled from beacon blocks.
|
||||
(
|
||||
ExecutionStatus::irrelevant(),
|
||||
Some(signed_bid.message.parent_block_hash),
|
||||
Some(signed_bid.message.block_hash),
|
||||
)
|
||||
} else if let Ok(execution_payload) = anchor_block.message().execution_payload() {
|
||||
// Pre-Gloas forks: do not set payload hashes, they are only used post-Gloas.
|
||||
if execution_payload.is_default_with_empty_roots() {
|
||||
// A default payload does not have execution enabled.
|
||||
ExecutionStatus::irrelevant()
|
||||
(ExecutionStatus::irrelevant(), None, None)
|
||||
} else {
|
||||
// Assume that this payload is valid, since the anchor should be a trusted block and
|
||||
// state.
|
||||
ExecutionStatus::Valid(execution_payload.block_hash())
|
||||
// Assume that this payload is valid, since the anchor should be a
|
||||
// trusted block and state.
|
||||
(
|
||||
ExecutionStatus::Valid(execution_payload.block_hash()),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// Pre-merge: no execution payload at all.
|
||||
(ExecutionStatus::irrelevant(), None, None)
|
||||
};
|
||||
|
||||
// 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());
|
||||
@@ -396,6 +454,10 @@ where
|
||||
next_epoch_shuffling_id,
|
||||
fc_store.unsatisfied_inclusion_list_blocks().clone(),
|
||||
execution_status,
|
||||
execution_payload_parent_hash,
|
||||
execution_payload_block_hash,
|
||||
anchor_block.message().proposer_index(),
|
||||
spec,
|
||||
)?;
|
||||
|
||||
let mut fork_choice = Self {
|
||||
@@ -481,7 +543,7 @@ where
|
||||
&mut self,
|
||||
system_time_current_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Hash256, Error<T::Error>> {
|
||||
) -> Result<(Hash256, PayloadStatus), Error<T::Error>> {
|
||||
// Provide the slot (as per the system clock) to the `fc_store` and then return its view of
|
||||
// the current slot. The `fc_store` will ensure that the `current_slot` is never
|
||||
// decreasing, a property which we must maintain.
|
||||
@@ -489,7 +551,7 @@ where
|
||||
|
||||
let store = &mut self.fc_store;
|
||||
|
||||
let head_root = self.proto_array.find_head::<E>(
|
||||
let (head_root, head_payload_status) = self.proto_array.find_head::<E>(
|
||||
*store.justified_checkpoint(),
|
||||
*store.finalized_checkpoint(),
|
||||
store.justified_balances(),
|
||||
@@ -500,9 +562,22 @@ where
|
||||
)?;
|
||||
|
||||
// Cache some values for the next forkchoiceUpdate call to the execution layer.
|
||||
let head_hash = self
|
||||
.get_block(&head_root)
|
||||
.and_then(|b| b.execution_status.block_hash());
|
||||
// For Gloas blocks, `execution_status` is Irrelevant (no embedded payload).
|
||||
// If the payload envelope was received (Full), use the bid's block_hash as the
|
||||
// execution chain head. Otherwise fall back to the parent hash (Pending) or None.
|
||||
// TODO(gloas): this is a bit messy, and we probably need a similar treatment for
|
||||
// justified/finalized
|
||||
// Can fix as part of: https://github.com/sigp/lighthouse/issues/8957
|
||||
let head_hash = self.get_block(&head_root).and_then(|b| {
|
||||
b.execution_status
|
||||
.block_hash()
|
||||
.or(match head_payload_status {
|
||||
PayloadStatus::Full => b.execution_payload_block_hash,
|
||||
PayloadStatus::Pending | PayloadStatus::Empty => {
|
||||
b.execution_payload_parent_hash
|
||||
}
|
||||
})
|
||||
});
|
||||
let justified_root = self.justified_checkpoint().root;
|
||||
let finalized_root = self.finalized_checkpoint().root;
|
||||
let justified_hash = self
|
||||
@@ -518,7 +593,7 @@ where
|
||||
finalized_hash,
|
||||
};
|
||||
|
||||
Ok(head_root)
|
||||
Ok((head_root, head_payload_status))
|
||||
}
|
||||
|
||||
/// Get the block to build on as proposer, taking into account proposer re-orgs.
|
||||
@@ -613,6 +688,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark a Gloas payload envelope as valid and received.
|
||||
///
|
||||
/// This must only be called for valid Gloas payloads.
|
||||
pub fn on_valid_payload_envelope_received(
|
||||
&mut self,
|
||||
block_root: Hash256,
|
||||
) -> Result<(), Error<T::Error>> {
|
||||
self.proto_array
|
||||
.on_valid_payload_envelope_received(block_root)
|
||||
.map_err(Error::FailedToProcessValidExecutionPayload)
|
||||
}
|
||||
|
||||
/// Pre-Gloas only.
|
||||
///
|
||||
/// See `ProtoArrayForkChoice::process_execution_payload_validation` for documentation.
|
||||
pub fn on_valid_execution_payload(
|
||||
&mut self,
|
||||
@@ -623,6 +712,8 @@ where
|
||||
.map_err(Error::FailedToProcessValidExecutionPayload)
|
||||
}
|
||||
|
||||
/// Pre-Gloas only.
|
||||
///
|
||||
/// See `ProtoArrayForkChoice::process_execution_payload_invalidation` for documentation.
|
||||
pub fn on_invalid_execution_payload(
|
||||
&mut self,
|
||||
@@ -672,6 +763,7 @@ where
|
||||
block_delay: Duration,
|
||||
state: &BeaconState<E>,
|
||||
payload_verification_status: PayloadVerificationStatus,
|
||||
canonical_head_proposer_index: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error<T::Error>> {
|
||||
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_ON_BLOCK_TIMES);
|
||||
@@ -734,12 +826,20 @@ 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);
|
||||
let attestation_threshold = spec.get_attestation_due::<E>(block.slot());
|
||||
|
||||
// Add proposer score boost if the block is the first timely block for this slot and its
|
||||
// proposer matches the expected proposer on the canonical chain (per spec
|
||||
// `update_proposer_boost_root`, introduced in v1.7.0-alpha.5).
|
||||
let is_before_attesting_interval = block_delay < attestation_threshold;
|
||||
|
||||
let is_first_block = self.fc_store.proposer_boost_root().is_zero();
|
||||
if current_slot == block.slot() && is_before_attesting_interval && is_first_block {
|
||||
let is_canonical_proposer = block.proposer_index() == canonical_head_proposer_index;
|
||||
if current_slot == block.slot()
|
||||
&& is_before_attesting_interval
|
||||
&& is_first_block
|
||||
&& is_canonical_proposer
|
||||
{
|
||||
self.fc_store.set_proposer_boost_root(block_root);
|
||||
}
|
||||
|
||||
@@ -888,6 +988,16 @@ where
|
||||
ExecutionStatus::irrelevant()
|
||||
};
|
||||
|
||||
let (execution_payload_parent_hash, execution_payload_block_hash) =
|
||||
if let Ok(signed_bid) = block.body().signed_execution_payload_bid() {
|
||||
(
|
||||
Some(signed_bid.message.parent_block_hash),
|
||||
Some(signed_bid.message.block_hash),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
// 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::<E>(
|
||||
@@ -914,10 +1024,13 @@ where
|
||||
execution_status,
|
||||
unrealized_justified_checkpoint: Some(unrealized_justified_checkpoint),
|
||||
unrealized_finalized_checkpoint: Some(unrealized_finalized_checkpoint),
|
||||
execution_payload_parent_hash,
|
||||
execution_payload_block_hash,
|
||||
proposer_index: Some(block.proposer_index()),
|
||||
},
|
||||
current_slot,
|
||||
self.justified_checkpoint(),
|
||||
self.finalized_checkpoint(),
|
||||
spec,
|
||||
block_delay,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -986,6 +1099,7 @@ where
|
||||
&self,
|
||||
indexed_attestation: IndexedAttestationRef<E>,
|
||||
is_from_block: AttestationFromBlock,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), InvalidAttestation> {
|
||||
// There is no point in processing an attestation with an empty bitfield. Reject
|
||||
// it immediately.
|
||||
@@ -1058,6 +1172,89 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
if spec
|
||||
.fork_name_at_slot::<E>(indexed_attestation.data().slot)
|
||||
.gloas_enabled()
|
||||
{
|
||||
let index = indexed_attestation.data().index;
|
||||
|
||||
// Post-Gloas: attestation index must be 0 or 1.
|
||||
if index > 1 {
|
||||
return Err(InvalidAttestation::InvalidAttestationIndex { index });
|
||||
}
|
||||
|
||||
// Same-slot attestations must have index == 0.
|
||||
if indexed_attestation.data().slot == block.slot && index != 0 {
|
||||
return Err(InvalidAttestation::InvalidSameSlotAttestationIndex {
|
||||
slot: block.slot,
|
||||
});
|
||||
}
|
||||
|
||||
// index == 1 (payload_present) requires the block's payload to have been received.
|
||||
// TODO(gloas): could optimise by adding `payload_received` to `Block`
|
||||
if index == 1
|
||||
&& !self
|
||||
.proto_array
|
||||
.is_payload_received(&indexed_attestation.data().beacon_block_root)
|
||||
{
|
||||
return Err(InvalidAttestation::PayloadNotReceived {
|
||||
beacon_block_root: indexed_attestation.data().beacon_block_root,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates a payload attestation for application to fork choice.
|
||||
fn validate_on_payload_attestation(
|
||||
&self,
|
||||
indexed_payload_attestation: &IndexedPayloadAttestation<E>,
|
||||
is_from_block: AttestationFromBlock,
|
||||
) -> Result<(), InvalidPayloadAttestation> {
|
||||
// This check is from `is_valid_indexed_payload_attestation`, but we do it immediately to
|
||||
// avoid wasting time on junk attestations.
|
||||
if indexed_payload_attestation.attesting_indices.is_empty() {
|
||||
return Err(InvalidPayloadAttestation::EmptyAggregationBitfield);
|
||||
}
|
||||
|
||||
// PTC attestation must be for a known block. If block is unknown, delay consideration until
|
||||
// the block is found (responsibility of caller).
|
||||
let block = self
|
||||
.proto_array
|
||||
.get_block(&indexed_payload_attestation.data.beacon_block_root)
|
||||
.ok_or(InvalidPayloadAttestation::UnknownHeadBlock {
|
||||
beacon_block_root: indexed_payload_attestation.data.beacon_block_root,
|
||||
})?;
|
||||
|
||||
// Not strictly part of the spec, but payload attestations to future slots are MORE INVALID
|
||||
// than payload attestations to blocks at previous slots.
|
||||
if block.slot > indexed_payload_attestation.data.slot {
|
||||
return Err(InvalidPayloadAttestation::AttestsToFutureBlock {
|
||||
block: block.slot,
|
||||
attestation: indexed_payload_attestation.data.slot,
|
||||
});
|
||||
}
|
||||
|
||||
// PTC votes can only change the vote for their assigned beacon block, return early otherwise
|
||||
if block.slot != indexed_payload_attestation.data.slot {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Gossip payload attestations must be for the current slot.
|
||||
// NOTE: signature is assumed to have been verified by caller.
|
||||
// https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/fork-choice.md
|
||||
if matches!(is_from_block, AttestationFromBlock::False)
|
||||
&& indexed_payload_attestation.data.slot != self.fc_store.get_current_slot()
|
||||
{
|
||||
return Err(
|
||||
InvalidPayloadAttestation::PayloadAttestationNotCurrentSlot {
|
||||
attestation_slot: indexed_payload_attestation.data.slot,
|
||||
current_slot: self.fc_store.get_current_slot(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1083,6 +1280,7 @@ where
|
||||
system_time_current_slot: Slot,
|
||||
attestation: IndexedAttestationRef<E>,
|
||||
is_from_block: AttestationFromBlock,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error<T::Error>> {
|
||||
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_ON_ATTESTATION_TIMES);
|
||||
|
||||
@@ -1105,14 +1303,21 @@ where
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.validate_on_attestation(attestation, is_from_block)?;
|
||||
self.validate_on_attestation(attestation, is_from_block, spec)?;
|
||||
|
||||
// Per Gloas spec: `payload_present = attestation.data.index == 1`.
|
||||
let payload_present = spec
|
||||
.fork_name_at_slot::<E>(attestation.data().slot)
|
||||
.gloas_enabled()
|
||||
&& attestation.data().index == 1;
|
||||
|
||||
if attestation.data().slot < self.fc_store.get_current_slot() {
|
||||
for validator_index in attestation.attesting_indices_iter() {
|
||||
self.proto_array.process_attestation(
|
||||
*validator_index as usize,
|
||||
attestation.data().beacon_block_root,
|
||||
attestation.data().target.epoch,
|
||||
attestation.data().slot,
|
||||
payload_present,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
@@ -1129,6 +1334,59 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register a payload attestation with the fork choice DAG.
|
||||
///
|
||||
/// `ptc` is the PTC committee for the attestation's slot: a list of validator indices
|
||||
/// ordered by committee position. Each attesting validator index is resolved to its
|
||||
/// position within `ptc` (its `ptc_index`) before being applied to the proto-array.
|
||||
pub fn on_payload_attestation(
|
||||
&mut self,
|
||||
system_time_current_slot: Slot,
|
||||
attestation: &IndexedPayloadAttestation<E>,
|
||||
is_from_block: AttestationFromBlock,
|
||||
ptc: &[usize],
|
||||
) -> Result<(), Error<T::Error>> {
|
||||
self.update_time(system_time_current_slot)?;
|
||||
|
||||
if attestation.data.beacon_block_root.is_zero() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// TODO(gloas): Should ignore wrong-slot payload attestations at the caller, they could
|
||||
// have been processed at the correct slot when received on gossip, but then have the
|
||||
// wrong-slot by the time they make it to here (TOCTOU).
|
||||
self.validate_on_payload_attestation(attestation, is_from_block)?;
|
||||
|
||||
// Resolve validator indices to PTC committee positions.
|
||||
let ptc_indices: Vec<usize> = attestation
|
||||
.attesting_indices
|
||||
.iter()
|
||||
.filter_map(|validator_index| ptc.iter().position(|&p| p == *validator_index as usize))
|
||||
.collect();
|
||||
|
||||
// Check that all the attesters are in the PTC
|
||||
if ptc_indices.len() != attestation.attesting_indices.len() {
|
||||
return Err(
|
||||
InvalidPayloadAttestation::PayloadAttestationAttestersNotInPtc {
|
||||
attesting_indices_len: attestation.attesting_indices.len(),
|
||||
attesting_indices_in_ptc: ptc_indices.len(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
for &ptc_index in &ptc_indices {
|
||||
self.proto_array.process_payload_attestation(
|
||||
attestation.data.beacon_block_root,
|
||||
ptc_index,
|
||||
attestation.data.payload_present,
|
||||
attestation.data.blob_data_available,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply an attester slashing to fork choice.
|
||||
///
|
||||
/// We assume that the attester slashing provided to this function has already been verified.
|
||||
@@ -1235,7 +1493,8 @@ where
|
||||
self.proto_array.process_attestation(
|
||||
*validator_index as usize,
|
||||
attestation.block_root,
|
||||
attestation.target_epoch,
|
||||
attestation.slot,
|
||||
attestation.payload_present,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@@ -1258,6 +1517,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the proposer should extend the execution payload chain of the given block.
|
||||
pub fn should_extend_payload(&self, block_root: &Hash256) -> Result<bool, Error<T::Error>> {
|
||||
let proposer_boost_root = self.fc_store.proposer_boost_root();
|
||||
self.proto_array
|
||||
.should_extend_payload::<E>(block_root, proposer_boost_root)
|
||||
.map_err(Error::ProtoArrayStringError)
|
||||
}
|
||||
|
||||
/// Returns an `ExecutionStatus` if the block is known **and** a descendant of the finalized root.
|
||||
pub fn get_block_execution_status(&self, block_root: &Hash256) -> Option<ExecutionStatus> {
|
||||
if self.is_finalized_checkpoint_or_descendant(*block_root) {
|
||||
@@ -1267,6 +1534,29 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the canonical payload status of a block. See
|
||||
/// `ProtoArrayForkChoice::get_canonical_payload_status`.
|
||||
pub fn get_canonical_payload_status(
|
||||
&self,
|
||||
block_root: &Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<PayloadStatus, Error<T::Error>> {
|
||||
if self.is_finalized_checkpoint_or_descendant(*block_root) {
|
||||
let current_slot = self.fc_store.get_current_slot();
|
||||
let proposer_boost_root = self.fc_store.proposer_boost_root();
|
||||
self.proto_array
|
||||
.get_canonical_payload_status::<E>(
|
||||
block_root,
|
||||
current_slot,
|
||||
proposer_boost_root,
|
||||
spec,
|
||||
)
|
||||
.map_err(Error::ProtoArrayError)
|
||||
} else {
|
||||
Err(Error::DoesNotDescendFromFinalizedCheckpoint)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the weight for the given block root.
|
||||
pub fn get_block_weight(&self, block_root: &Hash256) -> Option<u64> {
|
||||
self.proto_array.get_weight(block_root)
|
||||
@@ -1365,13 +1655,15 @@ where
|
||||
|
||||
/// Returns the latest message for a given validator, if any.
|
||||
///
|
||||
/// Returns `(block_root, block_slot)`.
|
||||
/// Returns `block_root, block_slot, payload_present`.
|
||||
///
|
||||
/// ## Notes
|
||||
///
|
||||
/// It may be prudent to call `Self::update_time` before calling this function,
|
||||
/// since some attestations might be queued and awaiting processing.
|
||||
pub fn latest_message(&self, validator_index: usize) -> Option<(Hash256, Epoch)> {
|
||||
///
|
||||
/// This function is only used in tests.
|
||||
pub fn latest_message(&self, validator_index: usize) -> Option<LatestMessage> {
|
||||
self.proto_array.latest_message(validator_index)
|
||||
}
|
||||
|
||||
@@ -1416,7 +1708,6 @@ where
|
||||
persisted_proto_array: proto_array::core::SszContainer,
|
||||
justified_balances: JustifiedBalances,
|
||||
reset_payload_statuses: ResetPayloadStatuses,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<ProtoArrayForkChoice, Error<T::Error>> {
|
||||
let mut proto_array = ProtoArrayForkChoice::from_container(
|
||||
persisted_proto_array.clone(),
|
||||
@@ -1441,7 +1732,7 @@ where
|
||||
|
||||
// Reset all blocks back to being "optimistic". This helps recover from an EL consensus
|
||||
// fault where an invalid payload becomes valid.
|
||||
if let Err(e) = proto_array.set_all_blocks_to_optimistic::<E>(spec) {
|
||||
if let Err(e) = proto_array.set_all_blocks_to_optimistic::<E>() {
|
||||
// If there is an error resetting the optimistic status then log loudly and revert
|
||||
// back to a proto-array which does not have the reset applied. This indicates a
|
||||
// significant error in Lighthouse and warrants detailed investigation.
|
||||
@@ -1471,7 +1762,6 @@ where
|
||||
persisted.proto_array,
|
||||
justified_balances,
|
||||
reset_payload_statuses,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
let current_slot = fc_store.get_current_slot();
|
||||
@@ -1479,7 +1769,7 @@ where
|
||||
let mut fork_choice = Self {
|
||||
fc_store,
|
||||
proto_array,
|
||||
queued_attestations: persisted.queued_attestations,
|
||||
queued_attestations: vec![],
|
||||
// Will be updated in the following call to `Self::get_head`.
|
||||
forkchoice_update_parameters: ForkchoiceUpdateParameters {
|
||||
head_hash: None,
|
||||
@@ -1505,7 +1795,7 @@ where
|
||||
// get a different result.
|
||||
fork_choice
|
||||
.proto_array
|
||||
.set_all_blocks_to_optimistic::<E>(spec)?;
|
||||
.set_all_blocks_to_optimistic::<E>()?;
|
||||
// If the second attempt at finding a head fails, return an error since we do not
|
||||
// expect this scenario.
|
||||
fork_choice.get_head(current_slot, spec)?;
|
||||
@@ -1518,10 +1808,7 @@ where
|
||||
/// be instantiated again later.
|
||||
pub fn to_persisted(&self) -> PersistedForkChoice {
|
||||
PersistedForkChoice {
|
||||
proto_array: self
|
||||
.proto_array()
|
||||
.as_ssz_container(self.justified_checkpoint(), self.finalized_checkpoint()),
|
||||
queued_attestations: self.queued_attestations().to_vec(),
|
||||
proto_array: self.proto_array().as_ssz_container(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1535,43 +1822,34 @@ where
|
||||
///
|
||||
/// This is used when persisting the state of the fork choice to disk.
|
||||
#[superstruct(
|
||||
variants(V17, V28),
|
||||
variants(V28, V29),
|
||||
variant_attributes(derive(Encode, Decode, Clone)),
|
||||
no_enum
|
||||
)]
|
||||
pub struct PersistedForkChoice {
|
||||
#[superstruct(only(V17))]
|
||||
pub proto_array_bytes: Vec<u8>,
|
||||
#[superstruct(only(V28))]
|
||||
pub proto_array: proto_array::core::SszContainerV28,
|
||||
pub queued_attestations: Vec<QueuedAttestation>,
|
||||
pub proto_array_v28: proto_array::core::SszContainerV28,
|
||||
#[superstruct(only(V29))]
|
||||
pub proto_array: proto_array::core::SszContainerV29,
|
||||
#[superstruct(only(V28))]
|
||||
pub queued_attestations_v28: Vec<QueuedAttestationV28>,
|
||||
}
|
||||
|
||||
pub type PersistedForkChoice = PersistedForkChoiceV28;
|
||||
pub type PersistedForkChoice = PersistedForkChoiceV29;
|
||||
|
||||
impl TryFrom<PersistedForkChoiceV17> for PersistedForkChoiceV28 {
|
||||
type Error = ssz::DecodeError;
|
||||
|
||||
fn try_from(v17: PersistedForkChoiceV17) -> Result<Self, Self::Error> {
|
||||
let container_v17 =
|
||||
proto_array::core::SszContainerV17::from_ssz_bytes(&v17.proto_array_bytes)?;
|
||||
let container_v28 = container_v17.into();
|
||||
|
||||
Ok(Self {
|
||||
proto_array: container_v28,
|
||||
queued_attestations: v17.queued_attestations,
|
||||
})
|
||||
impl From<PersistedForkChoiceV28> for PersistedForkChoiceV29 {
|
||||
fn from(v28: PersistedForkChoiceV28) -> Self {
|
||||
Self {
|
||||
proto_array: v28.proto_array_v28.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(PersistedForkChoiceV28, JustifiedBalances)> for PersistedForkChoiceV17 {
|
||||
fn from((v28, balances): (PersistedForkChoiceV28, JustifiedBalances)) -> Self {
|
||||
let container_v17 = proto_array::core::SszContainerV17::from((v28.proto_array, balances));
|
||||
let proto_array_bytes = container_v17.as_ssz_bytes();
|
||||
|
||||
impl From<PersistedForkChoiceV29> for PersistedForkChoiceV28 {
|
||||
fn from(v29: PersistedForkChoiceV29) -> Self {
|
||||
Self {
|
||||
proto_array_bytes,
|
||||
queued_attestations: v28.queued_attestations,
|
||||
proto_array_v28: v29.proto_array.into(),
|
||||
queued_attestations_v28: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1611,6 +1889,7 @@ mod tests {
|
||||
attesting_indices: vec![],
|
||||
block_root: Hash256::zero(),
|
||||
target_epoch: Epoch::new(0),
|
||||
payload_present: false,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ mod metrics;
|
||||
|
||||
pub use crate::fork_choice::{
|
||||
AttestationFromBlock, Error, ForkChoice, ForkChoiceView, ForkchoiceUpdateParameters,
|
||||
InvalidAttestation, InvalidBlock, PayloadVerificationStatus, PersistedForkChoice,
|
||||
PersistedForkChoiceV17, PersistedForkChoiceV28, QueuedAttestation, ResetPayloadStatuses,
|
||||
InvalidAttestation, InvalidBlock, InvalidPayloadAttestation, PayloadVerificationStatus,
|
||||
PersistedForkChoice, PersistedForkChoiceV28, PersistedForkChoiceV29, QueuedAttestation,
|
||||
ResetPayloadStatuses,
|
||||
};
|
||||
pub use fork_choice_store::ForkChoiceStore;
|
||||
pub use proto_array::{
|
||||
Block as ProtoBlock, ExecutionStatus, InvalidationOperation, ProposerHeadError,
|
||||
Block as ProtoBlock, ExecutionStatus, InvalidationOperation, PayloadStatus, ProposerHeadError,
|
||||
};
|
||||
|
||||
@@ -7,9 +7,11 @@ use beacon_chain::{
|
||||
BeaconChain, BeaconChainError, BeaconForkChoiceStore, ChainConfig, ForkChoiceError,
|
||||
StateSkipConfig, WhenSlotSkipped,
|
||||
};
|
||||
use bls::AggregateSignature;
|
||||
use fixed_bytes::FixedBytesExtended;
|
||||
use fork_choice::{
|
||||
ForkChoiceStore, InvalidAttestation, InvalidBlock, PayloadVerificationStatus, QueuedAttestation,
|
||||
AttestationFromBlock, ForkChoiceStore, InvalidAttestation, InvalidBlock,
|
||||
InvalidPayloadAttestation, PayloadVerificationStatus, QueuedAttestation,
|
||||
};
|
||||
use state_processing::state_advance::complete_state_advance;
|
||||
use std::fmt;
|
||||
@@ -19,8 +21,8 @@ use store::MemoryStore;
|
||||
use types::SingleAttestation;
|
||||
use types::{
|
||||
BeaconBlockRef, BeaconState, ChainSpec, Checkpoint, Epoch, EthSpec, ForkName, Hash256,
|
||||
IndexedAttestation, MainnetEthSpec, RelativeEpoch, SignedBeaconBlock, Slot, SubnetId,
|
||||
test_utils::generate_deterministic_keypair,
|
||||
IndexedAttestation, IndexedPayloadAttestation, MainnetEthSpec, PayloadAttestationData,
|
||||
RelativeEpoch, SignedBeaconBlock, Slot, SubnetId, test_utils::generate_deterministic_keypair,
|
||||
};
|
||||
|
||||
pub type E = MainnetEthSpec;
|
||||
@@ -71,6 +73,9 @@ impl ForkChoiceTest {
|
||||
Self { harness }
|
||||
}
|
||||
|
||||
/// Creates a new tester with the Gloas fork active at epoch 1.
|
||||
/// Genesis is a standard Fulu block (epoch 0), so block production works normally.
|
||||
/// Tests that need Gloas semantics should advance the chain into epoch 1 first.
|
||||
/// Get a value from the `ForkChoice` instantiation.
|
||||
fn get<T, U>(&self, func: T) -> U
|
||||
where
|
||||
@@ -311,6 +316,7 @@ impl ForkChoiceTest {
|
||||
Duration::from_secs(0),
|
||||
&state,
|
||||
PayloadVerificationStatus::Verified,
|
||||
block.message().proposer_index(),
|
||||
&self.harness.chain.spec,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -354,6 +360,7 @@ impl ForkChoiceTest {
|
||||
Duration::from_secs(0),
|
||||
&state,
|
||||
PayloadVerificationStatus::Verified,
|
||||
block.message().proposer_index(),
|
||||
&self.harness.chain.spec,
|
||||
)
|
||||
.expect_err("on_block did not return an error");
|
||||
@@ -923,6 +930,56 @@ async fn invalid_attestation_future_block() {
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Gossip payload attestations must be for the current slot. A payload attestation for slot S
|
||||
/// received at slot S+1 should be rejected per the spec.
|
||||
#[tokio::test]
|
||||
async fn non_block_payload_attestation_for_previous_slot_is_rejected() {
|
||||
let test = ForkChoiceTest::new()
|
||||
.apply_blocks_without_new_attestations(1)
|
||||
.await;
|
||||
|
||||
let chain = &test.harness.chain;
|
||||
let block_a = chain
|
||||
.block_at_slot(Slot::new(1), WhenSlotSkipped::Prev)
|
||||
.expect("lookup should succeed")
|
||||
.expect("block A should exist");
|
||||
let block_a_root = block_a.canonical_root();
|
||||
let s_plus_1 = block_a.slot().saturating_add(1_u64);
|
||||
|
||||
let payload_attestation = IndexedPayloadAttestation::<E> {
|
||||
attesting_indices: vec![0_u64].try_into().expect("valid attesting indices"),
|
||||
data: PayloadAttestationData {
|
||||
beacon_block_root: block_a_root,
|
||||
slot: Slot::new(1),
|
||||
payload_present: true,
|
||||
blob_data_available: true,
|
||||
},
|
||||
signature: AggregateSignature::empty(),
|
||||
};
|
||||
|
||||
let ptc = &[0_usize];
|
||||
|
||||
let result = chain
|
||||
.canonical_head
|
||||
.fork_choice_write_lock()
|
||||
.on_payload_attestation(
|
||||
s_plus_1,
|
||||
&payload_attestation,
|
||||
AttestationFromBlock::False,
|
||||
ptc,
|
||||
);
|
||||
assert!(
|
||||
matches!(
|
||||
result,
|
||||
Err(ForkChoiceError::InvalidPayloadAttestation(
|
||||
InvalidPayloadAttestation::PayloadAttestationNotCurrentSlot { .. }
|
||||
))
|
||||
),
|
||||
"gossip payload attestation for previous slot should be rejected, got: {:?}",
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
/// Specification v0.12.1:
|
||||
///
|
||||
/// assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot)
|
||||
|
||||
Reference in New Issue
Block a user