Skip to content

Commit 74b8c02

Browse files
Reimport the checkpoint sync block (#8417)
We want to not require checkpoint sync starts to include the required custody data columns, and instead fetch them from p2p. Closes #6837 The checkpoint sync slot can: 1. Be the first slot in the epoch, such that the epoch of the block == the start checkpoint epoch 2. Be in an epoch prior to the start checkpoint epoch In both cases backfill sync already fetches that epoch worth of blocks with current code. This PR modifies the backfill import filter function to allow to re-importing the oldest block slot in the DB. I feel this solution is sufficient unless I'm missing something. ~~I have not tested this yet!~~ Michael has tested this and it works. Co-Authored-By: dapplion <35266934+dapplion@users.noreply.github.com> Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
1 parent 8e54f6e commit 74b8c02

File tree

4 files changed

+52
-9
lines changed

4 files changed

+52
-9
lines changed

beacon_node/beacon_chain/src/historical_blocks.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::data_availability_checker::{AvailableBlock, AvailableBlockData};
2-
use crate::{BeaconChain, BeaconChainTypes, metrics};
2+
use crate::{BeaconChain, BeaconChainTypes, WhenSlotSkipped, metrics};
33
use itertools::Itertools;
44
use state_processing::{
55
per_block_processing::ParallelSignatureSets,
@@ -34,6 +34,8 @@ pub enum HistoricalBlockError {
3434
ValidatorPubkeyCacheTimeout,
3535
/// Logic error: should never occur.
3636
IndexOutOfBounds,
37+
/// Logic error: should never occur.
38+
MissingOldestBlockRoot { slot: Slot },
3739
/// Internal store error
3840
StoreError(StoreError),
3941
}
@@ -56,7 +58,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
5658
/// `SignatureSetError` or `InvalidSignature` will be returned.
5759
///
5860
/// To align with sync we allow some excess blocks with slots greater than or equal to
59-
/// `oldest_block_slot` to be provided. They will be ignored without being checked.
61+
/// `oldest_block_slot` to be provided. They will be re-imported to fill the columns of the
62+
/// checkpoint sync block.
6063
///
6164
/// This function should not be called concurrently with any other function that mutates
6265
/// the anchor info (including this function itself). If a concurrent mutation occurs that
@@ -72,9 +75,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
7275
let blob_info = self.store.get_blob_info();
7376
let data_column_info = self.store.get_data_column_info();
7477

75-
// Take all blocks with slots less than the oldest block slot.
78+
// Take all blocks with slots less than or equal to the oldest block slot.
79+
//
80+
// This allows for reimport of the blobs/columns for the finalized block after checkpoint
81+
// sync.
7682
let num_relevant = blocks.partition_point(|available_block| {
77-
available_block.block().slot() < anchor_info.oldest_block_slot
83+
available_block.block().slot() <= anchor_info.oldest_block_slot
7884
});
7985

8086
let total_blocks = blocks.len();
@@ -95,6 +101,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
95101
}
96102

97103
let mut expected_block_root = anchor_info.oldest_block_parent;
104+
let mut last_block_root = expected_block_root;
98105
let mut prev_block_slot = anchor_info.oldest_block_slot;
99106
let mut new_oldest_blob_slot = blob_info.oldest_blob_slot;
100107
let mut new_oldest_data_column_slot = data_column_info.oldest_data_column_slot;
@@ -107,7 +114,27 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
107114
for available_block in blocks_to_import.into_iter().rev() {
108115
let (block_root, block, block_data) = available_block.deconstruct();
109116

110-
if block_root != expected_block_root {
117+
if block.slot() == anchor_info.oldest_block_slot {
118+
// When reimporting, verify that this is actually the same block (same block root).
119+
let oldest_block_root = self
120+
.block_root_at_slot(block.slot(), WhenSlotSkipped::None)
121+
.ok()
122+
.flatten()
123+
.ok_or(HistoricalBlockError::MissingOldestBlockRoot { slot: block.slot() })?;
124+
if block_root != oldest_block_root {
125+
return Err(HistoricalBlockError::MismatchedBlockRoot {
126+
block_root,
127+
expected_block_root: oldest_block_root,
128+
});
129+
}
130+
131+
debug!(
132+
?block_root,
133+
slot = %block.slot(),
134+
"Re-importing historic block"
135+
);
136+
last_block_root = block_root;
137+
} else if block_root != expected_block_root {
111138
return Err(HistoricalBlockError::MismatchedBlockRoot {
112139
block_root,
113140
expected_block_root,
@@ -198,7 +225,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
198225
.ok_or(HistoricalBlockError::IndexOutOfBounds)?
199226
.iter()
200227
.map(|block| block.parent_root())
201-
.chain(iter::once(anchor_info.oldest_block_parent));
228+
.chain(iter::once(last_block_root));
202229
let signature_set = signed_blocks
203230
.iter()
204231
.zip_eq(block_roots)

beacon_node/execution_layer/src/test_utils/mock_builder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -842,7 +842,7 @@ impl<E: EthSpec> MockBuilder<E> {
842842
.beacon_client
843843
.get_beacon_blocks::<E>(BlockId::Finalized)
844844
.await
845-
.map_err(|_| "couldn't get finalized block".to_string())?
845+
.map_err(|e| format!("couldn't get finalized block: {e:?}"))?
846846
.ok_or_else(|| "missing finalized block".to_string())?
847847
.data()
848848
.message()
@@ -855,7 +855,7 @@ impl<E: EthSpec> MockBuilder<E> {
855855
.beacon_client
856856
.get_beacon_blocks::<E>(BlockId::Justified)
857857
.await
858-
.map_err(|_| "couldn't get justified block".to_string())?
858+
.map_err(|e| format!("couldn't get justified block: {e:?}"))?
859859
.ok_or_else(|| "missing justified block".to_string())?
860860
.data()
861861
.message()

beacon_node/network/src/network_beacon_processor/sync_methods.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,16 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
804804
// The peer is faulty if they bad signatures.
805805
Some(PeerAction::LowToleranceError)
806806
}
807+
HistoricalBlockError::MissingOldestBlockRoot { slot } => {
808+
warn!(
809+
%slot,
810+
error = "missing_oldest_block_root",
811+
"Backfill batch processing error"
812+
);
813+
// This is an internal error, do not penalize the peer.
814+
None
815+
}
816+
807817
HistoricalBlockError::ValidatorPubkeyCacheTimeout => {
808818
warn!(
809819
error = "pubkey_cache_timeout",

beacon_node/store/src/hot_cold_store.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,9 +649,15 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
649649
.inspect(|cache| cache.lock().put_block(*block_root, full_block.clone()));
650650

651651
DatabaseBlock::Full(full_block)
652-
} else if !self.config.prune_payloads {
652+
} else if !self.config.prune_payloads || *block_root == split.block_root {
653653
// If payload pruning is disabled there's a chance we may have the payload of
654654
// this finalized block. Attempt to load it but don't error in case it's missing.
655+
//
656+
// We also allow for the split block's payload to be loaded *if it exists*. This is
657+
// necessary on startup when syncing from an unaligned checkpoint (a checkpoint state
658+
// at a skipped slot), and then loading the canonical head (with payload). If we modify
659+
// payload pruning in future so that it doesn't prune the split block's payload, then
660+
// this case could move to the case above where we error if the payload is missing.
655661
let fork_name = blinded_block.fork_name(&self.spec)?;
656662
if let Some(payload) = self.get_execution_payload(block_root, fork_name)? {
657663
DatabaseBlock::Full(

0 commit comments

Comments
 (0)