Merge remote-tracking branch 'origin/unstable' into gloas-containers

This commit is contained in:
Michael Sproul
2025-12-16 14:04:18 +11:00
246 changed files with 2722 additions and 1786 deletions

View File

@@ -25,11 +25,14 @@ use beacon_chain::{
historical_blocks::HistoricalBlockError,
migrate::MigratorConfig,
};
use bls::{Keypair, Signature, SignatureBytes};
use fixed_bytes::FixedBytesExtended;
use logging::create_test_tracing_subscriber;
use maplit::hashset;
use rand::Rng;
use rand::rngs::StdRng;
use slot_clock::{SlotClock, TestingSlotClock};
use ssz_types::VariableList;
use state_processing::{BlockReplayer, state_advance::complete_state_advance};
use std::collections::HashMap;
use std::collections::HashSet;
@@ -2783,6 +2786,158 @@ async fn weak_subjectivity_sync_without_blobs() {
weak_subjectivity_sync_test(slots, checkpoint_slot, None, false).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,
@@ -5529,7 +5684,6 @@ fn get_finalized_epoch_boundary_blocks(
dump: &[BeaconSnapshot<MinimalEthSpec, BlindedPayload<MinimalEthSpec>>],
) -> HashSet<SignedBeaconBlockHash> {
dump.iter()
.cloned()
.map(|checkpoint| checkpoint.beacon_state.finalized_checkpoint().root.into())
.collect()
}
@@ -5538,7 +5692,6 @@ fn get_blocks(
dump: &[BeaconSnapshot<MinimalEthSpec, BlindedPayload<MinimalEthSpec>>],
) -> HashSet<SignedBeaconBlockHash> {
dump.iter()
.cloned()
.map(|checkpoint| checkpoint.beacon_block_root.into())
.collect()
}