Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions beacon_node/beacon_chain/tests/store_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2767,6 +2767,158 @@ async fn weak_subjectivity_sync_from_genesis() {
weak_subjectivity_sync_test(slots, checkpoint_slot, None).await
}

// Ensures that an unaligned checkpoint sync (the block is older than the state)
// works correctly even when `prune_payloads` is enabled.
//
// Previously, the `HotColdDB` would refuse to load the execution payload for the
// anchor block because it was considered "pruned", causing the node to fail startup.
#[tokio::test]
async fn reproduction_unaligned_checkpoint_sync_pruned_payload() {
let spec = test_spec::<E>();

// Requires Execution Payloads.
let Some(_) = spec.deneb_fork_epoch else {
return;
};

// Create an unaligned checkpoint with a gap of 3 slots.
let num_initial_slots = E::slots_per_epoch() * 11;
let checkpoint_slot = Slot::new(E::slots_per_epoch() * 9 - 3);

let slots = (1..num_initial_slots)
.map(Slot::new)
.filter(|&slot| slot <= checkpoint_slot || slot > checkpoint_slot + 3)
.collect::<Vec<_>>();

let temp1 = tempdir().unwrap();
let full_store = get_store_generic(&temp1, StoreConfig::default(), spec.clone());

let harness = get_harness_import_all_data_columns(full_store.clone(), LOW_VALIDATOR_COUNT);
let all_validators = (0..LOW_VALIDATOR_COUNT).collect::<Vec<_>>();

let (genesis_state, genesis_state_root) = harness.get_current_state_and_root();
harness
.add_attested_blocks_at_slots(
genesis_state.clone(),
genesis_state_root,
&slots,
&all_validators,
)
.await;

// Extract snapshot data from the harness.
let wss_block_root = harness
.chain
.block_root_at_slot(checkpoint_slot, WhenSlotSkipped::Prev)
.unwrap()
.unwrap();
let wss_state_root = harness
.chain
.state_root_at_slot(checkpoint_slot)
.unwrap()
.unwrap();

let wss_block = harness
.chain
.store
.get_full_block(&wss_block_root)
.unwrap()
.unwrap();

// The test premise requires the anchor block to have a payload.
assert!(wss_block.message().execution_payload().is_ok());

let wss_blobs_opt = harness
.chain
.get_or_reconstruct_blobs(&wss_block_root)
.unwrap();

let wss_state = full_store
.get_state(&wss_state_root, Some(checkpoint_slot), CACHE_STATE_IN_TESTS)
.unwrap()
.unwrap();

// Configure the client with `prune_payloads = true`.
// This triggers the path where `try_get_full_block` must explicitly handle the anchor block.
let temp2 = tempdir().unwrap();
let store_config = StoreConfig {
prune_payloads: true,
..StoreConfig::default()
};

let store = get_store_generic(&temp2, store_config, spec.clone());

let slot_clock = TestingSlotClock::new(
Slot::new(0),
Duration::from_secs(harness.chain.genesis_time),
Duration::from_secs(spec.seconds_per_slot),
);
slot_clock.set_slot(harness.get_current_slot().as_u64());

let chain_config = ChainConfig {
reconstruct_historic_states: true,
..ChainConfig::default()
};

let trusted_setup = get_kzg(&spec);
let (shutdown_tx, _shutdown_rx) = futures::channel::mpsc::channel(1);
let mock = mock_execution_layer_from_parts(
harness.spec.clone(),
harness.runtime.task_executor.clone(),
);
let all_custody_columns = (0..spec.number_of_custody_groups).collect::<Vec<_>>();

// Attempt to build the BeaconChain.
// If the bug is present, this will panic with `MissingFullBlockExecutionPayloadPruned`.
let beacon_chain = BeaconChainBuilder::<DiskHarnessType<E>>::new(MinimalEthSpec, trusted_setup)
.chain_config(chain_config)
.store(store.clone())
.custom_spec(spec.clone().into())
.task_executor(harness.chain.task_executor.clone())
.weak_subjectivity_state(
wss_state,
wss_block.clone(),
wss_blobs_opt.clone(),
genesis_state,
)
.unwrap()
.store_migrator_config(MigratorConfig::default().blocking())
.slot_clock(slot_clock)
.shutdown_sender(shutdown_tx)
.event_handler(Some(ServerSentEventHandler::new_with_capacity(1)))
.execution_layer(Some(mock.el))
.ordered_custody_column_indices(all_custody_columns)
.rng(Box::new(StdRng::seed_from_u64(42)))
.build();

assert!(
beacon_chain.is_ok(),
"Beacon Chain failed to build. The anchor payload may have been incorrectly pruned. Error: {:?}",
beacon_chain.err()
);

let chain = beacon_chain.as_ref().unwrap();
let wss_block_slot = wss_block.slot();

assert_ne!(
wss_block_slot,
chain.head_snapshot().beacon_state.slot(),
"Test invalid: Checkpoint was aligned (Slot {} == Slot {}). The test did not trigger the unaligned edge case.",
wss_block_slot,
chain.head_snapshot().beacon_state.slot()
);

let payload_exists = chain
.store
.execution_payload_exists(&wss_block_root)
.unwrap_or(false);

assert!(
payload_exists,
"Split block payload must exist in the new node's store after checkpoint sync"
);
}

async fn weak_subjectivity_sync_test(
slots: Vec<Slot>,
checkpoint_slot: Slot,
Expand Down