debug + fix: store envelope for DuplicateFullyImported blocks

After checkpoint sync, blocks between the finalized checkpoint and head
are already in fork choice but their envelopes aren't in the store.
When range sync downloads these blocks, they get skipped as
DuplicateFullyImported without persisting the envelope. The next new
block then fails load_parent because it can't find its parent's envelope.

Fix: persist the envelope in the DuplicateFullyImported path, same as
the existing WouldRevertFinalizedSlot path.
This commit is contained in:
Eitan Seri-Levi
2026-05-07 12:50:19 +00:00
parent 3ff03ecd89
commit 5a93cbdf77
2 changed files with 42 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,15 @@ 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 })?;
if chain.store.get_payload_envelope(&root)?.is_none() {
debug!(
parent_root = %root,
parent_slot = %parent_block.slot(),
%finalized_slot,
"load_parent: parent envelope not in store",
);
return Err(BlockError::ParentEnvelopeUnknown { parent_root: root });
}
}
// Load the parent block's state from the database, returning an error if it is not found.