fix: use execution_payload_block_hash for Pending payload status in fcU

When gloas envelopes are imported optimistically (EL returns SYNCING),
the payload status is Pending. Previously, Pending used
execution_payload_parent_hash for the head_hash in forkchoiceUpdated,
which tells geth to stay at the parent — never advancing.

Fix: use execution_payload_block_hash (the bid's committed hash) for
Pending status, same as Full. This tells geth to sync to the new head,
which is the purpose of optimistic sync. Geth will validate it and
transition from SYNCING to VALID on subsequent newPayload calls.

Also re-enables backfill sync for gloas with a dedicated
import_historical_gloas_block_batch that properly handles RangeSyncBlock::Gloas
variants (storing envelopes alongside blocks).
This commit is contained in:
Eitan Seri-Levi
2026-05-08 08:53:25 +00:00
committed by Devnet Bot
parent daa3759523
commit b53a969c30
4 changed files with 311 additions and 76 deletions

View File

@@ -799,13 +799,38 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
downloaded_blocks: Vec<RangeSyncBlock<T::EthSpec>>,
) -> (usize, Result<(), ChainSegmentFailed>) {
let total_blocks = downloaded_blocks.len();
// Check if this batch contains GLOaS blocks.
let is_gloas_batch = downloaded_blocks
.first()
.map(|b| matches!(b, RangeSyncBlock::Gloas { .. }))
.unwrap_or(false);
if is_gloas_batch {
// GLOaS blocks: store blocks and envelopes directly.
// KZG verification for columns was already done during coupling.
match self.chain.import_historical_gloas_block_batch(downloaded_blocks) {
Ok(imported_blocks) => {
metrics::inc_counter(
&metrics::BEACON_PROCESSOR_BACKFILL_CHAIN_SEGMENT_SUCCESS_TOTAL,
);
return (imported_blocks, Ok(()));
}
Err(e) => {
metrics::inc_counter(
&metrics::BEACON_PROCESSOR_BACKFILL_CHAIN_SEGMENT_FAILED_TOTAL,
);
return self.handle_historical_block_error(e);
}
}
}
// Pre-GLOaS path: convert to AvailableBlocks and verify KZG.
let available_blocks = downloaded_blocks
.into_iter()
.map(|block| block.into_available_block())
.collect::<Vec<_>>();
// TODO(gloas) when implementing backfill sync for gloas
// we need a batch verify kzg function in the new da checker
match self
.chain
.data_availability_checker
@@ -859,75 +884,83 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
metrics::inc_counter(
&metrics::BEACON_PROCESSOR_BACKFILL_CHAIN_SEGMENT_FAILED_TOTAL,
);
let peer_action = match &e {
HistoricalBlockError::MismatchedBlockRoot {
block_root,
expected_block_root,
} => {
debug!(
error = "mismatched_block_root",
?block_root,
expected_root = ?expected_block_root,
"Backfill batch processing error"
);
// The peer is faulty if they send blocks with bad roots.
Some(PeerAction::LowToleranceError)
}
HistoricalBlockError::InvalidSignature
| HistoricalBlockError::SignatureSet(_) => {
warn!(
error = ?e,
"Backfill batch processing error"
);
// The peer is faulty if they bad signatures.
Some(PeerAction::LowToleranceError)
}
HistoricalBlockError::MissingOldestBlockRoot { slot } => {
warn!(
%slot,
error = "missing_oldest_block_root",
"Backfill batch processing error"
);
// This is an internal error, do not penalize the peer.
None
}
HistoricalBlockError::ValidatorPubkeyCacheTimeout => {
warn!(
error = "pubkey_cache_timeout",
"Backfill batch processing error"
);
// This is an internal error, do not penalize the peer.
None
}
HistoricalBlockError::IndexOutOfBounds => {
error!(
error = ?e,
"Backfill batch OOB error"
);
// This should never occur, don't penalize the peer.
None
}
HistoricalBlockError::StoreError(e) => {
warn!(error = ?e, "Backfill batch processing error");
// This is an internal error, don't penalize the peer.
None
} //
// Do not use a fallback match, handle all errors explicitly
};
let err_str: &'static str = e.into();
(
0,
Err(ChainSegmentFailed {
message: format!("{:?}", err_str),
// This is an internal error, don't penalize the peer.
peer_action,
}),
)
self.handle_historical_block_error(e)
}
}
}
/// Maps a `HistoricalBlockError` to the appropriate peer action and error tuple.
fn handle_historical_block_error(
&self,
e: HistoricalBlockError,
) -> (usize, Result<(), ChainSegmentFailed>) {
let peer_action = match &e {
HistoricalBlockError::MismatchedBlockRoot {
block_root,
expected_block_root,
} => {
debug!(
error = "mismatched_block_root",
?block_root,
expected_root = ?expected_block_root,
"Backfill batch processing error"
);
// The peer is faulty if they send blocks with bad roots.
Some(PeerAction::LowToleranceError)
}
HistoricalBlockError::InvalidSignature
| HistoricalBlockError::SignatureSet(_) => {
warn!(
error = ?e,
"Backfill batch processing error"
);
// The peer is faulty if they bad signatures.
Some(PeerAction::LowToleranceError)
}
HistoricalBlockError::MissingOldestBlockRoot { slot } => {
warn!(
%slot,
error = "missing_oldest_block_root",
"Backfill batch processing error"
);
// This is an internal error, do not penalize the peer.
None
}
HistoricalBlockError::ValidatorPubkeyCacheTimeout => {
warn!(
error = "pubkey_cache_timeout",
"Backfill batch processing error"
);
// This is an internal error, do not penalize the peer.
None
}
HistoricalBlockError::IndexOutOfBounds => {
error!(
error = ?e,
"Backfill batch OOB error"
);
// This should never occur, don't penalize the peer.
None
}
HistoricalBlockError::StoreError(e) => {
warn!(error = ?e, "Backfill batch processing error");
// This is an internal error, don't penalize the peer.
None
} //
// Do not use a fallback match, handle all errors explicitly
};
let err_str: &'static str = e.into();
(
0,
Err(ChainSegmentFailed {
message: format!("{:?}", err_str),
// This is an internal error, don't penalize the peer.
peer_action,
}),
)
}
/// Helper function to handle a `BlockError` from `process_chain_segment`
fn handle_failed_chain_segment(&self, error: BlockError) -> Result<(), ChainSegmentFailed> {
match error {

View File

@@ -215,11 +215,6 @@ impl<T: BeaconChainTypes> BackFillSync<T> {
&mut self,
network: &mut SyncNetworkContext<T>,
) -> Result<SyncStart, BackFillError> {
// Skip backfill sync for GLOaS — not yet implemented for this fork.
if self.beacon_chain.spec.gloas_fork_epoch.is_some_and(|e| e != Epoch::max_value()) {
return Ok(SyncStart::NotSyncing);
}
match self.state() {
BackFillState::Syncing => {} // already syncing ignore.
BackFillState::Paused => {