block verification changes

This commit is contained in:
Eitan Seri- Levi
2026-02-13 15:00:31 -08:00
parent ebaca3144c
commit e5598d529c
8 changed files with 138 additions and 44 deletions

View File

@@ -57,7 +57,9 @@ use crate::observed_block_producers::ObservedBlockProducers;
use crate::observed_data_sidecars::ObservedDataSidecars;
use crate::observed_operations::{ObservationOutcome, ObservedOperations};
use crate::observed_slashable::ObservedSlashable;
use crate::payload_envelope_verification::{EnvelopeError, ExecutedEnvelope, ExecutionPendingEnvelope};
use crate::payload_envelope_verification::{
EnvelopeError, ExecutedEnvelope, ExecutionPendingEnvelope,
};
use crate::pending_payload_envelopes::PendingPayloadEnvelopes;
use crate::persisted_beacon_chain::PersistedBeaconChain;
use crate::persisted_custody::persist_custody_context;
@@ -3399,11 +3401,19 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
);
}
// GLOAS blocks don't need DA checking - they are always available from the
// block's perspective. Skip inserting into the DA cache.
if !unverified_block
.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.
let _full_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES);

View File

@@ -51,7 +51,9 @@
use crate::beacon_snapshot::PreProcessingSnapshot;
use crate::blob_verification::GossipBlobError;
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::execution_payload::{
AllowOptimisticImport, NotifyExecutionLayer, PayloadNotifier,
@@ -334,6 +336,15 @@ pub enum BlockError {
max_blobs_at_epoch: 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 penalised.
BidParentRootMismatch {
bid_parent_root: Hash256,
block_parent_root: Hash256,
},
}
/// Which specific signature(s) are invalid in a SignedBeaconBlock
@@ -888,7 +899,19 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
// Do not gossip blocks that claim to contain more blobs than the max allowed
// at the given block epoch.
if let Ok(commitments) = block.message().body().blob_kzg_commitments() {
// GLOAS: check bid's commitments; pre-GLOAS: check body's commitments.
if let Ok(bid) = block.message().body().signed_execution_payload_bid() {
let max_blobs_at_epoch = chain
.spec
.max_blobs_per_block(block.slot().epoch(T::EthSpec::slots_per_epoch()))
as usize;
if bid.message.blob_kzg_commitments.len() > max_blobs_at_epoch {
return Err(BlockError::InvalidBlobCount {
max_blobs_at_epoch,
block: bid.message.blob_kzg_commitments.len(),
});
}
} else if let Ok(commitments) = block.message().body().blob_kzg_commitments() {
let max_blobs_at_epoch = chain
.spec
.max_blobs_per_block(block.slot().epoch(T::EthSpec::slots_per_epoch()))
@@ -933,6 +956,32 @@ 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)?;
// GLOAS: Verify bid.parent_block_root matches block.parent_root.
if let Ok(bid) = block.message().body().signed_execution_payload_bid() {
if 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(),
});
}
// GLOAS: Check if the execution payload parent (bid.parent_block_hash) has been
// verified by the EL. If verified and found invalid, reject.
if let Some(beacon_root) = fork_choice_read_lock
.proto_array()
.execution_block_hash_to_beacon_block_root(&bid.message.parent_block_hash)
{
if let Some(parent_payload_block) = fork_choice_read_lock.get_block(&beacon_root) {
if parent_payload_block.execution_status.is_invalid() {
return Err(BlockError::ParentExecutionPayloadInvalid {
parent_root: beacon_root,
});
}
}
}
}
drop(fork_choice_read_lock);
// Track the number of skip slots between the block and its parent.
@@ -1212,15 +1261,32 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
let result = info_span!("signature_verify").in_scope(|| signature_verifier.verify());
match result {
Ok(_) => Ok(Self {
block: MaybeAvailableBlock::AvailabilityPending {
Ok(_) => {
// GLOAS blocks are always "data available" from the block's perspective
// (the execution payload arrives separately via the payload envelope).
let maybe_available = if block.fork_name_unchecked().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,
parent: Some(parent),
consensus_context,
}),
})
}
Err(_) => Err(BlockError::InvalidSignature(
InvalidSignature::BlockBodySignatures,
)),

