Skip to content

Commit 0121f88

Browse files
committed
chore(store): avoid pruning split block payloads and add coverage tests
Add a condition to skip pruning when the block is the split block root. Prevents `try_prune_execution_payloads` from deleting the split block's execution payload. Add two tests `pruning_preserves_payload_aligned` and `pruning_preserves_payload_unaligned` using the helper `verify_pruning_preserves_payload` to ensure correct behavior.
1 parent f3fd1f2 commit 0121f88

File tree

2 files changed

+152
-0
lines changed

2 files changed

+152
-0
lines changed

beacon_node/beacon_chain/tests/store_tests.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2782,6 +2782,157 @@ async fn weak_subjectivity_sync_without_blobs() {
27822782
weak_subjectivity_sync_test(slots, checkpoint_slot, None, false).await
27832783
}
27842784

2785+
#[tokio::test]
2786+
async fn pruning_preserves_payload_aligned() {
2787+
type E = MinimalEthSpec;
2788+
let num_initial_slots = E::slots_per_epoch() * 11;
2789+
let checkpoint_slot = Slot::new(E::slots_per_epoch() * 9); // Aligned Slot
2790+
2791+
let slots = (1..num_initial_slots).map(Slot::new).collect::<Vec<_>>();
2792+
2793+
verify_pruning_preserves_payload(checkpoint_slot, slots).await;
2794+
}
2795+
2796+
#[tokio::test]
2797+
async fn pruning_preserves_payload_unaligned() {
2798+
type E = MinimalEthSpec;
2799+
let num_initial_slots = E::slots_per_epoch() * 11;
2800+
let checkpoint_slot = Slot::new(E::slots_per_epoch() * 9 - 3);
2801+
2802+
let slots = (1..num_initial_slots)
2803+
.map(Slot::new)
2804+
.filter(|&slot| slot <= checkpoint_slot || slot > checkpoint_slot + 3)
2805+
.collect::<Vec<_>>();
2806+
2807+
verify_pruning_preserves_payload(checkpoint_slot, slots).await;
2808+
}
2809+
2810+
async fn verify_pruning_preserves_payload(
2811+
checkpoint_slot: Slot,
2812+
slots: Vec<Slot>,
2813+
) {
2814+
type E = MinimalEthSpec;
2815+
let spec = test_spec::<E>();
2816+
2817+
// Requires Execution Payloads.
2818+
let Some(_) = spec.deneb_fork_epoch else {
2819+
return;
2820+
};
2821+
2822+
// Create a standard chain.
2823+
let temp1 = tempdir().unwrap();
2824+
let full_store = get_store_generic(&temp1, StoreConfig::default(), spec.clone());
2825+
2826+
let harness = get_harness_import_all_data_columns(full_store.clone(), LOW_VALIDATOR_COUNT);
2827+
let all_validators = (0..LOW_VALIDATOR_COUNT).collect::<Vec<_>>();
2828+
2829+
let (genesis_state, genesis_state_root) = harness.get_current_state_and_root();
2830+
harness
2831+
.add_attested_blocks_at_slots(
2832+
genesis_state.clone(),
2833+
genesis_state_root,
2834+
&slots,
2835+
&all_validators,
2836+
)
2837+
.await;
2838+
2839+
// Extract snapshot data.
2840+
let wss_block_root = harness
2841+
.chain
2842+
.block_root_at_slot(checkpoint_slot, WhenSlotSkipped::Prev)
2843+
.unwrap()
2844+
.unwrap();
2845+
let wss_state_root = harness
2846+
.chain
2847+
.state_root_at_slot(checkpoint_slot)
2848+
.unwrap()
2849+
.unwrap();
2850+
2851+
let wss_block = harness
2852+
.chain
2853+
.store
2854+
.get_full_block(&wss_block_root)
2855+
.unwrap()
2856+
.unwrap();
2857+
let wss_blobs_opt = harness
2858+
.chain
2859+
.get_or_reconstruct_blobs(&wss_block_root)
2860+
.unwrap();
2861+
let wss_state = full_store
2862+
.get_state(&wss_state_root, Some(checkpoint_slot), CACHE_STATE_IN_TESTS)
2863+
.unwrap()
2864+
.unwrap();
2865+
2866+
// Ensure client with `prune_payloads = true`.
2867+
let temp2 = tempdir().unwrap();
2868+
let store_config = StoreConfig {
2869+
prune_payloads: true,
2870+
..StoreConfig::default()
2871+
};
2872+
2873+
let store = get_store_generic(&temp2, store_config, spec.clone());
2874+
let slot_clock = TestingSlotClock::new(
2875+
Slot::new(0),
2876+
Duration::from_secs(harness.chain.genesis_time),
2877+
Duration::from_secs(spec.seconds_per_slot),
2878+
);
2879+
slot_clock.set_slot(harness.get_current_slot().as_u64());
2880+
2881+
let chain_config = ChainConfig {
2882+
reconstruct_historic_states: true,
2883+
..ChainConfig::default()
2884+
};
2885+
2886+
let trusted_setup = get_kzg(&spec);
2887+
let (shutdown_tx, _shutdown_rx) = futures::channel::mpsc::channel(1);
2888+
let mock = mock_execution_layer_from_parts(
2889+
harness.spec.clone(),
2890+
harness.runtime.task_executor.clone(),
2891+
);
2892+
let all_custody_columns = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
2893+
2894+
let beacon_chain = BeaconChainBuilder::<DiskHarnessType<E>>::new(MinimalEthSpec, trusted_setup)
2895+
.chain_config(chain_config)
2896+
.store(store.clone())
2897+
.custom_spec(spec.clone().into())
2898+
.task_executor(harness.chain.task_executor.clone())
2899+
.weak_subjectivity_state(
2900+
wss_state,
2901+
wss_block.clone(),
2902+
wss_blobs_opt.clone(),
2903+
genesis_state,
2904+
)
2905+
.unwrap()
2906+
.store_migrator_config(MigratorConfig::default().blocking())
2907+
.slot_clock(slot_clock)
2908+
.shutdown_sender(shutdown_tx)
2909+
.event_handler(Some(ServerSentEventHandler::new_with_capacity(1)))
2910+
.execution_layer(Some(mock.el))
2911+
.ordered_custody_column_indices(all_custody_columns)
2912+
.rng(Box::new(StdRng::seed_from_u64(42)))
2913+
.build();
2914+
2915+
assert!(beacon_chain.is_ok(), "Beacon Chain failed to build");
2916+
let chain = beacon_chain.as_ref().unwrap();
2917+
2918+
// Trigger Pruning Explicitly.
2919+
chain
2920+
.store
2921+
.try_prune_execution_payloads(true)
2922+
.expect("Pruning should succeed");
2923+
2924+
// Assert the Split Payload still exists.
2925+
let payload_exists = chain
2926+
.store
2927+
.execution_payload_exists(&wss_block_root)
2928+
.unwrap_or(false);
2929+
2930+
assert!(
2931+
payload_exists,
2932+
"Split block payload must exist after pruning"
2933+
);
2934+
}
2935+
27852936
async fn weak_subjectivity_sync_test(
27862937
slots: Vec<Slot>,
27872938
checkpoint_slot: Slot,

beacon_node/store/src/hot_cold_store.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3183,6 +3183,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
31833183
}
31843184

31853185
if Some(block_root) != last_pruned_block_root
3186+
&& block_root != split_block_root
31863187
&& self.execution_payload_exists(&block_root)?
31873188
{
31883189
debug!(%slot, ?block_root, "Pruning execution payload");

0 commit comments

Comments
 (0)