From 2a2647411b95a7dd304da93dec8fde27998b1d4d Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Thu, 26 Mar 2026 14:45:49 -0700 Subject: [PATCH] Process envelopes correctly --- beacon_node/beacon_chain/src/beacon_chain.rs | 25 +++- .../beacon_chain/src/block_verification.rs | 50 +++++-- .../src/block_verification_types.rs | 8 +- .../src/data_availability_checker.rs | 16 ++- .../overflow_lru_cache.rs | 2 +- .../beacon_chain/src/early_attester_cache.rs | 2 +- .../beacon_chain/src/historical_blocks.rs | 2 +- .../payload_envelope_verification/import.rs | 136 +++++++++++++++++- .../src/payload_envelope_verification/mod.rs | 4 + .../gossip_methods.rs | 5 +- .../network_beacon_processor/sync_methods.rs | 9 -- 11 files changed, 219 insertions(+), 40 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 3c8ea30779..b378cca4c0 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -2951,12 +2951,13 @@ impl BeaconChain { } }; - // Import the blocks into the chain. - for signature_verified_block in signature_verified_blocks { + // Import the blocks (and envelopes for Gloas) into the chain. + for (signature_verified_block, maybe_envelope) in signature_verified_blocks { + let block_root = signature_verified_block.block_root(); let block_slot = signature_verified_block.slot(); match self .process_block( - signature_verified_block.block_root(), + block_root, signature_verified_block, notify_execution_layer, BlockImportSource::RangeSync, @@ -2969,6 +2970,22 @@ impl BeaconChain { AvailabilityProcessingStatus::Imported(block_root) => { // The block was imported successfully. imported_blocks.push((block_root, block_slot)); + + // Gloas: process the envelope now that the block is in fork choice. + if let Some(envelope) = maybe_envelope + && let Err(error) = self + .process_range_sync_envelope( + block_root, + envelope, + notify_execution_layer, + ) + .await + { + return ChainSegmentResult::Failed { + imported_blocks, + error: BlockError::EnvelopeError(Box::new(error)), + }; + } } AvailabilityProcessingStatus::MissingComponents(slot, block_root) => { warn!( @@ -7219,7 +7236,7 @@ impl BeaconChain { block_data: AvailableBlockData, ) -> Option> { match block_data { - AvailableBlockData::NoData => None, + AvailableBlockData::NoData | AvailableBlockData::DataInEnvelope => None, AvailableBlockData::Blobs(blobs) => { debug!( %block_root, diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index ae9acdefd5..4da2562364 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -60,6 +60,7 @@ use crate::execution_payload::{ }; use crate::kzg_utils::blobs_to_data_column_sidecars; use crate::observed_block_producers::SeenBlock; +use crate::payload_envelope_verification::{AvailableEnvelope, EnvelopeError}; use crate::validator_monitor::HISTORIC_EPOCHS as VALIDATOR_MONITOR_HISTORIC_EPOCHS; use crate::validator_pubkey_cache::ValidatorPubkeyCache; use crate::{ @@ -328,6 +329,8 @@ pub enum BlockError { /// It's unclear if this block is valid, but it cannot be fully verified without the parent's /// execution payload envelope. ParentEnvelopeUnknown { parent_root: Hash256 }, + /// An error occurred while processing the execution payload envelope during range sync. + EnvelopeError(Box), } /// Which specific signature(s) are invalid in a SignedBeaconBlock @@ -591,10 +594,17 @@ pub(crate) fn process_block_slash_info( mut chain_segment: Vec<(Hash256, RangeSyncBlock)>, chain: &BeaconChain, -) -> Result>, BlockError> { +) -> Result< + Vec<( + SignatureVerifiedBlock, + Option>>, + )>, + BlockError, +> { if chain_segment.is_empty() { return Ok(vec![]); } @@ -623,14 +633,30 @@ pub fn signature_verify_chain_segment( let consensus_context = ConsensusContext::new(block.slot()).set_current_block_root(block_root); - let available_block = block.into_available_block(); + let (available_block, envelope) = match block { + RangeSyncBlock::Base(ab) => (ab, None), + RangeSyncBlock::Gloas { block, envelope } => { + let ab = AvailableBlock::new( + block, + AvailableBlockData::DataInEnvelope, + &chain.data_availability_checker, + chain.spec.clone(), + ) + .map_err(BlockError::AvailabilityCheck)?; + (ab, envelope) + } + }; + available_blocks.push(available_block.clone()); - signature_verified_blocks.push(SignatureVerifiedBlock { - block: MaybeAvailableBlock::Available(available_block), - block_root, - parent: None, - consensus_context, - }); + signature_verified_blocks.push(( + SignatureVerifiedBlock { + block: MaybeAvailableBlock::Available(available_block), + block_root, + parent: None, + consensus_context, + }, + envelope, + )); } chain @@ -640,7 +666,7 @@ pub fn signature_verify_chain_segment( // verify signatures let pubkey_cache = get_validator_pubkey_cache(chain)?; let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec); - for svb in &mut signature_verified_blocks { + for (svb, _) in &mut signature_verified_blocks { signature_verifier .include_all_signatures(svb.block.as_block(), &mut svb.consensus_context)?; } @@ -651,7 +677,7 @@ pub fn signature_verify_chain_segment( drop(pubkey_cache); - if let Some(signature_verified_block) = signature_verified_blocks.first_mut() { + if let Some((signature_verified_block, _)) = signature_verified_blocks.first_mut() { signature_verified_block.parent = Some(parent); } @@ -1199,7 +1225,7 @@ impl SignatureVerifiedBlock { let result = info_span!("signature_verify").in_scope(|| signature_verifier.verify()); match result { Ok(_) => { - // gloas blocks are always available. + // Gloas blocks are always available — data arrives via the envelope. let maybe_available = if chain .spec .fork_name_at_slot::(block.slot()) @@ -1208,7 +1234,7 @@ impl SignatureVerifiedBlock { MaybeAvailableBlock::Available( AvailableBlock::new( block, - AvailableBlockData::NoData, + AvailableBlockData::DataInEnvelope, &chain.data_availability_checker, chain.spec.clone(), ) diff --git a/beacon_node/beacon_chain/src/block_verification_types.rs b/beacon_node/beacon_chain/src/block_verification_types.rs index 456e8794e7..e94752cfc0 100644 --- a/beacon_node/beacon_chain/src/block_verification_types.rs +++ b/beacon_node/beacon_chain/src/block_verification_types.rs @@ -139,7 +139,9 @@ impl RangeSyncBlock { pub fn n_blobs(&self) -> usize { match self { Self::Base(block) => match block.data() { - AvailableBlockData::NoData | AvailableBlockData::DataColumns(_) => 0, + AvailableBlockData::NoData + | AvailableBlockData::DataInEnvelope + | AvailableBlockData::DataColumns(_) => 0, AvailableBlockData::Blobs(blobs) => blobs.len(), }, Self::Gloas { .. } => 0, @@ -149,7 +151,9 @@ impl RangeSyncBlock { pub fn n_data_columns(&self) -> usize { match self { Self::Base(block) => match block.data() { - AvailableBlockData::NoData | AvailableBlockData::Blobs(_) => 0, + AvailableBlockData::NoData + | AvailableBlockData::DataInEnvelope + | AvailableBlockData::Blobs(_) => 0, AvailableBlockData::DataColumns(columns) => columns.len(), }, Self::Gloas { .. } => 0, diff --git a/beacon_node/beacon_chain/src/data_availability_checker.rs b/beacon_node/beacon_chain/src/data_availability_checker.rs index 1eb59b9b7b..87c240e906 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker.rs @@ -366,7 +366,7 @@ impl DataAvailabilityChecker { available_block: &AvailableBlock, ) -> Result<(), AvailabilityCheckError> { match available_block.data() { - AvailableBlockData::NoData => Ok(()), + AvailableBlockData::NoData | AvailableBlockData::DataInEnvelope => Ok(()), AvailableBlockData::Blobs(blobs) => verify_kzg_for_blob_list(blobs.iter(), &self.kzg) .map_err(AvailabilityCheckError::InvalidBlobs), AvailableBlockData::DataColumns(columns) => { @@ -388,7 +388,7 @@ impl DataAvailabilityChecker { for available_block in available_blocks { match available_block.data().to_owned() { - AvailableBlockData::NoData => {} + AvailableBlockData::NoData | AvailableBlockData::DataInEnvelope => {} AvailableBlockData::Blobs(blobs) => all_blobs.extend(blobs), AvailableBlockData::DataColumns(columns) => all_data_columns.extend(columns), } @@ -655,6 +655,9 @@ pub enum AvailableBlockData { Blobs(BlobSidecarList), /// Block is post-PeerDAS and has more than zero blobs DataColumns(DataColumnSidecarList), + /// Gloas: block data (payload + columns) arrives via the execution payload envelope, + /// not the block itself. + DataInEnvelope, } impl AvailableBlockData { @@ -676,7 +679,7 @@ impl AvailableBlockData { pub fn blobs(&self) -> Option> { match self { - AvailableBlockData::NoData => None, + AvailableBlockData::NoData | AvailableBlockData::DataInEnvelope => None, AvailableBlockData::Blobs(blobs) => Some(blobs.clone()), AvailableBlockData::DataColumns(_) => None, } @@ -692,7 +695,7 @@ impl AvailableBlockData { pub fn data_columns(&self) -> Option> { match self { - AvailableBlockData::NoData => None, + AvailableBlockData::NoData | AvailableBlockData::DataInEnvelope => None, AvailableBlockData::Blobs(_) => None, AvailableBlockData::DataColumns(data_columns) => Some(data_columns.clone()), } @@ -757,6 +760,8 @@ impl AvailableBlock { return Err(AvailabilityCheckError::MissingBlobs); } } + // Gloas: data availability is handled by the envelope path, not the block. + AvailableBlockData::DataInEnvelope => {} AvailableBlockData::Blobs(blobs) => { if !blobs_required { return Err(AvailabilityCheckError::InvalidAvailableBlockData); @@ -835,7 +840,7 @@ impl AvailableBlock { pub fn has_blobs(&self) -> bool { match self.blob_data { - AvailableBlockData::NoData => false, + AvailableBlockData::NoData | AvailableBlockData::DataInEnvelope => false, AvailableBlockData::Blobs(..) => true, AvailableBlockData::DataColumns(_) => false, } @@ -863,6 +868,7 @@ impl AvailableBlock { AvailableBlockData::DataColumns(data_columns) => { AvailableBlockData::DataColumns(data_columns.clone()) } + AvailableBlockData::DataInEnvelope => AvailableBlockData::DataInEnvelope, }, blobs_available_timestamp: self.blobs_available_timestamp, spec: self.spec.clone(), diff --git a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs index c0403595ee..f26e1d7ad4 100644 --- a/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs +++ b/beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs @@ -275,7 +275,7 @@ impl PendingComponents { // Block is available, construct `AvailableExecutedBlock` let blobs_available_timestamp = match blob_data { - AvailableBlockData::NoData => None, + AvailableBlockData::NoData | AvailableBlockData::DataInEnvelope => None, AvailableBlockData::Blobs(_) => self .verified_blobs .iter() diff --git a/beacon_node/beacon_chain/src/early_attester_cache.rs b/beacon_node/beacon_chain/src/early_attester_cache.rs index 752e4d1a96..644d60f238 100644 --- a/beacon_node/beacon_chain/src/early_attester_cache.rs +++ b/beacon_node/beacon_chain/src/early_attester_cache.rs @@ -138,7 +138,7 @@ impl EarlyAttesterCache { }; let (blobs, data_columns) = match block.data() { - AvailableBlockData::NoData => (None, None), + AvailableBlockData::NoData | AvailableBlockData::DataInEnvelope => (None, None), AvailableBlockData::Blobs(blobs) => (Some(blobs.clone()), None), AvailableBlockData::DataColumns(data_columns) => (None, Some(data_columns.clone())), }; diff --git a/beacon_node/beacon_chain/src/historical_blocks.rs b/beacon_node/beacon_chain/src/historical_blocks.rs index bfda52558e..ebb1bc4b76 100644 --- a/beacon_node/beacon_chain/src/historical_blocks.rs +++ b/beacon_node/beacon_chain/src/historical_blocks.rs @@ -157,7 +157,7 @@ impl BeaconChain { } match &block_data { - AvailableBlockData::NoData => (), + AvailableBlockData::NoData | AvailableBlockData::DataInEnvelope => (), AvailableBlockData::Blobs(_) => new_oldest_blob_slot = Some(block.slot()), AvailableBlockData::DataColumns(_) => { new_oldest_data_column_slot = Some(block.slot()) diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs index 85332a12ea..d07b7cde73 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs @@ -6,16 +6,25 @@ use fork_choice::PayloadVerificationStatus; use slot_clock::SlotClock; use store::StoreOp; use tracing::{debug, error, info, info_span, instrument, warn}; -use types::{BeaconState, BlockImportSource, Hash256, Slot}; +use types::{BeaconState, BlockImportSource, EthSpec, Hash256, Slot}; + +use state_processing::{ + VerifySignatures, + envelope_processing::{VerifyStateRoot, process_execution_payload_envelope}, +}; +use store::DatabaseBlock; use super::{ AvailableEnvelope, AvailableExecutedEnvelope, EnvelopeError, EnvelopeImportData, - ExecutedEnvelope, gossip_verified_envelope::GossipVerifiedEnvelope, + ExecutedEnvelope, MaybeAvailableEnvelope, gossip_verified_envelope::GossipVerifiedEnvelope, + gossip_verified_envelope::verify_envelope_consistency, load_snapshot_from_state_root, + payload_notifier::PayloadNotifier, }; use crate::{ AvailabilityProcessingStatus, BeaconChain, BeaconChainError, BeaconChainTypes, - NotifyExecutionLayer, block_verification_types::AvailableBlockData, metrics, - payload_envelope_verification::ExecutionPendingEnvelope, validator_monitor::get_slot_delay_ms, + NotifyExecutionLayer, PayloadVerificationOutcome, block_verification_types::AvailableBlockData, + metrics, payload_envelope_verification::ExecutionPendingEnvelope, + validator_monitor::get_slot_delay_ms, }; const ENVELOPE_METRICS_CACHE_SLOT_LIMIT: u32 = 64; @@ -148,6 +157,125 @@ impl BeaconChain { } } + /// Process an `AvailableEnvelope` from range sync. Unlike the gossip path, the block has + /// already been imported into fork choice so we can skip gossip-specific checks. + /// + /// Steps: consistency checks, signature verification, EL verification (newPayload), + /// state processing, await EL result, and import. + #[instrument(skip_all, fields(block_root = ?block_root))] + pub async fn process_range_sync_envelope( + self: &Arc, + block_root: Hash256, + available_envelope: Box>, + notify_execution_layer: NotifyExecutionLayer, + ) -> Result { + let signed_envelope = available_envelope.envelope().clone(); + let block_slot = signed_envelope.slot(); + + // Load the block from store (just imported, guaranteed to exist). + let block = match self.store.try_get_full_block(&block_root)? { + Some(DatabaseBlock::Full(block)) => Arc::new(block), + Some(DatabaseBlock::Blinded(_)) | None => { + return Err(EnvelopeError::BlockRootUnknown { block_root }); + } + }; + + // Envelope consistency checks. + let execution_bid = &block + .message() + .body() + .signed_execution_payload_bid()? + .message; + let latest_finalized_slot = self + .canonical_head + .cached_head() + .finalized_checkpoint() + .epoch + .start_slot(T::EthSpec::slots_per_epoch()); + verify_envelope_consistency( + &signed_envelope.message, + &block, + execution_bid, + latest_finalized_slot, + )?; + + // Load state for signature verification and state processing. + let snapshot = + load_snapshot_from_state_root::(block_root, block.state_root(), &self.store)?; + + // Verify the envelope signature. + let is_valid = + signed_envelope.verify_signature_with_state(&snapshot.pre_state, &self.spec)?; + if !is_valid { + return Err(EnvelopeError::BadSignature); + } + + // Start EL verification (newPayload) as early as possible. + let payload_notifier = PayloadNotifier::new( + self.clone(), + signed_envelope.clone(), + block.clone(), + notify_execution_layer, + )?; + let payload_verification_future = async move { + let chain = payload_notifier.chain.clone(); + if let Some(started_execution) = chain.slot_clock.now_duration() { + chain + .envelope_times_cache + .write() + .set_time_started_execution(block_root, block_slot, started_execution); + } + let payload_verification_status = payload_notifier.notify_new_payload().await?; + Ok(PayloadVerificationOutcome { + payload_verification_status, + }) + }; + let payload_verification_handle = self + .task_executor + .spawn_handle( + payload_verification_future, + "range_sync_envelope_payload_verification", + ) + .ok_or(BeaconChainError::RuntimeShutdown)?; + + // Run state processing (signatures already verified above). + let mut state = snapshot.pre_state; + process_execution_payload_envelope( + &mut state, + Some(snapshot.state_root), + &signed_envelope, + VerifySignatures::False, + VerifyStateRoot::True, + &self.spec, + )?; + + // Build the ExecutionPendingEnvelope with Available status (columns already bundled). + let execution_pending = ExecutionPendingEnvelope { + signed_envelope: MaybeAvailableEnvelope::Available(*available_envelope), + import_data: EnvelopeImportData { + block_root, + post_state: Box::new(state), + }, + payload_verification_handle, + }; + + // Await EL verification and import. + let executed_envelope = self + .clone() + .into_executed_payload_envelope(execution_pending) + .await?; + + match executed_envelope { + ExecutedEnvelope::Available(envelope) => { + self.import_available_execution_payload_envelope(Box::new(envelope)) + .await + } + ExecutedEnvelope::AvailabilityPending() => Err(EnvelopeError::InternalError( + "Pending payload envelope not yet implemented".to_owned(), + )), + } + } + /// Accepts a fully-verified payload envelope and awaits on its payload verification handle to /// get a fully `ExecutedEnvelope`. /// diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs index b4628ae2d0..1e7d00fce3 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs @@ -75,6 +75,10 @@ impl AvailableEnvelope { } } + pub fn envelope(&self) -> &Arc> { + &self.envelope + } + pub fn message(&self) -> &ExecutionPayloadEnvelope { &self.envelope.message } diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 2e04847630..8de90f991b 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -1390,7 +1390,10 @@ impl NetworkBeaconProcessor { return None; } // BlobNotRequired is unreachable. Only constructed in `process_gossip_blob` - Err(e @ BlockError::InternalError(_)) | Err(e @ BlockError::BlobNotRequired(_)) => { + // EnvelopeError is unreachable. Only constructed during range sync envelope processing. + Err(e @ BlockError::InternalError(_)) + | Err(e @ BlockError::BlobNotRequired(_)) + | Err(e @ BlockError::EnvelopeError(_)) => { error!(error = %e, "Internal block gossip validation error"); return None; } diff --git a/beacon_node/network/src/network_beacon_processor/sync_methods.rs b/beacon_node/network/src/network_beacon_processor/sync_methods.rs index f14816139c..f6d4940121 100644 --- a/beacon_node/network/src/network_beacon_processor/sync_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/sync_methods.rs @@ -619,15 +619,6 @@ impl NetworkBeaconProcessor { return; }; - // TODO(gloas): Implement Gloas chain segment processing. - // Gloas blocks carry separate envelopes and need a different import path. - if downloaded_blocks - .iter() - .any(|b| matches!(b, RangeSyncBlock::Gloas { .. })) - { - todo!("Gloas chain segment processing"); - } - let start_slot = downloaded_blocks.first().map(|b| b.slot().as_u64()); let end_slot = downloaded_blocks.last().map(|b| b.slot().as_u64()); let sent_blocks = downloaded_blocks.len();