Gloas lookup sync

Rewrites the single block lookup state machine for Gloas, where block, data
(blobs/columns), and execution payload envelope are independent components
that can arrive and import out of order.

- Three additive-only sub-state-machines for block / data / payload streams.
  Peer sets start empty for data/payload and grow as children arrive — the
  parent lookup's completion requirement can widen over time without
  mutating any state machine.
- `AwaitingParent` becomes a struct carrying the child's `parent_block_hash`
  so the parent can be classified empty/full from the child's bid reference.
- Wires `PayloadEnvelopesByRoot` RPC end-to-end through `SyncNetworkContext`:
  request sending, response routing (`SingleLookupReqId::SinglePayloadEnvelope`),
  and integration into `PayloadRequest`. Envelope *processing* is still a TODO;
  only the download path is wired.
- Test rig: serves envelopes from a `network_envelopes_by_root` cache
  populated from the external harness; bumps test validator count to 8 so
  `proposer_lookahead` can populate at the Fulu → Gloas upgrade.
- Enables gloas in `TEST_NETWORK_FORKS`.
- Fixes: genesis parent check, infinite retry loop on repeated download
  failure, no-op in `on_completed_request`, and peer sets not being cleared
  on disconnect.
This commit is contained in:
dapplion
2026-04-22 00:37:14 +02:00
parent 7731b5f250
commit ebe9fe228a
14 changed files with 1939 additions and 931 deletions

View File

