Gloas range sync (#9362)

N/A


  Implement range sync in gloas.
Basically requests blocks and payloads post gloas from the same peer, couples them and sends it for processing.
Does not change sync much at all other than adding the machinery for payloads by range requests.

Main changes are:
`RangeSyncBlock` which used to be a struct is an enum to account for the Gloas case. This allows a clear separation between gloas and pre-gloas code.
`AvailableBlockData` now has a `BlockInEnvelope` variant. This is to clearly indicate the post gloas case. I feel this is simpler to follow compared to `NoData` variant.


Tries to extract post gloas logic into its own functions so that there is minimal logic change in mainnet range sync behaviour.

This is meant as a stable base on which we can iterate further to make range sync cleaner and for unblocking range sync support on devnet. Some ideas for later is removing the retry mechanism in favour of delegating column fetching to lookup sync which can be done post #9155 and batch signature verifying envelopes.


Co-Authored-By: Pawan Dhananjay <pawandhananjay@gmail.com>

Co-Authored-By: dapplion <35266934+dapplion@users.noreply.github.com>

Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>
This commit is contained in:
Pawan Dhananjay
2026-06-10 16:00:57 +05:30
committed by GitHub
parent 844c6dd4a0
commit db3192e001
24 changed files with 1311 additions and 232 deletions

View File

@@ -4,6 +4,7 @@ use crate::data_availability_checker::DataAvailabilityChecker;
use crate::graffiti_calculator::GraffitiSettings;
use crate::kzg_utils::{build_data_column_sidecars_fulu, build_data_column_sidecars_gloas};
use crate::observed_operations::ObservationOutcome;
use crate::payload_envelope_verification::AvailableEnvelope;
pub use crate::persisted_beacon_chain::PersistedBeaconChain;
use crate::{BeaconBlockResponseWrapper, CustodyContext, get_block_root};
use crate::{
@@ -2929,18 +2930,29 @@ where
block: Arc<SignedBeaconBlock<E>>,
) -> RangeSyncBlock<E> {
let block_root = block_root.unwrap_or_else(|| get_block_root(&block));
let is_gloas = block.fork_name_unchecked().gloas_enabled();
// For Gloas, kzg commitments live in the bid (`signed_execution_payload_bid`), so the
// body's `blob_kzg_commitments()` accessor returns Err. `num_expected_blobs` already
// handles both shapes.
let has_blobs = block.num_expected_blobs() > 0;
if !has_blobs {
return RangeSyncBlock::new(
block,
AvailableBlockData::NoData,
&self.chain.data_availability_checker,
self.chain.spec.clone(),
)
.unwrap();
return if is_gloas {
let envelope = self
.chain
.get_payload_envelope(&block_root)
.unwrap()
.map(Arc::new)
.map(|envelope| AvailableEnvelope::new(envelope, vec![]));
RangeSyncBlock::new_gloas(block, envelope).unwrap()
} else {
RangeSyncBlock::new(
block,
AvailableBlockData::NoData,
&self.chain.data_availability_checker,
self.chain.spec.clone(),
)
.unwrap()
};
}
// Blobs are stored as data columns from Fulu (PeerDAS)
@@ -2952,14 +2964,24 @@ where
.unwrap()
.unwrap();
let custody_columns = columns.into_iter().collect::<Vec<_>>();
let block_data = AvailableBlockData::new_with_data_columns(custody_columns);
RangeSyncBlock::new(
block,
block_data,
&self.chain.data_availability_checker,
self.chain.spec.clone(),
)
.unwrap()
if is_gloas {
let envelope = self
.chain
.get_payload_envelope(&block_root)
.unwrap()
.map(Arc::new)
.map(|envelope| AvailableEnvelope::new(envelope, custody_columns));
RangeSyncBlock::new_gloas(block, envelope).unwrap()
} else {
let block_data = AvailableBlockData::new_with_data_columns(custody_columns);
RangeSyncBlock::new(
block,
block_data,
&self.chain.data_availability_checker,
self.chain.spec.clone(),
)
.unwrap()
}
} else {
let blobs = self.chain.get_blobs(&block_root).unwrap().blobs();
let block_data = if let Some(blobs) = blobs {
@@ -2984,6 +3006,19 @@ where
block: Arc<SignedBeaconBlock<E, FullPayload<E>>>,
blob_items: Option<(KzgProofs<E>, BlobsList<E>)>,
) -> Result<RangeSyncBlock<E>, BlockError> {
if block.fork_name_unchecked().gloas_enabled() {
let columns = blob_items
.map(|_| generate_data_column_sidecars_from_block(&block, &self.spec))
.unwrap_or_default();
let envelope = self
.chain
.get_payload_envelope(&block.canonical_root())
.map_err(|e| BlockError::BeaconChainError(Box::new(e)))?
.map(Arc::new)
.map(|envelope| AvailableEnvelope::new(envelope, columns));
return RangeSyncBlock::new_gloas(block, envelope).map_err(BlockError::InternalError);
}
Ok(if self.spec.is_peer_das_enabled_for_epoch(block.epoch()) {
let epoch = block.slot().epoch(E::slots_per_epoch());
let sampling_columns = self.chain.sampling_columns_for_epoch(epoch);