intro single_envelope_lookup.rs

This commit is contained in:
Eitan Seri-Levi
2026-04-30 11:31:08 +02:00
parent fe4ad22ac4
commit 666fcbd7c9
5 changed files with 106 additions and 39 deletions

View File

@@ -103,14 +103,18 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
ExecutedEnvelope::AvailabilityPending {
signed_envelope,
import_data,
payload_verification_outcome,
payload_verification_outcome: _,
} => {
self.import_pending_execution_payload_envelope(
signed_envelope,
import_data,
payload_verification_outcome,
)
.await
// The envelope has been executed but data columns have not yet arrived.
// Do not import — return MissingComponents so callers know to fetch columns.
// TODO(gloas): once an envelope DA checker exists, cache the envelope here
// (analogous to `data_availability_checker.put_executed_block`) so that
// import is driven automatically when columns arrive.
let slot = signed_envelope.slot();
let block_root = import_data.block_root;
Ok(AvailabilityProcessingStatus::MissingComponents(
slot, block_root,
))
}
}
};

View File

@@ -4007,8 +4007,11 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
block_root: *block_root,
});
}
Ok(AvailabilityProcessingStatus::MissingComponents(_, _)) => {
// Nothing to do
Ok(AvailabilityProcessingStatus::MissingComponents(_slot, _block_root)) => {
// TODO(gloas): wire this into the envelope DA checker once it exists, analogous to
// how `process_availability` drives block import once blobs/columns arrive. Until
// then gossip envelopes with missing columns will be stuck until columns arrive via
// gossip or engineGetBlobs.
}
Err(e) => match e {
EnvelopeError::ExecutionPayloadError(epe) if !epe.penalize_peer() => {}

View File

@@ -54,6 +54,7 @@ use types::{EthSpec, SignedBeaconBlock};
pub mod common;
pub mod parent_chain;
mod single_block_lookup;
mod single_envelope_lookup;
/// The maximum depth we will search for a parent block. In principle we should have sync'd any
/// canonical chain to its head once the peer connects. A chain should not appear where it's depth
@@ -645,6 +646,31 @@ impl<T: BeaconChainTypes> BlockLookups<T> {
self.on_processing_result_inner::<CustodyRequestState<T::EthSpec>>(id, result, cx)
}
BlockProcessType::SinglePayloadEnvelope { id, block_root } => {
// When envelope processing returns `MissingComponents`, the envelope has been
// executed but data columns are not yet available. Transition the lookup to fetch
// custody columns instead of retrying the envelope or erroring.
if matches!(
&result,
BlockProcessingResult::Ok(
AvailabilityProcessingStatus::MissingComponents { .. }
)
) && let Some(lookup) = self.single_block_lookups.get_mut(&id)
&& lookup.transition_envelope_to_custody()
{
debug!(
?block_root,
"Envelope processed, transitioning to custody column lookup"
);
let lookup_result = lookup.continue_requests(cx);
self.on_lookup_result(
id,
lookup_result,
"envelope_to_custody_transition",
cx,
);
return;
}
let result = self
.on_processing_result_inner::<EnvelopeRequestState<T::EthSpec>>(id, result, cx);
// On successful envelope import, unblock child lookups waiting for this envelope

View File

@@ -78,8 +78,8 @@ pub struct SingleBlockLookup<T: BeaconChainTypes> {
/// than the lifetime of a custody request.
#[educe(Debug(method(fmt_peer_set_as_len)))]
peers: Arc<RwLock<HashSet<PeerId>>>,
block_root: Hash256,
awaiting_parent: Option<AwaitingParent>,
pub(super) block_root: Hash256,
pub(super) awaiting_parent: Option<AwaitingParent>,
created: Instant,
pub(crate) span: Span,
}
@@ -120,21 +120,6 @@ impl<T: BeaconChainTypes> SingleBlockLookup<T> {
}
}
/// Create an envelope-only lookup. The block is already imported, we just need the envelope.
pub fn new_envelope_only(block_root: Hash256, peers: &[PeerId], id: Id) -> Self {
let mut lookup = Self::new(block_root, peers, id, None);
// Block is already imported, mark as completed
lookup
.block_request_state
.state
.on_completed_request("block already imported")
.expect("block state starts as AwaitingDownload");
lookup.component_requests =
ComponentRequests::ActiveEnvelopeRequest(EnvelopeRequestState::new(block_root));
lookup
}
/// Reset the status of all internal requests
pub fn reset_requests(&mut self) {
self.block_request_state = BlockRequestState::new(self.block_root);
match &self.component_requests {
@@ -174,24 +159,11 @@ impl<T: BeaconChainTypes> SingleBlockLookup<T> {
}
}
/// Returns the parent root if awaiting a parent envelope.
pub fn awaiting_parent_envelope(&self) -> Option<Hash256> {
match self.awaiting_parent {
Some(AwaitingParent::Envelope(root)) => Some(root),
_ => None,
}
}
/// Mark this lookup as awaiting a parent block to be imported before processing.
pub fn set_awaiting_parent(&mut self, parent_root: Hash256) {
self.awaiting_parent = Some(AwaitingParent::Block(parent_root));
}
/// Mark this lookup as awaiting a parent envelope to be imported before processing.
pub fn set_awaiting_parent_envelope(&mut self, parent_root: Hash256) {
self.awaiting_parent = Some(AwaitingParent::Envelope(parent_root));
}
/// Mark this lookup as no longer awaiting any parent.
pub fn resolve_awaiting_parent(&mut self) {
self.awaiting_parent = None;

View File

@@ -0,0 +1,62 @@
//! Envelope-specific extensions to `SingleBlockLookup`.
//!
//! Envelope-only lookups are created when a block's parent is known and imported but its
//! execution payload envelope has not yet been received. The block download step is skipped
//! (marked complete immediately), and only the envelope — and possibly subsequent custody
//! columns — are fetched.
use super::single_block_lookup::{
AwaitingParent, ComponentRequests, CustodyRequestState, EnvelopeRequestState, SingleBlockLookup,
};
use beacon_chain::BeaconChainTypes;
use lighthouse_network::PeerId;
use lighthouse_network::service::api_types::Id;
use store::Hash256;
impl<T: BeaconChainTypes> SingleBlockLookup<T> {
/// Create an envelope-only lookup. The block is already imported; only the envelope (and
/// potentially custody columns) need to be fetched.
pub fn new_envelope_only(block_root: Hash256, peers: &[PeerId], id: Id) -> Self {
let mut lookup = Self::new(block_root, peers, id, None);
// Block is already imported — advance past the download step immediately.
lookup
.block_request_state
.state
.on_completed_request("block already imported")
.expect("block state starts as AwaitingDownload");
lookup.component_requests =
ComponentRequests::ActiveEnvelopeRequest(EnvelopeRequestState::new(block_root));
lookup
}
/// Transition from `ActiveEnvelopeRequest` to `ActiveCustodyRequest`.
///
/// Called when envelope processing returns `MissingComponents`: the envelope has been executed
/// but data columns have not yet arrived and must be fetched separately.
/// Returns `true` if the transition was made, `false` if state was not an envelope request.
pub fn transition_envelope_to_custody(&mut self) -> bool {
if matches!(
self.component_requests,
ComponentRequests::ActiveEnvelopeRequest(_)
) {
self.component_requests =
ComponentRequests::ActiveCustodyRequest(CustodyRequestState::new(self.block_root));
true
} else {
false
}
}
/// Returns the parent root if this lookup is awaiting a parent envelope.
pub fn awaiting_parent_envelope(&self) -> Option<Hash256> {
match self.awaiting_parent {
Some(AwaitingParent::Envelope(root)) => Some(root),
_ => None,
}
}
/// Mark this lookup as awaiting a parent envelope before processing can resume.
pub fn set_awaiting_parent_envelope(&mut self, parent_root: Hash256) {
self.awaiting_parent = Some(AwaitingParent::Envelope(parent_root));
}
}