@@ -43,9 +43,7 @@ use super::range_sync::{EPOCHS_PER_BATCH, RangeSync, RangeSyncType};
use crate::network_beacon_processor::{ChainSegmentProcessId, NetworkBeaconProcessor};
use crate::service::NetworkMessage;
use crate::status::ToStatusMessage;
use crate::sync::block_lookups::{
BlobRequestState, BlockComponent, BlockRequestState, CustodyRequestState, DownloadResult,
};
use crate::sync::block_lookups::{BlockComponent, DownloadResult};
use crate::sync::custody_backfill_sync::CustodyBackFillSync;
use crate::sync::network_context::{PeerGroup, RpcResponseResult};
use beacon_chain::block_verification_types::AsBlock;
@@ -73,7 +71,8 @@ use strum::IntoStaticStr;
use tokio::sync::mpsc;
use tracing::{debug, error, info, trace};
use types::{
BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, Hash256, SignedBeaconBlock, Slot,
BlobSidecar, DataColumnSidecar, EthSpec, ForkContext, Hash256, SignedBeaconBlock,
SignedExecutionPayloadEnvelope, Slot,
};
/// The number of slots ahead of us that is allowed before requesting a long-range (batch) Sync
@@ -132,6 +131,14 @@ pub enum SyncMessage<E: EthSpec> {
seen_timestamp: Duration,
},
/// A payload envelope has been received from the RPC.
RpcPayloadEnvelope {
sync_request_id: SyncRequestId,
peer_id: PeerId,
envelope: Option<Arc<SignedExecutionPayloadEnvelope<E>>>,
seen_timestamp: Duration,
},
/// A block with an unknown parent has been received.
UnknownParentBlock(PeerId, Arc<SignedBeaconBlock<E>>, Hash256),
@@ -492,6 +499,9 @@ impl<T: BeaconChainTypes> SyncManager<T> {
SyncRequestId::SingleBlob { id } => {
self.on_single_blob_response(id, peer_id, RpcEvent::RPCError(error))
}
SyncRequestId::SinglePayloadEnvelope { id } => {
self.on_single_payload_envelope_response(id, peer_id, RpcEvent::RPCError(error))
}
SyncRequestId::DataColumnsByRoot(req_id) => {
self.on_data_columns_by_root_response(req_id, peer_id, RpcEvent::RPCError(error))
}
@@ -838,6 +848,17 @@ impl<T: BeaconChainTypes> SyncManager<T> {
} => {
self.rpc_data_column_received(sync_request_id, peer_id, data_column, seen_timestamp)
}
SyncMessage::RpcPayloadEnvelope {
sync_request_id,
peer_id,
envelope,
seen_timestamp,
} => self.rpc_payload_envelope_received(
sync_request_id,
peer_id,
envelope,
seen_timestamp,
),
SyncMessage::UnknownParentBlock(peer_id, block, block_root) => {
let block_slot = block.slot();
let parent_root = block.parent_root();
@@ -897,9 +918,33 @@ impl<T: BeaconChainTypes> SyncManager<T> {
}),
);
}
// TODO(gloas) support gloas data column variant
// In Gloas, data columns identify the beacon block root but do not carry
// parent root. Treat as an unknown block-root trigger (attestation-style).
// The peer is marked as data-capable since it sent us a data column.
DataColumnSidecar::Gloas(_) => {
error!("Gloas variant not yet supported")
match self.should_search_for_block(Some(data_column_slot), &peer_id) {
Ok(_) => {
if self.block_lookups.search_unknown_block_with_data_peer(
block_root,
&[peer_id],
&mut self.network,
) {
debug!(
?block_root,
"Created unknown block lookup from Gloas data column"
);
} else {
debug!(?block_root, "No lookup created from Gloas data column");
}
}
Err(reason) => {
debug!(
%block_root,
reason,
"Ignoring Gloas data column unknown block request"
);
}
}
}
}
}
@@ -1140,14 +1185,13 @@ impl<T: BeaconChainTypes> SyncManager<T> {
block: RpcEvent<Arc<SignedBeaconBlock<T::EthSpec>>>,
) {
if let Some(resp) = self.network.on_single_block_response(id, peer_id, block) {
self.block_lookups
.on_download_response::<BlockRequestState<T::EthSpec>>(
id,
resp.map(|(value, seen_timestamp)| {
(value, PeerGroup::from_single(peer_id), seen_timestamp)
}),
&mut self.network,
)
self.block_lookups.on_block_download_response(
id,
resp.map(|(value, seen_timestamp)| {
(value, PeerGroup::from_single(peer_id), seen_timestamp)
}),
&mut self.network,
)
}
}
@@ -1210,14 +1254,53 @@ impl<T: BeaconChainTypes> SyncManager<T> {
blob: RpcEvent<Arc<BlobSidecar<T::EthSpec>>>,
) {
if let Some(resp) = self.network.on_single_blob_response(id, peer_id, blob) {
self.block_lookups
.on_download_response::<BlobRequestState<T::EthSpec>>(
self.block_lookups.on_blob_download_response(
id,
resp.map(|(value, seen_timestamp)| {
(value, PeerGroup::from_single(peer_id), seen_timestamp)
}),
&mut self.network,
)
}
}
fn rpc_payload_envelope_received(
&mut self,
sync_request_id: SyncRequestId,
peer_id: PeerId,
envelope: Option<Arc<SignedExecutionPayloadEnvelope<T::EthSpec>>>,
seen_timestamp: Duration,
) {
match sync_request_id {
SyncRequestId::SinglePayloadEnvelope { id } => self
.on_single_payload_envelope_response(
id,
resp.map(|(value, seen_timestamp)| {
(value, PeerGroup::from_single(peer_id), seen_timestamp)
}),
&mut self.network,
)
peer_id,
RpcEvent::from_chunk(envelope, seen_timestamp),
),
_ => {
crit!(%peer_id, "bad request id for payload_envelope");
}
}
}
fn on_single_payload_envelope_response(
&mut self,
id: SingleLookupReqId,
peer_id: PeerId,
envelope: RpcEvent<Arc<SignedExecutionPayloadEnvelope<T::EthSpec>>>,
) {
if let Some(resp) = self
.network
.on_single_payload_envelope_response(id, peer_id, envelope)
{
self.block_lookups.on_payload_download_response(
id,
resp.map(|(value, seen_timestamp)| {
(value, PeerGroup::from_single(peer_id), seen_timestamp)
}),
&mut self.network,
)
}
}
@@ -1309,11 +1392,7 @@ impl<T: BeaconChainTypes> SyncManager<T> {
response: CustodyByRootResult<T::EthSpec>,
) {
self.block_lookups
.on_download_response::<CustodyRequestState<T::EthSpec>>(
requester.0,
response,
&mut self.network,
);
.on_custody_download_response(requester.0, response, &mut self.network);
}
/// Handles receiving a response for a range sync request that should have both blocks and