mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-05 05:44:30 +00:00
Reject importing Gloas block until parent's payload is imported (#9382)
Co-Authored-By: dapplion <35266934+dapplion@users.noreply.github.com> Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
@@ -70,7 +70,7 @@ use bls::{PublicKey, PublicKeyBytes};
|
||||
use educe::Educe;
|
||||
use eth2::types::{BlockGossip, EventKind};
|
||||
use execution_layer::PayloadStatus;
|
||||
pub use fork_choice::{AttestationFromBlock, PayloadVerificationStatus};
|
||||
pub use fork_choice::{AttestationFromBlock, ParentImportStatus, PayloadVerificationStatus};
|
||||
use metrics::TryExt;
|
||||
use parking_lot::RwLockReadGuard;
|
||||
use proto_array::Block as ProtoBlock;
|
||||
@@ -870,7 +870,7 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
|
||||
let block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch());
|
||||
let (parent_block, block) =
|
||||
verify_parent_block_is_known::<T>(&fork_choice_read_lock, block)?;
|
||||
verify_parent_block_and_envelope_are_known::<T>(&fork_choice_read_lock, block)?;
|
||||
|
||||
// [New in Gloas]: Verify bid.parent_block_root matches block.parent_root.
|
||||
if let Ok(bid) = block.message().body().signed_execution_payload_bid()
|
||||
@@ -882,13 +882,6 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(gloas) The following validation can only be completed once fork choice has been implemented:
|
||||
// The block's parent execution payload (defined by bid.parent_block_hash) has been seen
|
||||
// (via gossip or non-gossip sources) (a client MAY queue blocks for processing
|
||||
// once the parent payload is retrieved). If execution_payload verification of block's execution
|
||||
// payload parent by an execution node is complete, verify the block's execution payload
|
||||
// parent (defined by bid.parent_block_hash) passes all validation.
|
||||
|
||||
drop(fork_choice_read_lock);
|
||||
|
||||
// Track the number of skip slots between the block and its parent.
|
||||
@@ -1381,33 +1374,24 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
|
||||
.observe_proposal(block_root, block.message())
|
||||
.map_err(|e| BlockError::BeaconChainError(Box::new(e.into())))?;
|
||||
|
||||
if let Some(parent) = chain
|
||||
match chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.get_block(&block.parent_root())
|
||||
.get_parent_import_status(block.as_block())
|
||||
{
|
||||
// Reject any block where the parent has an invalid payload. It's impossible for a valid
|
||||
// block to descend from an invalid parent.
|
||||
ParentImportStatus::Imported(parent) => {
|
||||
if parent.execution_status.is_invalid() {
|
||||
return Err(BlockError::ParentExecutionPayloadInvalid {
|
||||
parent_root: block.parent_root(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Reject any block if its parent is not known to fork choice.
|
||||
//
|
||||
// A block that is not in fork choice is either:
|
||||
//
|
||||
// - Not yet imported: we should reject this block because we should only import a child
|
||||
// after its parent has been fully imported.
|
||||
// - Pre-finalized: if the parent block is _prior_ to finalization, we should ignore it
|
||||
// because it will revert finalization. Note that the finalized block is stored in fork
|
||||
// choice, so we will not reject any child of the finalized block (this is relevant during
|
||||
// genesis).
|
||||
}
|
||||
ParentImportStatus::UnknownBlock | ParentImportStatus::UnknownPayload => {
|
||||
return Err(BlockError::ParentUnknown {
|
||||
parent_root: block.parent_root(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform cursory checks to see if the block is even worth processing.
|
||||
@@ -1862,20 +1846,21 @@ pub fn get_block_header_root(block_header: &SignedBeaconBlockHeader) -> Hash256
|
||||
block_root
|
||||
}
|
||||
|
||||
/// Verify the parent of `block` is known, returning some information about the parent block from
|
||||
/// fork choice.
|
||||
/// Verify the parent block — and, for a post-Gloas FULL child, the parent payload — are known to
|
||||
/// fork choice; both missing cases return `ParentUnknown`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn verify_parent_block_is_known<T: BeaconChainTypes>(
|
||||
fn verify_parent_block_and_envelope_are_known<T: BeaconChainTypes>(
|
||||
fork_choice_read_lock: &RwLockReadGuard<BeaconForkChoice<T>>,
|
||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
) -> Result<(ProtoBlock, Arc<SignedBeaconBlock<T::EthSpec>>), BlockError> {
|
||||
if let Some(proto_block) = fork_choice_read_lock.get_block(&block.parent_root()) {
|
||||
Ok((proto_block, block))
|
||||
} else {
|
||||
match fork_choice_read_lock.get_parent_import_status(&block) {
|
||||
ParentImportStatus::Imported(parent) => Ok((parent, block)),
|
||||
ParentImportStatus::UnknownBlock | ParentImportStatus::UnknownPayload => {
|
||||
Err(BlockError::ParentUnknown {
|
||||
parent_root: block.parent_root(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the parent snapshot (block and state) of the given `block`.
|
||||
@@ -1901,7 +1886,7 @@ fn load_parent<T: BeaconChainTypes, B: AsBlock<T::EthSpec>>(
|
||||
if !chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.contains_block(&block.parent_root())
|
||||
.is_parent_imported(block.as_block())
|
||||
{
|
||||
return Err(BlockError::ParentUnknown {
|
||||
parent_root: block.parent_root(),
|
||||
|
||||
@@ -85,7 +85,7 @@ pub use beacon_fork_choice_store::{
|
||||
};
|
||||
pub use block_verification::{
|
||||
BlockError, ExecutionPayloadError, ExecutionPendingBlock, GossipVerifiedBlock,
|
||||
IntoExecutionPendingBlock, IntoGossipVerifiedBlock, InvalidSignature,
|
||||
IntoExecutionPendingBlock, IntoGossipVerifiedBlock, InvalidSignature, ParentImportStatus,
|
||||
PayloadVerificationOutcome, PayloadVerificationStatus, build_blob_data_column_sidecars,
|
||||
get_block_root, signature_verify_chain_segment,
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ use beacon_chain::{
|
||||
custody_context::NodeCustodyType,
|
||||
test_utils::{
|
||||
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
|
||||
MakeAttestationOptions, test_spec,
|
||||
MakeAttestationOptions, fork_name_from_env, test_spec,
|
||||
},
|
||||
};
|
||||
use beacon_chain::{
|
||||
@@ -359,6 +359,10 @@ fn update_data_column_signed_header<E: EthSpec>(
|
||||
|
||||
#[tokio::test]
|
||||
async fn chain_segment_full_segment() {
|
||||
// TODO(gloas): re-enable for Gloas once range sync imports payload envelopes.
|
||||
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||
return;
|
||||
}
|
||||
let harness = get_harness(VALIDATOR_COUNT, NodeCustodyType::Fullnode);
|
||||
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
||||
store_envelopes_for_chain_segment(&chain_segment, &harness);
|
||||
@@ -399,6 +403,10 @@ async fn chain_segment_full_segment() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn chain_segment_varying_chunk_size() {
|
||||
// TODO(gloas): re-enable for Gloas once range sync imports payload envelopes.
|
||||
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||
return;
|
||||
}
|
||||
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
||||
let harness = get_harness(VALIDATOR_COUNT, NodeCustodyType::Fullnode);
|
||||
let blocks: Vec<RangeSyncBlock<E>> =
|
||||
@@ -679,6 +687,10 @@ async fn get_invalid_sigs_harness(
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn invalid_signature_gossip_block() {
|
||||
// TODO(gloas): re-enable for Gloas once range sync imports payload envelopes.
|
||||
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||
return;
|
||||
}
|
||||
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
||||
for &block_index in BLOCK_INDICES {
|
||||
// Ensure the block will be rejected if imported on its own (without gossip checking).
|
||||
@@ -735,6 +747,10 @@ async fn invalid_signature_gossip_block() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_signature_block_proposal() {
|
||||
// TODO(gloas): re-enable for Gloas once range sync imports payload envelopes.
|
||||
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||
return;
|
||||
}
|
||||
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
||||
for &block_index in BLOCK_INDICES {
|
||||
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
||||
@@ -774,6 +790,10 @@ async fn invalid_signature_block_proposal() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_signature_randao_reveal() {
|
||||
// TODO(gloas): re-enable for Gloas once range sync imports payload envelopes.
|
||||
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||
return;
|
||||
}
|
||||
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
||||
for &block_index in BLOCK_INDICES {
|
||||
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
||||
@@ -802,6 +822,10 @@ async fn invalid_signature_randao_reveal() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_signature_proposer_slashing() {
|
||||
// TODO(gloas): re-enable for Gloas once range sync imports payload envelopes.
|
||||
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||
return;
|
||||
}
|
||||
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
||||
for &block_index in BLOCK_INDICES {
|
||||
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
||||
@@ -844,6 +868,10 @@ async fn invalid_signature_proposer_slashing() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_signature_attester_slashing() {
|
||||
// TODO(gloas): re-enable for Gloas once range sync imports payload envelopes.
|
||||
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||
return;
|
||||
}
|
||||
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
||||
for &block_index in BLOCK_INDICES {
|
||||
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
||||
@@ -965,6 +993,10 @@ async fn invalid_signature_attester_slashing() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_signature_attestation() {
|
||||
// TODO(gloas): re-enable for Gloas once range sync imports payload envelopes.
|
||||
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||
return;
|
||||
}
|
||||
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
||||
let mut checked_attestation = false;
|
||||
|
||||
@@ -1090,6 +1122,10 @@ async fn invalid_signature_deposit() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_signature_exit() {
|
||||
// TODO(gloas): re-enable for Gloas once range sync imports payload envelopes.
|
||||
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||
return;
|
||||
}
|
||||
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
||||
for &block_index in BLOCK_INDICES {
|
||||
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
||||
|
||||
@@ -3148,6 +3148,14 @@ async fn weak_subjectivity_sync_test(
|
||||
.store
|
||||
.put_payload_envelope(&wss_block_root, &envelope)
|
||||
.unwrap();
|
||||
|
||||
// `from_anchor` doesn't mark the anchor's payload received, so do it here; otherwise the
|
||||
// first forward block (a FULL child of the anchor) would be rejected with `ParentUnknown`.
|
||||
beacon_chain
|
||||
.canonical_head
|
||||
.fork_choice_write_lock()
|
||||
.on_valid_payload_envelope_received(wss_block_root)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Apply blocks forward to reach head.
|
||||
|
||||
@@ -207,6 +207,18 @@ pub enum InvalidPayloadAttestation {
|
||||
},
|
||||
}
|
||||
|
||||
/// The import status of a block's parent, as seen by fork choice.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum ParentImportStatus {
|
||||
/// The parent block is imported and the child's bid commits to a parent payload known to fork
|
||||
/// choice.
|
||||
Imported(ProtoBlock),
|
||||
/// The parent block is not known to fork choice.
|
||||
UnknownBlock,
|
||||
/// The parent block is known, but the child's bid commits to a payload not known to fork choice.
|
||||
UnknownPayload,
|
||||
}
|
||||
|
||||
impl<T> From<String> for Error<T> {
|
||||
fn from(e: String) -> Self {
|
||||
Error::ProtoArrayStringError(e)
|
||||
@@ -1537,6 +1549,37 @@ where
|
||||
&& self.is_finalized_checkpoint_or_descendant(*block_root)
|
||||
}
|
||||
|
||||
/// Returns `true` if the block's parent is imported (and, for a post-Gloas FULL child, its
|
||||
/// parent's payload is imported too). See [`Self::get_parent_import_status`].
|
||||
pub fn is_parent_imported(&self, block: &SignedBeaconBlock<E>) -> bool {
|
||||
matches!(
|
||||
self.get_parent_import_status(block),
|
||||
ParentImportStatus::Imported(_)
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the import status of the parent of `block`.
|
||||
///
|
||||
/// A post-Gloas FULL child also requires the parent's payload (committed to by the child's bid)
|
||||
/// to have been received by fork choice.
|
||||
pub fn get_parent_import_status(&self, block: &SignedBeaconBlock<E>) -> ParentImportStatus {
|
||||
if let Some(parent_block) = self.get_block(&block.parent_root()) {
|
||||
let Some(parent_block_hash) = parent_block.execution_payload_block_hash else {
|
||||
// Pre-Gloas parent: payload is embedded in the block, so treat as imported.
|
||||
return ParentImportStatus::Imported(parent_block);
|
||||
};
|
||||
if block.is_parent_block_full(parent_block_hash)
|
||||
&& !self.is_payload_received(&block.parent_root())
|
||||
{
|
||||
ParentImportStatus::UnknownPayload
|
||||
} else {
|
||||
ParentImportStatus::Imported(parent_block)
|
||||
}
|
||||
} else {
|
||||
ParentImportStatus::UnknownBlock
|
||||
}
|
||||
}
|
||||
|
||||
/// Called by the proposer to decide whether to build on the full or empty parent.
|
||||
pub fn should_build_on_full(
|
||||
&self,
|
||||
|
||||
@@ -4,9 +4,9 @@ mod metrics;
|
||||
|
||||
pub use crate::fork_choice::{
|
||||
AttestationFromBlock, Error, ForkChoice, ForkChoiceView, ForkchoiceUpdateParameters,
|
||||
InvalidAttestation, InvalidBlock, InvalidPayloadAttestation, PayloadVerificationStatus,
|
||||
PersistedForkChoice, PersistedForkChoiceV28, PersistedForkChoiceV29, QueuedAttestation,
|
||||
ResetPayloadStatuses,
|
||||
InvalidAttestation, InvalidBlock, InvalidPayloadAttestation, ParentImportStatus,
|
||||
PayloadVerificationStatus, PersistedForkChoice, PersistedForkChoiceV28, PersistedForkChoiceV29,
|
||||
QueuedAttestation, ResetPayloadStatuses,
|
||||
};
|
||||
pub use fork_choice_store::ForkChoiceStore;
|
||||
pub use proto_array::{
|
||||
|
||||
Reference in New Issue
Block a user