fix: store envelope for DuplicateFullyImported + fix load_parent

Two fixes for GLOaS range sync:

1. DuplicateFullyImported: persist envelope for blocks that are already
   in fork choice (e.g. post-checkpoint-sync blocks between finalized
   and head).

2. load_parent: if parent envelope isn't in store, check if parent is
   already in fork choice. If it is, the parent was already imported
   and validated — proceed without requiring the envelope in store.
   This handles the case where PayloadEnvelopesByRange doesn't return
   envelopes for all blocks (fewer envelopes than blocks).
This commit is contained in:
Eitan Seri-Levi
2026-05-07 12:50:19 +00:00
parent 3ff03ecd89
commit def1a6cacc
2 changed files with 52 additions and 5 deletions

View File

@@ -3060,7 +3060,34 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
//
// Note that `check_block_relevancy` is incapable of returning
// `DuplicateImportStatusUnknown` so we don't need to handle that case here.
Err(BlockError::DuplicateFullyImported(_)) => continue,
Err(BlockError::DuplicateFullyImported(_)) => {
debug!(
block_root = %block_root,
slot = %block.slot(),
"Skipping DuplicateFullyImported block in chain segment",
);
// For Gloas blocks, persist the envelope even though we're
// skipping the block. After checkpoint sync, blocks between
// the finalized checkpoint and the head are already in fork
// choice but their envelopes aren't in the store.
if let RangeSyncBlock::Gloas {
envelope: Some(ref available_envelope),
..
} = block
{
let (signed_envelope, _columns) = available_envelope.clone().deconstruct();
if let Err(e) = self
.store
.put_payload_envelope(&block_root, &signed_envelope)
{
return Err(Box::new(ChainSegmentResult::Failed {
imported_blocks,
error: BlockError::BeaconChainError(Box::new(e.into())),
}));
}
}
continue;
}
// If the block is the genesis block, simply ignore this block.
Err(BlockError::GenesisBlock) => continue,
// If the block is is for a finalized slot, simply ignore this block.
@@ -3077,6 +3104,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// However, we will potentially get a `ParentUnknown` on a later block. The sync
// protocol will need to ensure this is handled gracefully.
Err(BlockError::WouldRevertFinalizedSlot { .. }) => {
debug!(
block_root = %block_root,
slot = %block.slot(),
"Skipping WouldRevertFinalizedSlot block in chain segment",
);
// For Gloas blocks, persist the envelope even though we're skipping
// the block. This is needed after checkpoint sync: the checkpoint
// block's envelope must be in the store so that `load_parent` can

View File

@@ -2054,10 +2054,25 @@ fn load_parent<T: BeaconChainTypes, B: AsBlock<T::EthSpec>>(
.gloas_enabled()
&& parent_block.slot() > finalized_slot
{
let _envelope = chain
.store
.get_payload_envelope(&root)?
.ok_or(BlockError::ParentEnvelopeUnknown { parent_root: root })?;
let in_store = chain.store.get_payload_envelope(&root)?.is_some();
if !in_store {
// If the parent is already in fork choice it was previously imported.
// Its envelope may not be in the store if PayloadEnvelopesByRange
// didn't return it, but the block itself is valid and trusted.
let in_fork_choice = chain
.canonical_head
.fork_choice_read_lock()
.contains_block(&root);
if !in_fork_choice {
debug!(
parent_root = %root,
parent_slot = %parent_block.slot(),
%finalized_slot,
"load_parent: parent envelope not in store and not in fork choice",
);
return Err(BlockError::ParentEnvelopeUnknown { parent_root: root });
}
}
}
// Load the parent block's state from the database, returning an error if it is not found.