mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-30 19:23:50 +00:00
Gloas spec v1.7.0-alpha.5 and beacon_chain tests (#8998)
Fix database pruning post-Gloas - Fix DB pruning logic (and state summaries DAG) - Get the `beacon_chain` tests running with `FORK_NAME=gloas` 🎉 Co-Authored-By: Michael Sproul <michael@sigmaprime.io> Co-Authored-By: Jimmy Chen <jchen.tc@gmail.com> Co-Authored-By: Eitan Seri- Levi <eserilev@gmail.com> Co-Authored-By: dapplion <35266934+dapplion@users.noreply.github.com> Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>
This commit is contained in:
@@ -27,6 +27,7 @@ use beacon_chain::{
|
||||
};
|
||||
use bls::{Keypair, Signature, SignatureBytes};
|
||||
use fixed_bytes::FixedBytesExtended;
|
||||
use fork_choice::PayloadStatus;
|
||||
use logging::create_test_tracing_subscriber;
|
||||
use maplit::hashset;
|
||||
use rand::Rng;
|
||||
@@ -53,7 +54,7 @@ use types::test_utils::{SeedableRng, XorShiftRng};
|
||||
use types::*;
|
||||
|
||||
// Should ideally be divisible by 3.
|
||||
pub const LOW_VALIDATOR_COUNT: usize = 24;
|
||||
pub const LOW_VALIDATOR_COUNT: usize = 32;
|
||||
pub const HIGH_VALIDATOR_COUNT: usize = 64;
|
||||
|
||||
// When set to true, cache any states fetched from the db.
|
||||
@@ -184,6 +185,10 @@ async fn light_client_bootstrap_test() {
|
||||
// No-op prior to Altair.
|
||||
return;
|
||||
};
|
||||
// TODO(EIP-7732): Light client not yet implemented for Gloas.
|
||||
if spec.is_gloas_scheduled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store_generic(&db_path, StoreConfig::default(), spec.clone());
|
||||
@@ -239,6 +244,10 @@ async fn light_client_updates_test() {
|
||||
// No-op prior to Altair.
|
||||
return;
|
||||
};
|
||||
// TODO(EIP-7732): Light client not yet implemented for Gloas.
|
||||
if spec.is_gloas_scheduled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let num_final_blocks = E::slots_per_epoch() * 2;
|
||||
let db_path = tempdir().unwrap();
|
||||
@@ -568,13 +577,12 @@ async fn epoch_boundary_state_attestation_processing() {
|
||||
.get_blinded_block(&block_root)
|
||||
.unwrap()
|
||||
.expect("block exists");
|
||||
// Use get_state as the state may be finalized by this point
|
||||
// Use get_state as the state may be finalized by this point.
|
||||
let state_root = block.state_root();
|
||||
let mut epoch_boundary_state = store
|
||||
.get_state(&block.state_root(), None, CACHE_STATE_IN_TESTS)
|
||||
.get_state(&state_root, None, CACHE_STATE_IN_TESTS)
|
||||
.expect("no error")
|
||||
.unwrap_or_else(|| {
|
||||
panic!("epoch boundary state should exist {:?}", block.state_root())
|
||||
});
|
||||
.unwrap_or_else(|| panic!("epoch boundary state should exist {:?}", state_root));
|
||||
let ebs_state_root = epoch_boundary_state.update_tree_hash_cache().unwrap();
|
||||
let mut ebs_of_ebs = store
|
||||
.get_state(&ebs_state_root, None, CACHE_STATE_IN_TESTS)
|
||||
@@ -673,8 +681,11 @@ async fn forwards_iter_block_and_state_roots_until() {
|
||||
let block_root = block_roots[slot.as_usize()];
|
||||
assert_eq!(block_root_iter.next().unwrap().unwrap(), (block_root, slot));
|
||||
|
||||
let (iter_state_root, iter_slot) = state_root_iter.next().unwrap().unwrap();
|
||||
assert_eq!(iter_slot, slot);
|
||||
|
||||
let state_root = state_roots[slot.as_usize()];
|
||||
assert_eq!(state_root_iter.next().unwrap().unwrap(), (state_root, slot));
|
||||
assert_eq!(iter_state_root, state_root);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -708,13 +719,8 @@ async fn block_replayer_hooks() {
|
||||
.add_attested_blocks_at_slots(state.clone(), state_root, &block_slots, &all_validators)
|
||||
.await;
|
||||
|
||||
let (blocks, envelopes) = store
|
||||
.load_blocks_to_replay(
|
||||
Slot::new(0),
|
||||
max_slot,
|
||||
end_block_root.into(),
|
||||
StatePayloadStatus::Pending,
|
||||
)
|
||||
let blocks = store
|
||||
.load_blocks_to_replay(Slot::new(0), max_slot, end_block_root.into())
|
||||
.unwrap();
|
||||
|
||||
let mut pre_slots = vec![];
|
||||
@@ -749,7 +755,7 @@ async fn block_replayer_hooks() {
|
||||
post_block_slots.push(block.slot());
|
||||
Ok(())
|
||||
}))
|
||||
.apply_blocks(blocks, envelopes, None)
|
||||
.apply_blocks(blocks, None)
|
||||
.unwrap()
|
||||
.into_state();
|
||||
|
||||
@@ -2871,12 +2877,6 @@ async fn reproduction_unaligned_checkpoint_sync_pruned_payload() {
|
||||
.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
|
||||
@@ -2884,8 +2884,21 @@ async fn reproduction_unaligned_checkpoint_sync_pruned_payload() {
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// The test premise requires the anchor block to have a payload.
|
||||
assert!(wss_block.message().execution_payload().is_ok());
|
||||
let wss_state_root = harness
|
||||
.chain
|
||||
.state_root_at_slot(checkpoint_slot)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
// The test premise requires the anchor block to have a payload (or a payload bid in Gloas).
|
||||
assert!(
|
||||
wss_block.message().execution_payload().is_ok()
|
||||
|| wss_block
|
||||
.message()
|
||||
.body()
|
||||
.signed_execution_payload_bid()
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
let wss_blobs_opt = harness
|
||||
.chain
|
||||
@@ -2967,15 +2980,19 @@ async fn reproduction_unaligned_checkpoint_sync_pruned_payload() {
|
||||
chain.head_snapshot().beacon_state.slot()
|
||||
);
|
||||
|
||||
let payload_exists = chain
|
||||
.store
|
||||
.execution_payload_exists(&wss_block_root)
|
||||
.unwrap_or(false);
|
||||
// In Gloas, the execution payload envelope is separate from the block and will be synced
|
||||
// from the network. We don't check for its existence here.
|
||||
if !wss_block.fork_name_unchecked().gloas_enabled() {
|
||||
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"
|
||||
);
|
||||
assert!(
|
||||
payload_exists,
|
||||
"Split block payload must exist in the new node's store after checkpoint sync"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn weak_subjectivity_sync_test(
|
||||
@@ -3013,18 +3030,17 @@ async fn weak_subjectivity_sync_test(
|
||||
.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();
|
||||
let wss_state_root = harness
|
||||
.chain
|
||||
.state_root_at_slot(checkpoint_slot)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let wss_blobs_opt = harness
|
||||
.chain
|
||||
.get_or_reconstruct_blobs(&wss_block_root)
|
||||
@@ -3101,6 +3117,20 @@ async fn weak_subjectivity_sync_test(
|
||||
.build()
|
||||
.expect("should build");
|
||||
|
||||
// Store the WSS envelope to simulate it arriving from network sync.
|
||||
// In production, the envelope would be synced from the network after checkpoint sync.
|
||||
if let Some(envelope) = harness
|
||||
.chain
|
||||
.store
|
||||
.get_payload_envelope(&wss_block.canonical_root())
|
||||
.unwrap_or(None)
|
||||
{
|
||||
beacon_chain
|
||||
.store
|
||||
.put_payload_envelope(&wss_block.canonical_root(), &envelope)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let beacon_chain = Arc::new(beacon_chain);
|
||||
let wss_block_root = wss_block.canonical_root();
|
||||
let store_wss_block = harness
|
||||
@@ -3120,6 +3150,21 @@ async fn weak_subjectivity_sync_test(
|
||||
assert_eq!(store_wss_blobs_opt, wss_blobs_opt);
|
||||
}
|
||||
|
||||
// Store the WSS block's envelope in the new chain (required for Gloas forward sync).
|
||||
// The first forward block needs the checkpoint block's envelope to determine the parent's
|
||||
// Full state.
|
||||
if let Some(envelope) = harness
|
||||
.chain
|
||||
.store
|
||||
.get_payload_envelope(&wss_block_root)
|
||||
.unwrap()
|
||||
{
|
||||
beacon_chain
|
||||
.store
|
||||
.put_payload_envelope(&wss_block_root, &envelope)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Apply blocks forward to reach head.
|
||||
let chain_dump = harness.chain.chain_dump().unwrap();
|
||||
let new_blocks = chain_dump
|
||||
@@ -3154,6 +3199,21 @@ async fn weak_subjectivity_sync_test(
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Store the envelope and apply it to fork choice.
|
||||
if let Some(envelope) = &snapshot.execution_envelope {
|
||||
beacon_chain
|
||||
.store
|
||||
.put_payload_envelope(&block_root, envelope)
|
||||
.unwrap();
|
||||
// Update fork choice so head selection accounts for Full payload status.
|
||||
beacon_chain
|
||||
.canonical_head
|
||||
.fork_choice_write_lock()
|
||||
.on_valid_payload_envelope_received(block_root)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
beacon_chain.recompute_head_at_current_slot().await;
|
||||
|
||||
// Check that the new block's state can be loaded correctly.
|
||||
@@ -3305,6 +3365,17 @@ async fn weak_subjectivity_sync_test(
|
||||
}
|
||||
assert_eq!(beacon_chain.store.get_oldest_block_slot(), 0);
|
||||
|
||||
// Store envelopes for all historic blocks (needed for dumping the chain from the new node).
|
||||
for snapshot in chain_dump.iter() {
|
||||
let block_root = snapshot.beacon_block_root;
|
||||
if let Some(envelope) = &snapshot.execution_envelope {
|
||||
beacon_chain
|
||||
.store
|
||||
.put_payload_envelope(&block_root, envelope)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check for non-aligned WSS starts, to make sure the WSS block is persisted properly
|
||||
if wss_block_slot != wss_state_slot {
|
||||
let new_node_block_root_at_wss_block = beacon_chain
|
||||
@@ -3374,13 +3445,12 @@ async fn weak_subjectivity_sync_test(
|
||||
assert_eq!(state.canonical_root().unwrap(), state_root);
|
||||
}
|
||||
|
||||
// Anchor slot is still set to the slot of the checkpoint block.
|
||||
// Note: since hot tree states the anchor slot is set to the aligned ws state slot
|
||||
// https://github.com/sigp/lighthouse/pull/6750
|
||||
let wss_aligned_slot = if checkpoint_slot % E::slots_per_epoch() == 0 {
|
||||
checkpoint_slot
|
||||
// Anchor slot is set to the WSS state slot, which is always epoch-aligned (the state is
|
||||
// advanced to an epoch boundary during checkpoint sync).
|
||||
let wss_aligned_slot = if wss_state_slot % E::slots_per_epoch() == 0 {
|
||||
wss_state_slot
|
||||
} else {
|
||||
(checkpoint_slot.epoch(E::slots_per_epoch()) + Epoch::new(1))
|
||||
(wss_state_slot.epoch(E::slots_per_epoch()) + Epoch::new(1))
|
||||
.start_slot(E::slots_per_epoch())
|
||||
};
|
||||
assert_eq!(store.get_anchor_info().anchor_slot, wss_aligned_slot);
|
||||
@@ -3635,6 +3705,10 @@ async fn test_import_historical_data_columns_batch_no_block_found() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.fulu_enabled()) {
|
||||
return;
|
||||
};
|
||||
// TODO(Gloas): blocks don't have blob_kzg_commitments (blobs are in the execution payload envelope).
|
||||
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let spec = test_spec::<E>();
|
||||
let db_path = tempdir().unwrap();
|
||||
@@ -3745,12 +3819,14 @@ async fn process_blocks_and_attestations_for_unaligned_checkpoint() {
|
||||
|
||||
let all_validators = (0..LOW_VALIDATOR_COUNT).collect::<Vec<_>>();
|
||||
|
||||
let split_slot = Slot::new(E::slots_per_epoch() * 4);
|
||||
let finalized_epoch_start_slot = Slot::new(E::slots_per_epoch() * 4);
|
||||
let pre_skips = 1;
|
||||
let post_skips = 1;
|
||||
|
||||
// Build the chain up to the intended split slot, with 3 skips before the split.
|
||||
let slots = (1..=split_slot.as_u64() - pre_skips)
|
||||
let split_slot = finalized_epoch_start_slot;
|
||||
|
||||
// Build the chain up to the intended finalized epoch slot, with 1 skip before the split.
|
||||
let slots = (1..=finalized_epoch_start_slot.as_u64() - pre_skips)
|
||||
.map(Slot::new)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -3769,20 +3845,26 @@ async fn process_blocks_and_attestations_for_unaligned_checkpoint() {
|
||||
//
|
||||
// - one that is invalid because it conflicts with finalization (slot <= finalized_slot)
|
||||
// - one that is valid because its slot is not finalized (slot > finalized_slot)
|
||||
//
|
||||
// Note: block verification uses finalized_checkpoint.epoch.start_slot() (==
|
||||
// finalized_epoch_start_slot) for the finalized slot check.
|
||||
let (unadvanced_split_state, unadvanced_split_state_root) =
|
||||
harness.get_current_state_and_root();
|
||||
|
||||
let ((invalid_fork_block, _), _) = harness
|
||||
.make_block(unadvanced_split_state.clone(), split_slot)
|
||||
.make_block(unadvanced_split_state.clone(), finalized_epoch_start_slot)
|
||||
.await;
|
||||
let ((valid_fork_block, _), _) = harness
|
||||
.make_block(unadvanced_split_state.clone(), split_slot + 1)
|
||||
.make_block(
|
||||
unadvanced_split_state.clone(),
|
||||
finalized_epoch_start_slot + 1,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Advance the chain so that the intended split slot is finalized.
|
||||
// Do not attest in the epoch boundary slot, to make attestation production later easier (no
|
||||
// equivocations).
|
||||
let finalizing_slot = split_slot + 2 * E::slots_per_epoch();
|
||||
let finalizing_slot = finalized_epoch_start_slot + 2 * E::slots_per_epoch();
|
||||
for _ in 0..pre_skips + post_skips {
|
||||
harness.advance_slot();
|
||||
}
|
||||
@@ -3834,12 +3916,7 @@ async fn process_blocks_and_attestations_for_unaligned_checkpoint() {
|
||||
let (split_state_root, mut advanced_split_state) = harness
|
||||
.chain
|
||||
.store
|
||||
.get_advanced_hot_state(
|
||||
split.block_root,
|
||||
StatePayloadStatus::Pending,
|
||||
split.slot,
|
||||
split.state_root,
|
||||
)
|
||||
.get_advanced_hot_state(split.block_root, split.slot, split.state_root)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
complete_state_advance(
|
||||
@@ -3973,6 +4050,7 @@ async fn schema_downgrade_to_min_version(store_config: StoreConfig, archive: boo
|
||||
let num_blocks_produced = E::slots_per_epoch() * 4;
|
||||
let db_path = tempdir().unwrap();
|
||||
let spec = test_spec::<E>();
|
||||
let is_gloas = spec.is_gloas_scheduled();
|
||||
|
||||
let chain_config = ChainConfig {
|
||||
archive,
|
||||
@@ -3995,7 +4073,11 @@ async fn schema_downgrade_to_min_version(store_config: StoreConfig, archive: boo
|
||||
)
|
||||
.await;
|
||||
|
||||
let min_version = SchemaVersion(28);
|
||||
let min_version = if is_gloas {
|
||||
SchemaVersion(29)
|
||||
} else {
|
||||
SchemaVersion(28)
|
||||
};
|
||||
|
||||
// Save the slot clock so that the new harness doesn't revert in time.
|
||||
let slot_clock = harness.chain.slot_clock.clone();
|
||||
@@ -4565,6 +4647,10 @@ async fn fulu_prune_data_columns_happy_case() {
|
||||
// No-op if PeerDAS not scheduled.
|
||||
return;
|
||||
}
|
||||
// TODO(Gloas): blocks don't have blob_kzg_commitments (blobs are in the execution payload envelope).
|
||||
if store.get_chain_spec().is_gloas_scheduled() {
|
||||
return;
|
||||
}
|
||||
let Some(fulu_fork_epoch) = store.get_chain_spec().fulu_fork_epoch else {
|
||||
// No-op prior to Fulu.
|
||||
return;
|
||||
@@ -4620,6 +4706,10 @@ async fn fulu_prune_data_columns_no_finalization() {
|
||||
// No-op if PeerDAS not scheduled.
|
||||
return;
|
||||
}
|
||||
// TODO(Gloas): blocks don't have blob_kzg_commitments (blobs are in the execution payload envelope).
|
||||
if store.get_chain_spec().is_gloas_scheduled() {
|
||||
return;
|
||||
}
|
||||
let Some(fulu_fork_epoch) = store.get_chain_spec().fulu_fork_epoch else {
|
||||
// No-op prior to Fulu.
|
||||
return;
|
||||
@@ -4839,6 +4929,10 @@ async fn fulu_prune_data_columns_margin_test(margin: u64) {
|
||||
// No-op if PeerDAS not scheduled.
|
||||
return;
|
||||
}
|
||||
// TODO(Gloas): blocks don't have blob_kzg_commitments (blobs are in the execution payload envelope).
|
||||
if store.get_chain_spec().is_gloas_scheduled() {
|
||||
return;
|
||||
}
|
||||
let Some(fulu_fork_epoch) = store.get_chain_spec().fulu_fork_epoch else {
|
||||
// No-op prior to Fulu.
|
||||
return;
|
||||
@@ -5156,6 +5250,10 @@ async fn test_custody_column_filtering_regular_node() {
|
||||
if !test_spec::<E>().is_peer_das_scheduled() {
|
||||
return;
|
||||
}
|
||||
// TODO(Gloas): blocks don't have blob_kzg_commitments (blobs are in the execution payload envelope).
|
||||
if test_spec::<E>().is_gloas_scheduled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store(&db_path);
|
||||
@@ -5200,6 +5298,10 @@ async fn test_custody_column_filtering_supernode() {
|
||||
if !test_spec::<E>().is_peer_das_scheduled() {
|
||||
return;
|
||||
}
|
||||
// TODO(Gloas): blocks don't have blob_kzg_commitments (blobs are in the execution payload envelope).
|
||||
if test_spec::<E>().is_gloas_scheduled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store(&db_path);
|
||||
@@ -5515,7 +5617,7 @@ async fn test_gloas_block_and_envelope_storage_generic(
|
||||
let mut state = genesis_state;
|
||||
|
||||
let mut block_roots = vec![];
|
||||
let mut stored_states = vec![(Slot::new(0), StatePayloadStatus::Full, genesis_state_root)];
|
||||
let mut stored_states = vec![(Slot::new(0), genesis_state_root)];
|
||||
|
||||
for i in 1..=num_slots {
|
||||
let slot = Slot::new(i);
|
||||
@@ -5527,10 +5629,10 @@ async fn test_gloas_block_and_envelope_storage_generic(
|
||||
|
||||
let state_root = state.canonical_root().unwrap();
|
||||
store.put_state(&state_root, &state).unwrap();
|
||||
stored_states.push((slot, state.payload_status(), state_root));
|
||||
stored_states.push((slot, state_root));
|
||||
}
|
||||
|
||||
let (block_contents, envelope, mut pending_state) =
|
||||
let (block_contents, envelope, mut post_block_state) =
|
||||
harness.make_block_with_envelope(state, slot).await;
|
||||
let block_root = block_contents.0.canonical_root();
|
||||
|
||||
@@ -5540,21 +5642,17 @@ async fn test_gloas_block_and_envelope_storage_generic(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let pending_state_root = pending_state.update_tree_hash_cache().unwrap();
|
||||
stored_states.push((slot, StatePayloadStatus::Pending, pending_state_root));
|
||||
let state_root = post_block_state.update_tree_hash_cache().unwrap();
|
||||
stored_states.push((slot, state_root));
|
||||
|
||||
// Process the envelope.
|
||||
let envelope = envelope.expect("Gloas block should have envelope");
|
||||
let mut full_state = pending_state.clone();
|
||||
let envelope_state_root = envelope.message.state_root;
|
||||
let full_state_root = harness
|
||||
.process_envelope(block_root, envelope, &mut full_state)
|
||||
harness
|
||||
.process_envelope(block_root, envelope, &post_block_state, state_root)
|
||||
.await;
|
||||
assert_eq!(full_state_root, envelope_state_root);
|
||||
stored_states.push((slot, StatePayloadStatus::Full, full_state_root));
|
||||
|
||||
block_roots.push(block_root);
|
||||
state = full_state;
|
||||
state = post_block_state;
|
||||
}
|
||||
|
||||
// Verify block storage.
|
||||
@@ -5577,20 +5675,15 @@ async fn test_gloas_block_and_envelope_storage_generic(
|
||||
|
||||
// Verify state storage.
|
||||
// Iterate in reverse order to frustrate the cache.
|
||||
for (slot, payload_status, state_root) in stored_states.into_iter().rev() {
|
||||
for (slot, state_root) in stored_states.into_iter().rev() {
|
||||
println!("{slot}: {state_root:?}");
|
||||
let Some(mut loaded_state) = store
|
||||
.get_state(&state_root, Some(slot), CACHE_STATE_IN_TESTS)
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("missing {payload_status:?} state at slot {slot} with root {state_root:?}");
|
||||
panic!("missing state at slot {slot} with root {state_root:?}");
|
||||
};
|
||||
assert_eq!(loaded_state.slot(), slot);
|
||||
assert_eq!(
|
||||
loaded_state.payload_status(),
|
||||
payload_status,
|
||||
"slot = {slot}"
|
||||
);
|
||||
assert_eq!(
|
||||
loaded_state.canonical_root().unwrap(),
|
||||
state_root,
|
||||
@@ -5600,74 +5693,6 @@ async fn test_gloas_block_and_envelope_storage_generic(
|
||||
check_db_invariants(&harness);
|
||||
}
|
||||
|
||||
/// Test that Pending and Full states have the correct payload status through round-trip
|
||||
/// storage and retrieval.
|
||||
#[tokio::test]
|
||||
async fn test_gloas_state_payload_status() {
|
||||
if !fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store(&db_path);
|
||||
let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT);
|
||||
|
||||
let num_blocks = 6u64;
|
||||
let (genesis_state, _genesis_state_root) = harness.get_current_state_and_root();
|
||||
let mut state = genesis_state;
|
||||
|
||||
for i in 1..=num_blocks {
|
||||
let slot = Slot::new(i);
|
||||
harness.advance_slot();
|
||||
|
||||
let (block_contents, envelope, pending_state) =
|
||||
harness.make_block_with_envelope(state, slot).await;
|
||||
let block_root = block_contents.0.canonical_root();
|
||||
|
||||
harness
|
||||
.process_block(slot, block_root, block_contents)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Verify the pending state has correct payload status.
|
||||
assert_eq!(
|
||||
pending_state.payload_status(),
|
||||
StatePayloadStatus::Pending,
|
||||
"pending state at slot {} should be Pending",
|
||||
i
|
||||
);
|
||||
|
||||
// Process the envelope and verify the full state has correct payload status.
|
||||
let envelope = envelope.expect("Gloas block should have envelope");
|
||||
let mut full_state = pending_state;
|
||||
let full_state_root = harness
|
||||
.process_envelope(block_root, envelope, &mut full_state)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
full_state.payload_status(),
|
||||
StatePayloadStatus::Full,
|
||||
"full state at slot {} should be Full",
|
||||
i
|
||||
);
|
||||
|
||||
// Round-trip: load the full state from DB and check status.
|
||||
let loaded_full = store
|
||||
.get_state(&full_state_root, None, CACHE_STATE_IN_TESTS)
|
||||
.unwrap()
|
||||
.expect("full state should exist in DB");
|
||||
assert_eq!(
|
||||
loaded_full.payload_status(),
|
||||
StatePayloadStatus::Full,
|
||||
"loaded full state at slot {} should be Full after round-trip",
|
||||
i
|
||||
);
|
||||
|
||||
state = full_state;
|
||||
}
|
||||
check_db_invariants(&harness);
|
||||
}
|
||||
|
||||
/// Test block replay with and without envelopes.
|
||||
#[tokio::test]
|
||||
async fn test_gloas_block_replay_with_envelopes() {
|
||||
@@ -5704,11 +5729,11 @@ async fn test_gloas_block_replay_with_envelopes() {
|
||||
pending_states.insert(slot, (pending_state_root, pending_state.clone()));
|
||||
|
||||
let envelope = envelope.expect("Gloas block should have envelope");
|
||||
let mut full_state = pending_state;
|
||||
let full_state_root = harness
|
||||
.process_envelope(block_root, envelope, &mut full_state)
|
||||
let full_state = pending_state;
|
||||
harness
|
||||
.process_envelope(block_root, envelope, &full_state, pending_state_root)
|
||||
.await;
|
||||
full_states.insert(slot, (full_state_root, full_state.clone()));
|
||||
full_states.insert(slot, (pending_state_root, full_state.clone()));
|
||||
|
||||
last_block_root = block_root;
|
||||
state = full_state;
|
||||
@@ -5716,94 +5741,29 @@ async fn test_gloas_block_replay_with_envelopes() {
|
||||
|
||||
let end_slot = Slot::new(num_blocks);
|
||||
|
||||
// Load blocks for Pending replay (no envelopes for the last block).
|
||||
let (blocks_pending, envelopes_pending) = store
|
||||
.load_blocks_to_replay(
|
||||
Slot::new(0),
|
||||
end_slot,
|
||||
last_block_root,
|
||||
StatePayloadStatus::Pending,
|
||||
)
|
||||
// Load blocks for replay.
|
||||
let blocks = store
|
||||
.load_blocks_to_replay(Slot::new(0), end_slot, last_block_root)
|
||||
.unwrap();
|
||||
assert!(
|
||||
!blocks_pending.is_empty(),
|
||||
"should have blocks for pending replay"
|
||||
);
|
||||
// For Pending, no envelope for the first block (slot 0) or last block; envelopes for
|
||||
// intermediate blocks whose payloads are canonical.
|
||||
let expected_pending_envelopes = blocks_pending.len().saturating_sub(2);
|
||||
assert!(!blocks.is_empty(), "should have blocks for replay");
|
||||
|
||||
// Replay blocks and verify against the expected state.
|
||||
let mut replayed = BlockReplayer::<MinimalEthSpec>::new(genesis_state, store.get_chain_spec())
|
||||
.no_signature_verification()
|
||||
.minimal_block_root_verification()
|
||||
.apply_blocks(blocks, None)
|
||||
.expect("should replay blocks")
|
||||
.into_state();
|
||||
replayed.apply_pending_mutations().unwrap();
|
||||
|
||||
let (_, mut expected) = pending_states.get(&end_slot).unwrap().clone();
|
||||
expected.apply_pending_mutations().unwrap();
|
||||
|
||||
replayed.drop_all_caches().unwrap();
|
||||
expected.drop_all_caches().unwrap();
|
||||
assert_eq!(
|
||||
envelopes_pending.len(),
|
||||
expected_pending_envelopes,
|
||||
"pending replay should have envelopes for all blocks except the last"
|
||||
);
|
||||
assert!(
|
||||
blocks_pending
|
||||
.iter()
|
||||
.skip(1)
|
||||
.take(envelopes_pending.len())
|
||||
.map(|block| block.slot())
|
||||
.eq(envelopes_pending
|
||||
.iter()
|
||||
.map(|envelope| envelope.message.slot)),
|
||||
"block and envelope slots should match"
|
||||
);
|
||||
|
||||
// Load blocks for Full replay (envelopes for all blocks including the last).
|
||||
let (blocks_full, envelopes_full) = store
|
||||
.load_blocks_to_replay(
|
||||
Slot::new(0),
|
||||
end_slot,
|
||||
last_block_root,
|
||||
StatePayloadStatus::Full,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
envelopes_full.len(),
|
||||
expected_pending_envelopes + 1,
|
||||
"full replay should have one more envelope than pending replay"
|
||||
);
|
||||
|
||||
// Replay to Pending state and verify.
|
||||
let mut replayed_pending =
|
||||
BlockReplayer::<MinimalEthSpec>::new(genesis_state.clone(), store.get_chain_spec())
|
||||
.no_signature_verification()
|
||||
.minimal_block_root_verification()
|
||||
.desired_state_payload_status(StatePayloadStatus::Pending)
|
||||
.apply_blocks(blocks_pending, envelopes_pending, None)
|
||||
.expect("should replay blocks to pending state")
|
||||
.into_state();
|
||||
replayed_pending.apply_pending_mutations().unwrap();
|
||||
|
||||
let (_, mut expected_pending) = pending_states.get(&end_slot).unwrap().clone();
|
||||
expected_pending.apply_pending_mutations().unwrap();
|
||||
|
||||
replayed_pending.drop_all_caches().unwrap();
|
||||
expected_pending.drop_all_caches().unwrap();
|
||||
assert_eq!(
|
||||
replayed_pending, expected_pending,
|
||||
"replayed pending state should match stored pending state"
|
||||
);
|
||||
|
||||
// Replay to Full state and verify.
|
||||
let mut replayed_full =
|
||||
BlockReplayer::<MinimalEthSpec>::new(genesis_state, store.get_chain_spec())
|
||||
.no_signature_verification()
|
||||
.minimal_block_root_verification()
|
||||
.desired_state_payload_status(StatePayloadStatus::Full)
|
||||
.apply_blocks(blocks_full, envelopes_full, None)
|
||||
.expect("should replay blocks to full state")
|
||||
.into_state();
|
||||
replayed_full.apply_pending_mutations().unwrap();
|
||||
|
||||
let (_, mut expected_full) = full_states.get(&end_slot).unwrap().clone();
|
||||
expected_full.apply_pending_mutations().unwrap();
|
||||
|
||||
replayed_full.drop_all_caches().unwrap();
|
||||
expected_full.drop_all_caches().unwrap();
|
||||
assert_eq!(
|
||||
replayed_full, expected_full,
|
||||
"replayed full state should match stored full state"
|
||||
replayed, expected,
|
||||
"replayed state should match stored state"
|
||||
);
|
||||
check_db_invariants(&harness);
|
||||
}
|
||||
@@ -5836,40 +5796,43 @@ async fn test_gloas_hot_state_hierarchy() {
|
||||
let slot = Slot::new(i);
|
||||
harness.advance_slot();
|
||||
|
||||
let (block_contents, envelope, pending_state) =
|
||||
let (block_contents, envelope, mut pending_state) =
|
||||
harness.make_block_with_envelope(state.clone(), slot).await;
|
||||
let block_root = block_contents.0.canonical_root();
|
||||
|
||||
// Attest to previous block before processing next.
|
||||
if i > 1 {
|
||||
let state_root = state.update_tree_hash_cache().unwrap();
|
||||
harness.attest_block(
|
||||
&state,
|
||||
state_root,
|
||||
last_block_root.into(),
|
||||
&block_contents.0,
|
||||
&some_validators,
|
||||
);
|
||||
}
|
||||
let signed_block = block_contents.0.clone();
|
||||
|
||||
harness
|
||||
.process_block(slot, block_root, block_contents)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Attest to the current block at its own slot (same-slot attestation).
|
||||
// In Gloas, same-slot attestations have index=0 and route to Pending in
|
||||
// fork choice, correctly propagating weight through the Full path.
|
||||
// Use pending_state (at slot i) so the target root resolves correctly.
|
||||
let pending_state_root = pending_state.update_tree_hash_cache().unwrap();
|
||||
harness.attest_block(
|
||||
&pending_state,
|
||||
pending_state_root,
|
||||
block_root.into(),
|
||||
&signed_block,
|
||||
&some_validators,
|
||||
);
|
||||
|
||||
let envelope = envelope.expect("Gloas block should have envelope");
|
||||
let mut full_state = pending_state;
|
||||
let full_state = pending_state;
|
||||
harness
|
||||
.process_envelope(block_root, envelope, &mut full_state)
|
||||
.process_envelope(block_root, envelope, &full_state, pending_state_root)
|
||||
.await;
|
||||
|
||||
last_block_root = block_root;
|
||||
state = full_state;
|
||||
}
|
||||
|
||||
// Verify states can be loaded and have correct payload status.
|
||||
let _head_state = harness.get_current_state();
|
||||
let _head_slot = harness.head_slot();
|
||||
// Head should be the block at slot 40 with full payload.
|
||||
let head = harness.chain.canonical_head.cached_head();
|
||||
assert_eq!(head.head_block_root(), last_block_root);
|
||||
assert_eq!(head.head_payload_status(), PayloadStatus::Full);
|
||||
|
||||
// States at all slots on the canonical chain should be retrievable.
|
||||
for slot_num in 1..=num_blocks {
|
||||
@@ -5880,7 +5843,7 @@ async fn test_gloas_hot_state_hierarchy() {
|
||||
let mut loaded_state = store
|
||||
.get_state(&state_root, Some(slot), CACHE_STATE_IN_TESTS)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
.unwrap_or_else(|| panic!("missing state at {slot}/{state_root:?}"));
|
||||
assert_eq!(loaded_state.canonical_root().unwrap(), state_root);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user