mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-03 21:04:28 +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 educe::Educe;
|
||||||
use eth2::types::{BlockGossip, EventKind};
|
use eth2::types::{BlockGossip, EventKind};
|
||||||
use execution_layer::PayloadStatus;
|
use execution_layer::PayloadStatus;
|
||||||
pub use fork_choice::{AttestationFromBlock, PayloadVerificationStatus};
|
pub use fork_choice::{AttestationFromBlock, ParentImportStatus, PayloadVerificationStatus};
|
||||||
use metrics::TryExt;
|
use metrics::TryExt;
|
||||||
use parking_lot::RwLockReadGuard;
|
use parking_lot::RwLockReadGuard;
|
||||||
use proto_array::Block as ProtoBlock;
|
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 block_epoch = block.slot().epoch(T::EthSpec::slots_per_epoch());
|
||||||
let (parent_block, block) =
|
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.
|
// [New in Gloas]: Verify bid.parent_block_root matches block.parent_root.
|
||||||
if let Ok(bid) = block.message().body().signed_execution_payload_bid()
|
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);
|
drop(fork_choice_read_lock);
|
||||||
|
|
||||||
// Track the number of skip slots between the block and its parent.
|
// Track the number of skip slots between the block and its parent.
|
||||||
@@ -1381,32 +1374,23 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
|
|||||||
.observe_proposal(block_root, block.message())
|
.observe_proposal(block_root, block.message())
|
||||||
.map_err(|e| BlockError::BeaconChainError(Box::new(e.into())))?;
|
.map_err(|e| BlockError::BeaconChainError(Box::new(e.into())))?;
|
||||||
|
|
||||||
if let Some(parent) = chain
|
match chain
|
||||||
.canonical_head
|
.canonical_head
|
||||||
.fork_choice_read_lock()
|
.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
|
ParentImportStatus::Imported(parent) => {
|
||||||
// block to descend from an invalid parent.
|
if parent.execution_status.is_invalid() {
|
||||||
if parent.execution_status.is_invalid() {
|
return Err(BlockError::ParentExecutionPayloadInvalid {
|
||||||
return Err(BlockError::ParentExecutionPayloadInvalid {
|
parent_root: block.parent_root(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParentImportStatus::UnknownBlock | ParentImportStatus::UnknownPayload => {
|
||||||
|
return Err(BlockError::ParentUnknown {
|
||||||
parent_root: block.parent_root(),
|
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).
|
|
||||||
return Err(BlockError::ParentUnknown {
|
|
||||||
parent_root: block.parent_root(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1862,19 +1846,20 @@ pub fn get_block_header_root(block_header: &SignedBeaconBlockHeader) -> Hash256
|
|||||||
block_root
|
block_root
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the parent of `block` is known, returning some information about the parent block from
|
/// Verify the parent block — and, for a post-Gloas FULL child, the parent payload — are known to
|
||||||
/// fork choice.
|
/// fork choice; both missing cases return `ParentUnknown`.
|
||||||
#[allow(clippy::type_complexity)]
|
#[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>>,
|
fork_choice_read_lock: &RwLockReadGuard<BeaconForkChoice<T>>,
|
||||||
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||||
) -> Result<(ProtoBlock, Arc<SignedBeaconBlock<T::EthSpec>>), BlockError> {
|
) -> Result<(ProtoBlock, Arc<SignedBeaconBlock<T::EthSpec>>), BlockError> {
|
||||||
if let Some(proto_block) = fork_choice_read_lock.get_block(&block.parent_root()) {
|
match fork_choice_read_lock.get_parent_import_status(&block) {
|
||||||
Ok((proto_block, block))
|
ParentImportStatus::Imported(parent) => Ok((parent, block)),
|
||||||
} else {
|
ParentImportStatus::UnknownBlock | ParentImportStatus::UnknownPayload => {
|
||||||
Err(BlockError::ParentUnknown {
|
Err(BlockError::ParentUnknown {
|
||||||
parent_root: block.parent_root(),
|
parent_root: block.parent_root(),
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1901,7 +1886,7 @@ fn load_parent<T: BeaconChainTypes, B: AsBlock<T::EthSpec>>(
|
|||||||
if !chain
|
if !chain
|
||||||
.canonical_head
|
.canonical_head
|
||||||
.fork_choice_read_lock()
|
.fork_choice_read_lock()
|
||||||
.contains_block(&block.parent_root())
|
.is_parent_imported(block.as_block())
|
||||||
{
|
{
|
||||||
return Err(BlockError::ParentUnknown {
|
return Err(BlockError::ParentUnknown {
|
||||||
parent_root: block.parent_root(),
|
parent_root: block.parent_root(),
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ pub use beacon_fork_choice_store::{
|
|||||||
};
|
};
|
||||||
pub use block_verification::{
|
pub use block_verification::{
|
||||||
BlockError, ExecutionPayloadError, ExecutionPendingBlock, GossipVerifiedBlock,
|
BlockError, ExecutionPayloadError, ExecutionPendingBlock, GossipVerifiedBlock,
|
||||||
IntoExecutionPendingBlock, IntoGossipVerifiedBlock, InvalidSignature,
|
IntoExecutionPendingBlock, IntoGossipVerifiedBlock, InvalidSignature, ParentImportStatus,
|
||||||
PayloadVerificationOutcome, PayloadVerificationStatus, build_blob_data_column_sidecars,
|
PayloadVerificationOutcome, PayloadVerificationStatus, build_blob_data_column_sidecars,
|
||||||
get_block_root, signature_verify_chain_segment,
|
get_block_root, signature_verify_chain_segment,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use beacon_chain::{
|
|||||||
custody_context::NodeCustodyType,
|
custody_context::NodeCustodyType,
|
||||||
test_utils::{
|
test_utils::{
|
||||||
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
|
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
|
||||||
MakeAttestationOptions, test_spec,
|
MakeAttestationOptions, fork_name_from_env, test_spec,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use beacon_chain::{
|
use beacon_chain::{
|
||||||
@@ -359,6 +359,10 @@ fn update_data_column_signed_header<E: EthSpec>(
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn chain_segment_full_segment() {
|
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 harness = get_harness(VALIDATOR_COUNT, NodeCustodyType::Fullnode);
|
||||||
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
||||||
store_envelopes_for_chain_segment(&chain_segment, &harness);
|
store_envelopes_for_chain_segment(&chain_segment, &harness);
|
||||||
@@ -399,6 +403,10 @@ async fn chain_segment_full_segment() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn chain_segment_varying_chunk_size() {
|
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 (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
||||||
let harness = get_harness(VALIDATOR_COUNT, NodeCustodyType::Fullnode);
|
let harness = get_harness(VALIDATOR_COUNT, NodeCustodyType::Fullnode);
|
||||||
let blocks: Vec<RangeSyncBlock<E>> =
|
let blocks: Vec<RangeSyncBlock<E>> =
|
||||||
@@ -679,6 +687,10 @@ async fn get_invalid_sigs_harness(
|
|||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn invalid_signature_gossip_block() {
|
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;
|
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
||||||
for &block_index in BLOCK_INDICES {
|
for &block_index in BLOCK_INDICES {
|
||||||
// Ensure the block will be rejected if imported on its own (without gossip checking).
|
// 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]
|
#[tokio::test]
|
||||||
async fn invalid_signature_block_proposal() {
|
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;
|
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
||||||
for &block_index in BLOCK_INDICES {
|
for &block_index in BLOCK_INDICES {
|
||||||
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
||||||
@@ -774,6 +790,10 @@ async fn invalid_signature_block_proposal() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn invalid_signature_randao_reveal() {
|
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;
|
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
||||||
for &block_index in BLOCK_INDICES {
|
for &block_index in BLOCK_INDICES {
|
||||||
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
||||||
@@ -802,6 +822,10 @@ async fn invalid_signature_randao_reveal() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn invalid_signature_proposer_slashing() {
|
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;
|
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
||||||
for &block_index in BLOCK_INDICES {
|
for &block_index in BLOCK_INDICES {
|
||||||
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
||||||
@@ -844,6 +868,10 @@ async fn invalid_signature_proposer_slashing() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn invalid_signature_attester_slashing() {
|
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;
|
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
||||||
for &block_index in BLOCK_INDICES {
|
for &block_index in BLOCK_INDICES {
|
||||||
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
||||||
@@ -965,6 +993,10 @@ async fn invalid_signature_attester_slashing() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn invalid_signature_attestation() {
|
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 (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
||||||
let mut checked_attestation = false;
|
let mut checked_attestation = false;
|
||||||
|
|
||||||
@@ -1090,6 +1122,10 @@ async fn invalid_signature_deposit() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn invalid_signature_exit() {
|
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;
|
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
||||||
for &block_index in BLOCK_INDICES {
|
for &block_index in BLOCK_INDICES {
|
||||||
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
||||||
|
|||||||
@@ -3148,6 +3148,14 @@ async fn weak_subjectivity_sync_test(
|
|||||||
.store
|
.store
|
||||||
.put_payload_envelope(&wss_block_root, &envelope)
|
.put_payload_envelope(&wss_block_root, &envelope)
|
||||||
.unwrap();
|
.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.
|
// 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> {
|
impl<T> From<String> for Error<T> {
|
||||||
fn from(e: String) -> Self {
|
fn from(e: String) -> Self {
|
||||||
Error::ProtoArrayStringError(e)
|
Error::ProtoArrayStringError(e)
|
||||||
@@ -1537,6 +1549,37 @@ where
|
|||||||
&& self.is_finalized_checkpoint_or_descendant(*block_root)
|
&& 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.
|
/// Called by the proposer to decide whether to build on the full or empty parent.
|
||||||
pub fn should_build_on_full(
|
pub fn should_build_on_full(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ mod metrics;
|
|||||||
|
|
||||||
pub use crate::fork_choice::{
|
pub use crate::fork_choice::{
|
||||||
AttestationFromBlock, Error, ForkChoice, ForkChoiceView, ForkchoiceUpdateParameters,
|
AttestationFromBlock, Error, ForkChoice, ForkChoiceView, ForkchoiceUpdateParameters,
|
||||||
InvalidAttestation, InvalidBlock, InvalidPayloadAttestation, PayloadVerificationStatus,
|
InvalidAttestation, InvalidBlock, InvalidPayloadAttestation, ParentImportStatus,
|
||||||
PersistedForkChoice, PersistedForkChoiceV28, PersistedForkChoiceV29, QueuedAttestation,
|
PayloadVerificationStatus, PersistedForkChoice, PersistedForkChoiceV28, PersistedForkChoiceV29,
|
||||||
ResetPayloadStatuses,
|
QueuedAttestation, ResetPayloadStatuses,
|
||||||
};
|
};
|
||||||
pub use fork_choice_store::ForkChoiceStore;
|
pub use fork_choice_store::ForkChoiceStore;
|
||||||
pub use proto_array::{
|
pub use proto_array::{
|
||||||
|
|||||||
Reference in New Issue
Block a user