Implement gloas block gossip verification changes (#8878)

Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>

Co-Authored-By: Eitan Seri- Levi <eserilev@gmail.com>

Co-Authored-By: Michael Sproul <michaelsproul@users.noreply.github.com>
This commit is contained in:
Eitan Seri-Levi
2026-02-22 22:17:24 -08:00
committed by GitHub
parent 2b214175d5
commit dcc43e3d20
5 changed files with 132 additions and 34 deletions

View File

@@ -3378,11 +3378,19 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
); );
} }
self.data_availability_checker.put_pre_execution_block( // Gloas blocks dont need to be inserted into the DA cache
block_root, // they are always available.
unverified_block.block_cloned(), if !unverified_block
block_source, .block()
)?; .fork_name_unchecked()
.gloas_enabled()
{
self.data_availability_checker.put_pre_execution_block(
block_root,
unverified_block.block_cloned(),
block_source,
)?;
}
// Start the Prometheus timer. // Start the Prometheus timer.
let _full_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES); let _full_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES);

View File

@@ -51,7 +51,9 @@
use crate::beacon_snapshot::PreProcessingSnapshot; use crate::beacon_snapshot::PreProcessingSnapshot;
use crate::blob_verification::GossipBlobError; use crate::blob_verification::GossipBlobError;
use crate::block_verification_types::{AsBlock, BlockImportData, RpcBlock}; use crate::block_verification_types::{AsBlock, BlockImportData, RpcBlock};
use crate::data_availability_checker::{AvailabilityCheckError, MaybeAvailableBlock}; use crate::data_availability_checker::{
AvailabilityCheckError, AvailableBlock, AvailableBlockData, MaybeAvailableBlock,
};
use crate::data_column_verification::GossipDataColumnError; use crate::data_column_verification::GossipDataColumnError;
use crate::execution_payload::{ use crate::execution_payload::{
AllowOptimisticImport, NotifyExecutionLayer, PayloadNotifier, AllowOptimisticImport, NotifyExecutionLayer, PayloadNotifier,
@@ -334,6 +336,15 @@ pub enum BlockError {
max_blobs_at_epoch: usize, max_blobs_at_epoch: usize,
block: usize, block: usize,
}, },
/// The bid's parent_block_root does not match the block's parent_root.
///
/// ## Peer scoring
///
/// The block is invalid and the peer should be penalized.
BidParentRootMismatch {
bid_parent_root: Hash256,
block_parent_root: Hash256,
},
} }
/// Which specific signature(s) are invalid in a SignedBeaconBlock /// Which specific signature(s) are invalid in a SignedBeaconBlock
@@ -887,15 +898,15 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
// Do not gossip blocks that claim to contain more blobs than the max allowed // Do not gossip blocks that claim to contain more blobs than the max allowed
// at the given block epoch. // at the given block epoch.
if let Ok(commitments) = block.message().body().blob_kzg_commitments() { if let Some(blob_kzg_commitments_len) = block.message().blob_kzg_commitments_len() {
let max_blobs_at_epoch = chain let max_blobs_at_epoch = chain
.spec .spec
.max_blobs_per_block(block.slot().epoch(T::EthSpec::slots_per_epoch())) .max_blobs_per_block(block.slot().epoch(T::EthSpec::slots_per_epoch()))
as usize; as usize;
if commitments.len() > max_blobs_at_epoch { if blob_kzg_commitments_len > max_blobs_at_epoch {
return Err(BlockError::InvalidBlobCount { return Err(BlockError::InvalidBlobCount {
max_blobs_at_epoch, max_blobs_at_epoch,
block: commitments.len(), block: blob_kzg_commitments_len,
}); });
} }
} }
@@ -932,6 +943,24 @@ 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_is_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()
&& bid.message.parent_block_root != block.message().parent_root()
{
return Err(BlockError::BidParentRootMismatch {
bid_parent_root: bid.message.parent_block_root,
block_parent_root: block.message().parent_root(),
});
}
// 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.
@@ -1038,8 +1067,15 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
}); });
} }
// Validate the block's execution_payload (if any). // [New in Gloas]: Skip payload validation checks. The payload now arrives separately
validate_execution_payload_for_gossip(&parent_block, block.message(), chain)?; // via `ExecutionPayloadEnvelope`.
if !chain
.spec
.fork_name_at_slot::<T::EthSpec>(block.slot())
.gloas_enabled()
{
validate_execution_payload_for_gossip(&parent_block, block.message(), chain)?;
}
// Beacon API block_gossip events // Beacon API block_gossip events
if let Some(event_handler) = chain.event_handler.as_ref() if let Some(event_handler) = chain.event_handler.as_ref()
@@ -1211,15 +1247,35 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
let result = info_span!("signature_verify").in_scope(|| signature_verifier.verify()); let result = info_span!("signature_verify").in_scope(|| signature_verifier.verify());
match result { match result {
Ok(_) => Ok(Self { Ok(_) => {
block: MaybeAvailableBlock::AvailabilityPending { // gloas blocks are always available.
let maybe_available = if chain
.spec
.fork_name_at_slot::<T::EthSpec>(block.slot())
.gloas_enabled()
{
MaybeAvailableBlock::Available(
AvailableBlock::new(
block,
AvailableBlockData::NoData,
&chain.data_availability_checker,
chain.spec.clone(),
)
.map_err(BlockError::AvailabilityCheck)?,
)
} else {
MaybeAvailableBlock::AvailabilityPending {
block_root: from.block_root,
block,
}
};
Ok(Self {
block: maybe_available,
block_root: from.block_root, block_root: from.block_root,
block, parent: Some(parent),
}, consensus_context,
block_root: from.block_root, })
parent: Some(parent), }
consensus_context,
}),
Err(_) => Err(BlockError::InvalidSignature( Err(_) => Err(BlockError::InvalidSignature(
InvalidSignature::BlockBodySignatures, InvalidSignature::BlockBodySignatures,
)), )),

View File

@@ -62,7 +62,10 @@ impl<T: BeaconChainTypes> PayloadNotifier<T> {
state: &BeaconState<T::EthSpec>, state: &BeaconState<T::EthSpec>,
notify_execution_layer: NotifyExecutionLayer, notify_execution_layer: NotifyExecutionLayer,
) -> Result<Self, BlockError> { ) -> Result<Self, BlockError> {
let payload_verification_status = if is_execution_enabled(state, block.message().body()) { let payload_verification_status = if block.fork_name_unchecked().gloas_enabled() {
// Gloas blocks don't contain an execution payload.
Some(PayloadVerificationStatus::Irrelevant)
} else if is_execution_enabled(state, block.message().body()) {
// Perform the initial stages of payload verification. // Perform the initial stages of payload verification.
// //
// We will duplicate these checks again during `per_block_processing`, however these // We will duplicate these checks again during `per_block_processing`, however these
@@ -294,6 +297,12 @@ pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
block: BeaconBlockRef<'_, T::EthSpec>, block: BeaconBlockRef<'_, T::EthSpec>,
chain: &BeaconChain<T>, chain: &BeaconChain<T>,
) -> Result<(), BlockError> { ) -> Result<(), BlockError> {
// Gloas blocks don't have an execution payload in the block body.
// Bid-related validations are handled in gossip block verification.
if block.fork_name_unchecked().gloas_enabled() {
return Ok(());
}
// Only apply this validation if this is a Bellatrix beacon block. // Only apply this validation if this is a Bellatrix beacon block.
if let Ok(execution_payload) = block.body().execution_payload() { if let Ok(execution_payload) = block.body().execution_payload() {
// This logic should match `is_execution_enabled`. We use only the execution block hash of // This logic should match `is_execution_enabled`. We use only the execution block hash of

View File

@@ -1356,7 +1356,8 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
| Err(e @ BlockError::ParentExecutionPayloadInvalid { .. }) | Err(e @ BlockError::ParentExecutionPayloadInvalid { .. })
| Err(e @ BlockError::KnownInvalidExecutionPayload(_)) | Err(e @ BlockError::KnownInvalidExecutionPayload(_))
| Err(e @ BlockError::GenesisBlock) | Err(e @ BlockError::GenesisBlock)
| Err(e @ BlockError::InvalidBlobCount { .. }) => { | Err(e @ BlockError::InvalidBlobCount { .. })
| Err(e @ BlockError::BidParentRootMismatch { .. }) => {
warn!(error = %e, "Could not verify block for gossip. Rejecting the block"); warn!(error = %e, "Could not verify block for gossip. Rejecting the block");
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
self.gossip_penalize_peer( self.gossip_penalize_peer(
@@ -1490,19 +1491,23 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
// Block is gossip valid. Attempt to fetch blobs from the EL using versioned hashes derived // Block is gossip valid. Attempt to fetch blobs from the EL using versioned hashes derived
// from kzg commitments, without having to wait for all blobs to be sent from the peers. // from kzg commitments, without having to wait for all blobs to be sent from the peers.
let publish_blobs = true; // TODO(gloas) we'll want to use this same optimization, but we need to refactor the
let self_clone = self.clone(); // `fetch_and_process_engine_blobs` flow to support gloas.
let block_clone = block.clone(); if !block.fork_name_unchecked().gloas_enabled() {
let current_span = Span::current(); let publish_blobs = true;
self.executor.spawn( let self_clone = self.clone();
async move { let block_clone = block.clone();
self_clone let current_span = Span::current();
.fetch_engine_blobs_and_publish(block_clone, block_root, publish_blobs) self.executor.spawn(
.await async move {
} self_clone
.instrument(current_span), .fetch_engine_blobs_and_publish(block_clone, block_root, publish_blobs)
"fetch_blobs_gossip", .await
); }
.instrument(current_span),
"fetch_blobs_gossip",
);
}
let result = self let result = self
.chain .chain

View File

@@ -309,6 +309,26 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockRef<'a, E, Payl
pub fn execution_payload(&self) -> Result<Payload::Ref<'a>, BeaconStateError> { pub fn execution_payload(&self) -> Result<Payload::Ref<'a>, BeaconStateError> {
self.body().execution_payload() self.body().execution_payload()
} }
pub fn blob_kzg_commitments_len(&self) -> Option<usize> {
match self {
BeaconBlockRef::Base(_) => None,
BeaconBlockRef::Altair(_) => None,
BeaconBlockRef::Bellatrix(_) => None,
BeaconBlockRef::Capella(_) => None,
BeaconBlockRef::Deneb(block) => Some(block.body.blob_kzg_commitments.len()),
BeaconBlockRef::Electra(block) => Some(block.body.blob_kzg_commitments.len()),
BeaconBlockRef::Fulu(block) => Some(block.body.blob_kzg_commitments.len()),
BeaconBlockRef::Gloas(block) => Some(
block
.body
.signed_execution_payload_bid
.message
.blob_kzg_commitments
.len(),
),
}
}
} }
impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockRefMut<'a, E, Payload> { impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockRefMut<'a, E, Payload> {