View File

@@ -62,7 +62,11 @@ impl<T: BeaconChainTypes> PayloadNotifier<T> {
state: &BeaconState<T::EthSpec>,
notify_execution_layer: NotifyExecutionLayer,
) -> 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 carry an execution payload in the block body.
// Execution verification happens via the payload envelope pipeline.
Some(PayloadVerificationStatus::Irrelevant)
} else if is_execution_enabled(state, block.message().body()) {
// Perform the initial stages of payload verification.
//
// We will duplicate these checks again during `per_block_processing`, however these
@@ -304,6 +308,12 @@ pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
block: BeaconBlockRef<'_, T::EthSpec>,
chain: &BeaconChain<T>,
) -> 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.
if let Ok(execution_payload) = block.body().execution_payload() {
// This logic should match `is_execution_enabled`. We use only the execution block hash of

View File

@@ -2,7 +2,10 @@ use std::sync::Arc;
use educe::Educe;
use slot_clock::SlotClock;
use state_processing::{VerifySignatures, envelope_processing::{VerifyStateRoot, process_execution_payload_envelope}};
use state_processing::{
VerifySignatures,
envelope_processing::{VerifyStateRoot, process_execution_payload_envelope},
};
use tracing::{Span, debug};
use types::{
EthSpec, SignedBeaconBlock, SignedExecutionPayloadEnvelope,

View File

@@ -1359,7 +1359,8 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
| Err(e @ BlockError::ParentExecutionPayloadInvalid { .. })
| Err(e @ BlockError::KnownInvalidExecutionPayload(_))
| 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");
self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Reject);
self.gossip_penalize_peer(
@@ -1493,6 +1494,8 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
// 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.
// GLOAS blocks don't carry blobs; the execution payload arrives separately.
if !block.fork_name_unchecked().gloas_enabled() {
let publish_blobs = true;
let self_clone = self.clone();
let block_clone = block.clone();
@@ -1506,6 +1509,7 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
.instrument(current_span),
"fetch_blobs_gossip",
);
}
let result = self
.chain

View File

@@ -643,10 +643,7 @@ where
/// Register that a valid execution payload envelope has been received for `block_root`,
/// updating the node's `payload_status` from PENDING to FULL.
pub fn on_execution_payload(
&mut self,
block_root: Hash256,
) -> Result<(), Error<T::Error>> {
pub fn on_execution_payload(&mut self, block_root: Hash256) -> Result<(), Error<T::Error>> {
self.proto_array
.on_execution_payload(block_root)
.map_err(Error::FailedToProcessValidExecutionPayload)

View File

@@ -383,10 +383,7 @@ impl ProtoArray {
/// Updates the node's `payload_status` from `PENDING` to `FULL`.
///
/// Returns an error if the block is unknown to fork choice.
pub fn on_execution_payload(
&mut self,
block_root: Hash256,
) -> Result<(), Error> {
pub fn on_execution_payload(&mut self, block_root: Hash256) -> Result<(), Error> {
let index = self
.indices
.get(&block_root)

View File

@@ -491,10 +491,7 @@ impl ProtoArrayForkChoice {
/// Register that a valid execution payload envelope has been received for `block_root`.
///
/// See `ProtoArray::on_execution_payload` for documentation.
pub fn on_execution_payload(
&mut self,
block_root: Hash256,
) -> Result<(), String> {
pub fn on_execution_payload(&mut self, block_root: Hash256) -> Result<(), String> {
self.proto_array
.on_execution_payload(block_root)
.map_err(|e| format!("on_execution_payload error: {:?}", e))
@@ -899,6 +896,16 @@ impl ProtoArrayForkChoice {
Some(block.execution_status)
}
/// Returns the first *beacon block root* which contains an execution payload with the given
/// `block_hash`, if any.
pub fn execution_block_hash_to_beacon_block_root(
&self,
block_hash: &ExecutionBlockHash,
) -> Option<Hash256> {
self.proto_array
.execution_block_hash_to_beacon_block_root(block_hash)
}
/// Returns the weight of a given block.
pub fn get_weight(&self, block_root: &Hash256) -> Option<u64> {
let block_index = self.proto_array.indices.get(block_root)?;