mirror of
https://github.com/sigp/lighthouse.git
synced 2026-06-29 10:54:24 +00:00
Merge branch 'unstable' of github.com:sigp/lighthouse into gloas-fc-proto
This commit is contained in:
@@ -14,6 +14,7 @@ use beacon_chain::{
|
||||
},
|
||||
};
|
||||
use bls::{AggregateSignature, Keypair, SecretKey};
|
||||
use execution_layer::test_utils::generate_genesis_header;
|
||||
use fixed_bytes::FixedBytesExtended;
|
||||
use genesis::{DEFAULT_ETH1_BLOCK_HASH, interop_genesis_state};
|
||||
use int_to_bytes::int_to_bytes32;
|
||||
@@ -79,11 +80,13 @@ fn get_harness_capella_spec(
|
||||
let spec = Arc::new(spec);
|
||||
|
||||
let validator_keypairs = KEYPAIRS[0..validator_count].to_vec();
|
||||
// Use the proper genesis execution payload header that matches the mock execution layer
|
||||
let execution_payload_header = generate_genesis_header(&spec);
|
||||
let genesis_state = interop_genesis_state(
|
||||
&validator_keypairs,
|
||||
HARNESS_GENESIS_TIME,
|
||||
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
|
||||
None,
|
||||
execution_payload_header,
|
||||
&spec,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -106,11 +109,6 @@ fn get_harness_capella_spec(
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
harness
|
||||
.execution_block_generator()
|
||||
.move_to_terminal_block()
|
||||
.unwrap();
|
||||
|
||||
harness.advance_slot();
|
||||
|
||||
(harness, spec)
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
#![cfg(not(debug_assertions))] // Tests run too slow in debug.
|
||||
|
||||
use beacon_chain::test_utils::BeaconChainHarness;
|
||||
use execution_layer::test_utils::{Block, DEFAULT_TERMINAL_BLOCK, generate_pow_block};
|
||||
use types::*;
|
||||
|
||||
const VALIDATOR_COUNT: usize = 32;
|
||||
|
||||
type E = MainnetEthSpec;
|
||||
|
||||
fn verify_execution_payload_chain<E: EthSpec>(chain: &[FullPayload<E>]) {
|
||||
let mut prev_ep: Option<FullPayload<E>> = None;
|
||||
|
||||
for ep in chain {
|
||||
assert!(!ep.is_default_with_empty_roots());
|
||||
assert!(ep.block_hash() != ExecutionBlockHash::zero());
|
||||
|
||||
// Check against previous `ExecutionPayload`.
|
||||
if let Some(prev_ep) = prev_ep {
|
||||
assert_eq!(prev_ep.block_hash(), ep.parent_hash());
|
||||
assert_eq!(prev_ep.block_number() + 1, ep.block_number());
|
||||
assert!(ep.timestamp() > prev_ep.timestamp());
|
||||
}
|
||||
prev_ep = Some(ep.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
// TODO(merge): This isn't working cause the non-zero values in `initialize_beacon_state_from_eth1`
|
||||
// are causing failed lookups to the execution node. I need to come back to this.
|
||||
#[should_panic]
|
||||
async fn merge_with_terminal_block_hash_override() {
|
||||
let altair_fork_epoch = Epoch::new(0);
|
||||
let bellatrix_fork_epoch = Epoch::new(0);
|
||||
|
||||
let mut spec = E::default_spec();
|
||||
spec.altair_fork_epoch = Some(altair_fork_epoch);
|
||||
spec.bellatrix_fork_epoch = Some(bellatrix_fork_epoch);
|
||||
|
||||
let genesis_pow_block_hash = generate_pow_block(
|
||||
spec.terminal_total_difficulty,
|
||||
DEFAULT_TERMINAL_BLOCK,
|
||||
0,
|
||||
ExecutionBlockHash::zero(),
|
||||
)
|
||||
.unwrap()
|
||||
.block_hash;
|
||||
|
||||
spec.terminal_block_hash = genesis_pow_block_hash;
|
||||
|
||||
let harness = BeaconChainHarness::builder(E::default())
|
||||
.spec(spec.into())
|
||||
.deterministic_keypairs(VALIDATOR_COUNT)
|
||||
.fresh_ephemeral_store()
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
assert_eq!(
|
||||
harness
|
||||
.execution_block_generator()
|
||||
.latest_block()
|
||||
.unwrap()
|
||||
.block_hash(),
|
||||
genesis_pow_block_hash,
|
||||
"pre-condition"
|
||||
);
|
||||
|
||||
assert!(
|
||||
harness
|
||||
.chain
|
||||
.head_snapshot()
|
||||
.beacon_block
|
||||
.as_bellatrix()
|
||||
.is_ok(),
|
||||
"genesis block should be a bellatrix block"
|
||||
);
|
||||
|
||||
let mut execution_payloads = vec![];
|
||||
for i in 0..E::slots_per_epoch() * 3 {
|
||||
harness.extend_slots(1).await;
|
||||
|
||||
let block = &harness.chain.head_snapshot().beacon_block;
|
||||
|
||||
let execution_payload = block.message().body().execution_payload().unwrap();
|
||||
if i == 0 {
|
||||
assert_eq!(execution_payload.block_hash(), genesis_pow_block_hash);
|
||||
}
|
||||
execution_payloads.push(execution_payload.into());
|
||||
}
|
||||
|
||||
verify_execution_payload_chain(execution_payloads.as_slice());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn base_altair_bellatrix_with_terminal_block_after_fork() {
|
||||
let altair_fork_epoch = Epoch::new(4);
|
||||
let altair_fork_slot = altair_fork_epoch.start_slot(E::slots_per_epoch());
|
||||
let bellatrix_fork_epoch = Epoch::new(8);
|
||||
let bellatrix_fork_slot = bellatrix_fork_epoch.start_slot(E::slots_per_epoch());
|
||||
|
||||
let mut spec = E::default_spec();
|
||||
spec.altair_fork_epoch = Some(altair_fork_epoch);
|
||||
spec.bellatrix_fork_epoch = Some(bellatrix_fork_epoch);
|
||||
|
||||
let mut execution_payloads = vec![];
|
||||
|
||||
let harness = BeaconChainHarness::builder(E::default())
|
||||
.spec(spec.into())
|
||||
.deterministic_keypairs(VALIDATOR_COUNT)
|
||||
.fresh_ephemeral_store()
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
/*
|
||||
* Start with the base fork.
|
||||
*/
|
||||
|
||||
assert!(harness.chain.head_snapshot().beacon_block.as_base().is_ok());
|
||||
|
||||
/*
|
||||
* Do the Altair fork.
|
||||
*/
|
||||
|
||||
harness.extend_to_slot(altair_fork_slot).await;
|
||||
|
||||
let altair_head = &harness.chain.head_snapshot().beacon_block;
|
||||
assert!(altair_head.as_altair().is_ok());
|
||||
assert_eq!(altair_head.slot(), altair_fork_slot);
|
||||
|
||||
/*
|
||||
* Do the Bellatrix fork, without a terminal PoW block.
|
||||
*/
|
||||
|
||||
Box::pin(harness.extend_to_slot(bellatrix_fork_slot)).await;
|
||||
|
||||
let bellatrix_head = &harness.chain.head_snapshot().beacon_block;
|
||||
assert!(bellatrix_head.as_bellatrix().is_ok());
|
||||
assert_eq!(bellatrix_head.slot(), bellatrix_fork_slot);
|
||||
assert!(
|
||||
bellatrix_head
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.unwrap()
|
||||
.is_default_with_empty_roots(),
|
||||
"Bellatrix head is default payload"
|
||||
);
|
||||
|
||||
/*
|
||||
* Next Bellatrix block shouldn't include an exec payload.
|
||||
*/
|
||||
|
||||
harness.extend_slots(1).await;
|
||||
|
||||
let one_after_bellatrix_head = &harness.chain.head_snapshot().beacon_block;
|
||||
assert!(
|
||||
one_after_bellatrix_head
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.unwrap()
|
||||
.is_default_with_empty_roots(),
|
||||
"One after bellatrix head is default payload"
|
||||
);
|
||||
assert_eq!(one_after_bellatrix_head.slot(), bellatrix_fork_slot + 1);
|
||||
|
||||
/*
|
||||
* Trigger the terminal PoW block.
|
||||
*/
|
||||
|
||||
harness
|
||||
.execution_block_generator()
|
||||
.move_to_terminal_block()
|
||||
.unwrap();
|
||||
|
||||
// Add a slot duration to get to the next slot
|
||||
let timestamp = harness.get_timestamp_at_slot() + harness.spec.get_slot_duration().as_secs();
|
||||
|
||||
harness
|
||||
.execution_block_generator()
|
||||
.modify_last_block(|block| {
|
||||
if let Block::PoW(terminal_block) = block {
|
||||
terminal_block.timestamp = timestamp;
|
||||
}
|
||||
});
|
||||
|
||||
harness.extend_slots(1).await;
|
||||
|
||||
let two_after_bellatrix_head = &harness.chain.head_snapshot().beacon_block;
|
||||
assert!(
|
||||
two_after_bellatrix_head
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.unwrap()
|
||||
.is_default_with_empty_roots(),
|
||||
"Two after bellatrix head is default payload"
|
||||
);
|
||||
assert_eq!(two_after_bellatrix_head.slot(), bellatrix_fork_slot + 2);
|
||||
|
||||
/*
|
||||
* Next Bellatrix block should include an exec payload.
|
||||
*/
|
||||
for _ in 0..4 {
|
||||
harness.extend_slots(1).await;
|
||||
|
||||
let block = &harness.chain.head_snapshot().beacon_block;
|
||||
execution_payloads.push(block.message().body().execution_payload().unwrap().into());
|
||||
}
|
||||
|
||||
verify_execution_payload_chain(execution_payloads.as_slice());
|
||||
}
|
||||
@@ -20,10 +20,9 @@ use fixed_bytes::FixedBytesExtended;
|
||||
use logging::create_test_tracing_subscriber;
|
||||
use slasher::{Config as SlasherConfig, Slasher};
|
||||
use state_processing::{
|
||||
BlockProcessingError, ConsensusContext, VerifyBlockRoot,
|
||||
BlockProcessingError, BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot,
|
||||
common::{attesting_indices_base, attesting_indices_electra},
|
||||
per_block_processing::{BlockSignatureStrategy, per_block_processing},
|
||||
per_slot_processing,
|
||||
per_block_processing, per_slot_processing,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
@@ -1849,10 +1848,8 @@ async fn add_altair_block_to_base_chain() {
|
||||
// https://github.com/sigp/lighthouse/issues/4332#issuecomment-1565092279
|
||||
#[tokio::test]
|
||||
async fn import_duplicate_block_unrealized_justification() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
|
||||
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
||||
.spec(spec.into())
|
||||
.default_spec()
|
||||
.keypairs(KEYPAIRS[..].to_vec())
|
||||
.fresh_ephemeral_store()
|
||||
.mock_execution_layer()
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
#![cfg(not(debug_assertions))] // Tests run too slow in debug.
|
||||
|
||||
use beacon_chain::test_utils::BeaconChainHarness;
|
||||
use execution_layer::test_utils::Block;
|
||||
use types::*;
|
||||
|
||||
const VALIDATOR_COUNT: usize = 32;
|
||||
type E = MainnetEthSpec;
|
||||
|
||||
fn verify_execution_payload_chain<E: EthSpec>(chain: &[FullPayload<E>]) {
|
||||
let mut prev_ep: Option<FullPayload<E>> = None;
|
||||
|
||||
for ep in chain {
|
||||
assert!(!ep.is_default_with_empty_roots());
|
||||
assert!(ep.block_hash() != ExecutionBlockHash::zero());
|
||||
|
||||
// Check against previous `ExecutionPayload`.
|
||||
if let Some(prev_ep) = prev_ep {
|
||||
assert_eq!(prev_ep.block_hash(), ep.parent_hash());
|
||||
assert_eq!(prev_ep.block_number() + 1, ep.block_number());
|
||||
assert!(ep.timestamp() > prev_ep.timestamp());
|
||||
}
|
||||
prev_ep = Some(ep.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn base_altair_bellatrix_capella() {
|
||||
let altair_fork_epoch = Epoch::new(4);
|
||||
let altair_fork_slot = altair_fork_epoch.start_slot(E::slots_per_epoch());
|
||||
let bellatrix_fork_epoch = Epoch::new(8);
|
||||
let bellatrix_fork_slot = bellatrix_fork_epoch.start_slot(E::slots_per_epoch());
|
||||
let capella_fork_epoch = Epoch::new(12);
|
||||
let capella_fork_slot = capella_fork_epoch.start_slot(E::slots_per_epoch());
|
||||
|
||||
let mut spec = E::default_spec();
|
||||
spec.altair_fork_epoch = Some(altair_fork_epoch);
|
||||
spec.bellatrix_fork_epoch = Some(bellatrix_fork_epoch);
|
||||
spec.capella_fork_epoch = Some(capella_fork_epoch);
|
||||
|
||||
let harness = BeaconChainHarness::builder(E::default())
|
||||
.spec(spec.into())
|
||||
.deterministic_keypairs(VALIDATOR_COUNT)
|
||||
.fresh_ephemeral_store()
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
/*
|
||||
* Start with the base fork.
|
||||
*/
|
||||
assert!(harness.chain.head_snapshot().beacon_block.as_base().is_ok());
|
||||
|
||||
/*
|
||||
* Do the Altair fork.
|
||||
*/
|
||||
Box::pin(harness.extend_to_slot(altair_fork_slot)).await;
|
||||
|
||||
let altair_head = &harness.chain.head_snapshot().beacon_block;
|
||||
assert!(altair_head.as_altair().is_ok());
|
||||
assert_eq!(altair_head.slot(), altair_fork_slot);
|
||||
|
||||
/*
|
||||
* Do the Bellatrix fork, without a terminal PoW block.
|
||||
*/
|
||||
Box::pin(harness.extend_to_slot(bellatrix_fork_slot)).await;
|
||||
|
||||
let bellatrix_head = &harness.chain.head_snapshot().beacon_block;
|
||||
assert!(bellatrix_head.as_bellatrix().is_ok());
|
||||
assert_eq!(bellatrix_head.slot(), bellatrix_fork_slot);
|
||||
assert!(
|
||||
bellatrix_head
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.unwrap()
|
||||
.is_default_with_empty_roots(),
|
||||
"Bellatrix head is default payload"
|
||||
);
|
||||
|
||||
/*
|
||||
* Next Bellatrix block shouldn't include an exec payload.
|
||||
*/
|
||||
Box::pin(harness.extend_slots(1)).await;
|
||||
|
||||
let one_after_bellatrix_head = &harness.chain.head_snapshot().beacon_block;
|
||||
assert!(
|
||||
one_after_bellatrix_head
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.unwrap()
|
||||
.is_default_with_empty_roots(),
|
||||
"One after bellatrix head is default payload"
|
||||
);
|
||||
assert_eq!(one_after_bellatrix_head.slot(), bellatrix_fork_slot + 1);
|
||||
|
||||
/*
|
||||
* Trigger the terminal PoW block.
|
||||
*/
|
||||
harness
|
||||
.execution_block_generator()
|
||||
.move_to_terminal_block()
|
||||
.unwrap();
|
||||
|
||||
// Add a slot duration to get to the next slot
|
||||
let timestamp = harness.get_timestamp_at_slot() + harness.spec.get_slot_duration().as_secs();
|
||||
harness
|
||||
.execution_block_generator()
|
||||
.modify_last_block(|block| {
|
||||
if let Block::PoW(terminal_block) = block {
|
||||
terminal_block.timestamp = timestamp;
|
||||
}
|
||||
});
|
||||
Box::pin(harness.extend_slots(1)).await;
|
||||
|
||||
let two_after_bellatrix_head = &harness.chain.head_snapshot().beacon_block;
|
||||
assert!(
|
||||
two_after_bellatrix_head
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.unwrap()
|
||||
.is_default_with_empty_roots(),
|
||||
"Two after bellatrix head is default payload"
|
||||
);
|
||||
assert_eq!(two_after_bellatrix_head.slot(), bellatrix_fork_slot + 2);
|
||||
|
||||
/*
|
||||
* Next Bellatrix block should include an exec payload.
|
||||
*/
|
||||
let mut execution_payloads = vec![];
|
||||
for _ in (bellatrix_fork_slot.as_u64() + 3)..capella_fork_slot.as_u64() {
|
||||
harness.extend_slots(1).await;
|
||||
let block = &harness.chain.head_snapshot().beacon_block;
|
||||
let full_payload: FullPayload<E> =
|
||||
block.message().body().execution_payload().unwrap().into();
|
||||
// pre-capella shouldn't have withdrawals
|
||||
assert!(full_payload.withdrawals_root().is_err());
|
||||
execution_payloads.push(full_payload);
|
||||
}
|
||||
|
||||
/*
|
||||
* Should enter capella fork now.
|
||||
*/
|
||||
for _ in 0..16 {
|
||||
harness.extend_slots(1).await;
|
||||
let block = &harness.chain.head_snapshot().beacon_block;
|
||||
let full_payload: FullPayload<E> =
|
||||
block.message().body().execution_payload().unwrap().into();
|
||||
// post-capella should have withdrawals
|
||||
assert!(full_payload.withdrawals_root().is_ok());
|
||||
execution_payloads.push(full_payload);
|
||||
}
|
||||
|
||||
verify_execution_payload_chain(execution_payloads.as_slice());
|
||||
}
|
||||
@@ -115,7 +115,7 @@ async fn data_column_sidecar_event_on_process_gossip_data_column() {
|
||||
/// Verifies that a blob event is emitted when blobs are received via RPC.
|
||||
#[tokio::test]
|
||||
async fn blob_sidecar_event_on_process_rpc_blobs() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.deneb_enabled() || f.fulu_enabled()) {
|
||||
if fork_name_from_env().is_none_or(|f| !f.deneb_enabled() || f.fulu_enabled()) {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -170,7 +170,7 @@ async fn blob_sidecar_event_on_process_rpc_blobs() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn data_column_sidecar_event_on_process_rpc_columns() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.fulu_enabled()) {
|
||||
if fork_name_from_env().is_none_or(|f| !f.fulu_enabled()) {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
mod attestation_production;
|
||||
mod attestation_verification;
|
||||
mod bellatrix;
|
||||
mod blob_verification;
|
||||
mod block_verification;
|
||||
mod capella;
|
||||
mod column_verification;
|
||||
mod events;
|
||||
mod op_verification;
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
use beacon_chain::block_verification_types::RpcBlock;
|
||||
use beacon_chain::{
|
||||
BeaconChainError, BlockError, ChainConfig, ExecutionPayloadError,
|
||||
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, NotifyExecutionLayer, OverrideForkchoiceUpdate,
|
||||
StateSkipConfig, WhenSlotSkipped,
|
||||
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, NotifyExecutionLayer, StateSkipConfig,
|
||||
WhenSlotSkipped,
|
||||
canonical_head::{CachedHead, CanonicalHead},
|
||||
test_utils::{BeaconChainHarness, EphemeralHarnessType, test_spec},
|
||||
test_utils::{BeaconChainHarness, EphemeralHarnessType, fork_name_from_env, test_spec},
|
||||
};
|
||||
use execution_layer::{
|
||||
ExecutionLayer, ForkchoiceState, PayloadAttributes,
|
||||
@@ -138,25 +138,6 @@ impl InvalidPayloadRig {
|
||||
payload_attributes
|
||||
}
|
||||
|
||||
fn move_to_terminal_block(&self) {
|
||||
let mock_execution_layer = self.harness.mock_execution_layer.as_ref().unwrap();
|
||||
mock_execution_layer
|
||||
.server
|
||||
.execution_block_generator()
|
||||
.move_to_terminal_block()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn latest_execution_block_hash(&self) -> ExecutionBlockHash {
|
||||
let mock_execution_layer = self.harness.mock_execution_layer.as_ref().unwrap();
|
||||
mock_execution_layer
|
||||
.server
|
||||
.execution_block_generator()
|
||||
.latest_execution_block()
|
||||
.unwrap()
|
||||
.block_hash
|
||||
}
|
||||
|
||||
async fn build_blocks(&mut self, num_blocks: u64, is_valid: Payload) -> Vec<Hash256> {
|
||||
let mut roots = Vec::with_capacity(num_blocks as usize);
|
||||
for _ in 0..num_blocks {
|
||||
@@ -389,8 +370,10 @@ impl InvalidPayloadRig {
|
||||
/// Simple test of the different import types.
|
||||
#[tokio::test]
|
||||
async fn valid_invalid_syncing() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let mut rig = InvalidPayloadRig::new();
|
||||
rig.move_to_terminal_block();
|
||||
|
||||
rig.import_block(Payload::Valid).await;
|
||||
rig.import_block(Payload::Invalid {
|
||||
@@ -404,8 +387,10 @@ async fn valid_invalid_syncing() {
|
||||
/// `latest_valid_hash`.
|
||||
#[tokio::test]
|
||||
async fn invalid_payload_invalidates_parent() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
rig.move_to_first_justification(Payload::Syncing).await;
|
||||
|
||||
@@ -437,7 +422,6 @@ async fn immediate_forkchoice_update_invalid_test(
|
||||
invalid_payload: impl FnOnce(Option<ExecutionBlockHash>) -> Payload,
|
||||
) {
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
rig.move_to_first_justification(Payload::Syncing).await;
|
||||
|
||||
@@ -460,6 +444,9 @@ async fn immediate_forkchoice_update_invalid_test(
|
||||
|
||||
#[tokio::test]
|
||||
async fn immediate_forkchoice_update_payload_invalid() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
immediate_forkchoice_update_invalid_test(|latest_valid_hash| Payload::Invalid {
|
||||
latest_valid_hash,
|
||||
})
|
||||
@@ -468,11 +455,17 @@ async fn immediate_forkchoice_update_payload_invalid() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn immediate_forkchoice_update_payload_invalid_block_hash() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
immediate_forkchoice_update_invalid_test(|_| Payload::InvalidBlockHash).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn immediate_forkchoice_update_payload_invalid_terminal_block() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
immediate_forkchoice_update_invalid_test(|_| Payload::Invalid {
|
||||
latest_valid_hash: Some(ExecutionBlockHash::zero()),
|
||||
})
|
||||
@@ -482,8 +475,10 @@ async fn immediate_forkchoice_update_payload_invalid_terminal_block() {
|
||||
/// Ensure the client tries to exit when the justified checkpoint is invalidated.
|
||||
#[tokio::test]
|
||||
async fn justified_checkpoint_becomes_invalid() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
rig.move_to_first_justification(Payload::Syncing).await;
|
||||
|
||||
@@ -524,11 +519,13 @@ async fn justified_checkpoint_becomes_invalid() {
|
||||
/// Ensure that a `latest_valid_hash` for a pre-finality block only reverts a single block.
|
||||
#[tokio::test]
|
||||
async fn pre_finalized_latest_valid_hash() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let num_blocks = E::slots_per_epoch() * 4;
|
||||
let finalized_epoch = 2;
|
||||
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
let mut blocks = vec![];
|
||||
blocks.push(rig.import_block(Payload::Valid).await); // Import a valid transition block.
|
||||
blocks.extend(rig.build_blocks(num_blocks - 1, Payload::Syncing).await);
|
||||
@@ -571,10 +568,12 @@ async fn pre_finalized_latest_valid_hash() {
|
||||
/// - Will not validate `latest_valid_root` and its ancestors.
|
||||
#[tokio::test]
|
||||
async fn latest_valid_hash_will_not_validate() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
const LATEST_VALID_SLOT: u64 = 3;
|
||||
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
|
||||
let mut blocks = vec![];
|
||||
blocks.push(rig.import_block(Payload::Valid).await); // Import a valid transition block.
|
||||
@@ -618,11 +617,13 @@ async fn latest_valid_hash_will_not_validate() {
|
||||
/// Check behaviour when the `latest_valid_hash` is a junk value.
|
||||
#[tokio::test]
|
||||
async fn latest_valid_hash_is_junk() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let num_blocks = E::slots_per_epoch() * 5;
|
||||
let finalized_epoch = 3;
|
||||
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
let mut blocks = vec![];
|
||||
blocks.push(rig.import_block(Payload::Valid).await); // Import a valid transition block.
|
||||
blocks.extend(rig.build_blocks(num_blocks, Payload::Syncing).await);
|
||||
@@ -659,12 +660,14 @@ async fn latest_valid_hash_is_junk() {
|
||||
/// Check that descendants of invalid blocks are also invalidated.
|
||||
#[tokio::test]
|
||||
async fn invalidates_all_descendants() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let num_blocks = E::slots_per_epoch() * 4 + E::slots_per_epoch() / 2;
|
||||
let finalized_epoch = 2;
|
||||
let finalized_slot = E::slots_per_epoch() * 2;
|
||||
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
let blocks = rig.build_blocks(num_blocks, Payload::Syncing).await;
|
||||
|
||||
@@ -766,12 +769,14 @@ async fn invalidates_all_descendants() {
|
||||
/// Check that the head will switch after the canonical branch is invalidated.
|
||||
#[tokio::test]
|
||||
async fn switches_heads() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let num_blocks = E::slots_per_epoch() * 4 + E::slots_per_epoch() / 2;
|
||||
let finalized_epoch = 2;
|
||||
let finalized_slot = E::slots_per_epoch() * 2;
|
||||
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
let blocks = rig.build_blocks(num_blocks, Payload::Syncing).await;
|
||||
|
||||
@@ -869,8 +874,10 @@ async fn switches_heads() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_during_processing() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let mut rig = InvalidPayloadRig::new();
|
||||
rig.move_to_terminal_block();
|
||||
|
||||
let roots = &[
|
||||
rig.import_block(Payload::Valid).await,
|
||||
@@ -901,8 +908,10 @@ async fn invalid_during_processing() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_after_optimistic_sync() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
|
||||
let mut roots = vec![
|
||||
@@ -939,8 +948,10 @@ async fn invalid_after_optimistic_sync() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn manually_validate_child() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
|
||||
let parent = rig.import_block(Payload::Syncing).await;
|
||||
@@ -957,8 +968,10 @@ async fn manually_validate_child() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn manually_validate_parent() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
|
||||
let parent = rig.import_block(Payload::Syncing).await;
|
||||
@@ -975,8 +988,10 @@ async fn manually_validate_parent() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn payload_preparation() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let mut rig = InvalidPayloadRig::new();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await;
|
||||
|
||||
let el = rig.execution_layer();
|
||||
@@ -1036,8 +1051,10 @@ async fn payload_preparation() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_parent() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let mut rig = InvalidPayloadRig::new();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
|
||||
// Import a syncing block atop the transition block (we'll call this the "parent block" since we
|
||||
@@ -1105,83 +1122,12 @@ async fn invalid_parent() {
|
||||
));
|
||||
}
|
||||
|
||||
/// Tests to ensure that we will still send a proposer preparation
|
||||
#[tokio::test]
|
||||
async fn payload_preparation_before_transition_block() {
|
||||
let rig = InvalidPayloadRig::new();
|
||||
let el = rig.execution_layer();
|
||||
|
||||
// Run the watchdog routine so that the status of the execution engine is set. This ensures
|
||||
// that we don't end up with `eth_syncing` requests later in this function that will impede
|
||||
// testing.
|
||||
el.watchdog_task().await;
|
||||
|
||||
let head = rig.harness.chain.head_snapshot();
|
||||
assert_eq!(
|
||||
head.beacon_block
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.unwrap()
|
||||
.block_hash(),
|
||||
ExecutionBlockHash::zero(),
|
||||
"the head block is post-bellatrix but pre-transition"
|
||||
);
|
||||
|
||||
let current_slot = rig.harness.chain.slot().unwrap();
|
||||
let next_slot = current_slot + 1;
|
||||
let proposer = head
|
||||
.beacon_state
|
||||
.get_beacon_proposer_index(next_slot, &rig.harness.chain.spec)
|
||||
.unwrap();
|
||||
let fee_recipient = Address::repeat_byte(99);
|
||||
|
||||
// Provide preparation data to the EL for `proposer`.
|
||||
el.update_proposer_preparation(
|
||||
Epoch::new(0),
|
||||
[(
|
||||
&ProposerPreparationData {
|
||||
validator_index: proposer as u64,
|
||||
fee_recipient,
|
||||
},
|
||||
&None,
|
||||
)],
|
||||
)
|
||||
.await;
|
||||
|
||||
rig.move_to_terminal_block();
|
||||
|
||||
rig.harness
|
||||
.chain
|
||||
.prepare_beacon_proposer(current_slot)
|
||||
.await
|
||||
.unwrap();
|
||||
let forkchoice_update_params = rig
|
||||
.harness
|
||||
.chain
|
||||
.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.get_forkchoice_update_parameters();
|
||||
rig.harness
|
||||
.chain
|
||||
.update_execution_engine_forkchoice(
|
||||
current_slot,
|
||||
forkchoice_update_params,
|
||||
OverrideForkchoiceUpdate::Yes,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (fork_choice_state, payload_attributes) = rig.previous_forkchoice_update_params();
|
||||
let latest_block_hash = rig.latest_execution_block_hash();
|
||||
assert_eq!(payload_attributes.suggested_fee_recipient(), fee_recipient);
|
||||
assert_eq!(fork_choice_state.head_block_hash, latest_block_hash);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn attesting_to_optimistic_head() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let mut rig = InvalidPayloadRig::new();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
|
||||
let root = rig.import_block(Payload::Syncing).await;
|
||||
@@ -1304,7 +1250,6 @@ impl InvalidHeadSetup {
|
||||
async fn new() -> InvalidHeadSetup {
|
||||
let slots_per_epoch = E::slots_per_epoch();
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
|
||||
// Import blocks until the first time the chain finalizes. This avoids
|
||||
@@ -1392,6 +1337,9 @@ impl InvalidHeadSetup {
|
||||
|
||||
#[tokio::test]
|
||||
async fn recover_from_invalid_head_by_importing_blocks() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let InvalidHeadSetup {
|
||||
rig,
|
||||
fork_block,
|
||||
@@ -1437,6 +1385,9 @@ async fn recover_from_invalid_head_by_importing_blocks() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn recover_from_invalid_head_after_persist_and_reboot() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let InvalidHeadSetup {
|
||||
rig,
|
||||
fork_block: _,
|
||||
@@ -1479,8 +1430,10 @@ async fn recover_from_invalid_head_after_persist_and_reboot() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn weights_after_resetting_optimistic_status() {
|
||||
if fork_name_from_env().is_some_and(|f| !f.bellatrix_enabled()) {
|
||||
return;
|
||||
}
|
||||
let mut rig = InvalidPayloadRig::new().enable_attestations();
|
||||
rig.move_to_terminal_block();
|
||||
rig.import_block(Payload::Valid).await; // Import a valid transition block.
|
||||
|
||||
let mut roots = vec![];
|
||||
|
||||
@@ -3924,188 +3924,6 @@ async fn finalizes_after_resuming_from_db() {
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::large_stack_frames)]
|
||||
#[tokio::test]
|
||||
async fn revert_minority_fork_on_resume() {
|
||||
let validator_count = 16;
|
||||
let slots_per_epoch = MinimalEthSpec::slots_per_epoch();
|
||||
|
||||
let fork_epoch = Epoch::new(4);
|
||||
let fork_slot = fork_epoch.start_slot(slots_per_epoch);
|
||||
let initial_blocks = slots_per_epoch * fork_epoch.as_u64() - 1;
|
||||
let post_fork_blocks = slots_per_epoch * 3;
|
||||
|
||||
let mut spec1 = MinimalEthSpec::default_spec();
|
||||
spec1.altair_fork_epoch = None;
|
||||
let mut spec2 = MinimalEthSpec::default_spec();
|
||||
spec2.altair_fork_epoch = Some(fork_epoch);
|
||||
|
||||
let all_validators = (0..validator_count).collect::<Vec<usize>>();
|
||||
|
||||
// Chain with no fork epoch configured.
|
||||
let db_path1 = tempdir().unwrap();
|
||||
let store1 = get_store_generic(&db_path1, StoreConfig::default(), spec1.clone());
|
||||
let harness1 = BeaconChainHarness::builder(MinimalEthSpec)
|
||||
.spec(spec1.clone().into())
|
||||
.keypairs(KEYPAIRS[0..validator_count].to_vec())
|
||||
.fresh_disk_store(store1)
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
// Chain with fork epoch configured.
|
||||
let db_path2 = tempdir().unwrap();
|
||||
let store2 = get_store_generic(&db_path2, StoreConfig::default(), spec2.clone());
|
||||
let harness2 = BeaconChainHarness::builder(MinimalEthSpec)
|
||||
.spec(spec2.clone().into())
|
||||
.keypairs(KEYPAIRS[0..validator_count].to_vec())
|
||||
.fresh_disk_store(store2)
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
// Apply the same blocks to both chains initially.
|
||||
let mut state = harness1.get_current_state();
|
||||
let mut block_root = harness1.chain.genesis_block_root;
|
||||
for slot in (1..=initial_blocks).map(Slot::new) {
|
||||
let state_root = state.update_tree_hash_cache().unwrap();
|
||||
|
||||
let attestations = harness1.make_attestations(
|
||||
&all_validators,
|
||||
&state,
|
||||
state_root,
|
||||
block_root.into(),
|
||||
slot,
|
||||
);
|
||||
harness1.set_current_slot(slot);
|
||||
harness2.set_current_slot(slot);
|
||||
harness1.process_attestations(attestations.clone(), &state);
|
||||
harness2.process_attestations(attestations, &state);
|
||||
|
||||
let ((block, blobs), new_state) = harness1.make_block(state, slot).await;
|
||||
|
||||
harness1
|
||||
.process_block(slot, block.canonical_root(), (block.clone(), blobs.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
harness2
|
||||
.process_block(slot, block.canonical_root(), (block.clone(), blobs.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
state = new_state;
|
||||
block_root = block.canonical_root();
|
||||
}
|
||||
|
||||
assert_eq!(harness1.head_slot(), fork_slot - 1);
|
||||
assert_eq!(harness2.head_slot(), fork_slot - 1);
|
||||
|
||||
// Fork the two chains.
|
||||
let mut state1 = state.clone();
|
||||
let mut state2 = state.clone();
|
||||
|
||||
let mut majority_blocks = vec![];
|
||||
|
||||
for i in 0..post_fork_blocks {
|
||||
let slot = fork_slot + i;
|
||||
|
||||
// Attestations on majority chain.
|
||||
let state_root = state.update_tree_hash_cache().unwrap();
|
||||
|
||||
let attestations = harness2.make_attestations(
|
||||
&all_validators,
|
||||
&state2,
|
||||
state_root,
|
||||
block_root.into(),
|
||||
slot,
|
||||
);
|
||||
harness2.set_current_slot(slot);
|
||||
harness2.process_attestations(attestations, &state2);
|
||||
|
||||
// Minority chain block (no attesters).
|
||||
let ((block1, blobs1), new_state1) = harness1.make_block(state1, slot).await;
|
||||
harness1
|
||||
.process_block(slot, block1.canonical_root(), (block1, blobs1))
|
||||
.await
|
||||
.unwrap();
|
||||
state1 = new_state1;
|
||||
|
||||
// Majority chain block (all attesters).
|
||||
let ((block2, blobs2), new_state2) = harness2.make_block(state2, slot).await;
|
||||
harness2
|
||||
.process_block(slot, block2.canonical_root(), (block2.clone(), blobs2))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
state2 = new_state2;
|
||||
block_root = block2.canonical_root();
|
||||
|
||||
majority_blocks.push(block2);
|
||||
}
|
||||
|
||||
let end_slot = fork_slot + post_fork_blocks - 1;
|
||||
assert_eq!(harness1.head_slot(), end_slot);
|
||||
assert_eq!(harness2.head_slot(), end_slot);
|
||||
|
||||
// Resume from disk with the hard-fork activated: this should revert the post-fork blocks.
|
||||
// We have to do some hackery with the `slot_clock` so that the correct slot is set when
|
||||
// the beacon chain builder loads the head block.
|
||||
drop(harness1);
|
||||
let resume_store = get_store_generic(&db_path1, StoreConfig::default(), spec2.clone());
|
||||
|
||||
let resumed_harness = TestHarness::builder(MinimalEthSpec)
|
||||
.spec(spec2.clone().into())
|
||||
.keypairs(KEYPAIRS[0..validator_count].to_vec())
|
||||
.resumed_disk_store(resume_store)
|
||||
.override_store_mutator(Box::new(move |mut builder| {
|
||||
builder = builder
|
||||
.resume_from_db()
|
||||
.unwrap()
|
||||
.testing_slot_clock(spec2.get_slot_duration())
|
||||
.unwrap();
|
||||
builder
|
||||
.get_slot_clock()
|
||||
.unwrap()
|
||||
.set_slot(end_slot.as_u64());
|
||||
builder
|
||||
}))
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
// Head should now be just before the fork.
|
||||
resumed_harness.chain.recompute_head_at_current_slot().await;
|
||||
assert_eq!(resumed_harness.head_slot(), fork_slot - 1);
|
||||
|
||||
// Fork choice should only know the canonical head. When we reverted the head we also should
|
||||
// have called `reset_fork_choice_to_finalization` which rebuilds fork choice from scratch
|
||||
// without the reverted block.
|
||||
assert_eq!(
|
||||
resumed_harness.chain.heads(),
|
||||
vec![(resumed_harness.head_block_root(), fork_slot - 1)]
|
||||
);
|
||||
|
||||
// Apply blocks from the majority chain and trigger finalization.
|
||||
let initial_split_slot = resumed_harness.chain.store.get_split_slot();
|
||||
for block in &majority_blocks {
|
||||
resumed_harness
|
||||
.process_block_result((block.clone(), None))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// The canonical head should be the block from the majority chain.
|
||||
resumed_harness.chain.recompute_head_at_current_slot().await;
|
||||
assert_eq!(resumed_harness.head_slot(), block.slot());
|
||||
assert_eq!(resumed_harness.head_block_root(), block.canonical_root());
|
||||
}
|
||||
let advanced_split_slot = resumed_harness.chain.store.get_split_slot();
|
||||
|
||||
// Check that the migration ran successfully.
|
||||
assert!(advanced_split_slot > initial_split_slot);
|
||||
|
||||
// Check that there is only a single head now matching harness2 (the minority chain is gone).
|
||||
let heads = resumed_harness.chain.heads();
|
||||
assert_eq!(heads, harness2.chain.heads());
|
||||
assert_eq!(heads.len(), 1);
|
||||
}
|
||||
|
||||
// This test checks whether the schema downgrade from the latest version to some minimum supported
|
||||
// version is correct. This is the easiest schema test to write without historic versions of
|
||||
// Lighthouse on-hand, but has the disadvantage that the min version needs to be adjusted manually
|
||||
@@ -5740,6 +5558,226 @@ fn check_iterators_from_slot(harness: &TestHarness, slot: Slot) {
|
||||
);
|
||||
}
|
||||
|
||||
/// Test that blocks with default (pre-merge) execution payloads and non-default (post-merge)
|
||||
/// execution payloads can be produced, stored, and retrieved correctly through a merge transition.
|
||||
///
|
||||
/// Spec (see .claude/plans/8658.md):
|
||||
/// - Bellatrix at epoch 0 (genesis), genesis has default execution payload header
|
||||
/// - Slots 1-9: blocks have default (zeroed) execution payloads
|
||||
/// - Slot 10: first block with a non-default execution payload (merge transition block)
|
||||
/// - Slots 11-32+: non-default payloads, each with parent_hash == prev payload block_hash
|
||||
/// - Chain must finalize past genesis
|
||||
#[tokio::test]
|
||||
async fn bellatrix_produce_and_store_payloads() {
|
||||
use beacon_chain::test_utils::{
|
||||
DEFAULT_ETH1_BLOCK_HASH, HARNESS_GENESIS_TIME, InteropGenesisBuilder,
|
||||
};
|
||||
use safe_arith::SafeArith;
|
||||
use state_processing::per_block_processing::is_merge_transition_complete;
|
||||
use tree_hash::TreeHash;
|
||||
|
||||
let merge_slot = 10u64;
|
||||
let total_slots = 48u64;
|
||||
let spec = ForkName::Bellatrix.make_genesis_spec(E::default_spec());
|
||||
|
||||
// Build genesis state with a default (zeroed) execution payload header so that
|
||||
// is_merge_transition_complete = false at genesis.
|
||||
let keypairs = KEYPAIRS[0..LOW_VALIDATOR_COUNT].to_vec();
|
||||
let genesis_state = InteropGenesisBuilder::default()
|
||||
.set_alternating_eth1_withdrawal_credentials()
|
||||
.set_opt_execution_payload_header(None)
|
||||
.build_genesis_state(
|
||||
&keypairs,
|
||||
HARNESS_GENESIS_TIME,
|
||||
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
|
||||
&spec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
!is_merge_transition_complete(&genesis_state),
|
||||
"genesis should NOT have merge complete"
|
||||
);
|
||||
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store_generic(
|
||||
&db_path,
|
||||
StoreConfig {
|
||||
prune_payloads: false,
|
||||
..StoreConfig::default()
|
||||
},
|
||||
spec.clone(),
|
||||
);
|
||||
|
||||
let chain_config = ChainConfig {
|
||||
archive: true,
|
||||
..ChainConfig::default()
|
||||
};
|
||||
let harness = TestHarness::builder(MinimalEthSpec)
|
||||
.spec(store.get_chain_spec().clone())
|
||||
.keypairs(keypairs.clone())
|
||||
.fresh_disk_store(store.clone())
|
||||
.override_store_mutator(Box::new(move |builder: BeaconChainBuilder<_>| {
|
||||
builder
|
||||
.genesis_state(genesis_state)
|
||||
.expect("should set genesis state")
|
||||
}))
|
||||
.mock_execution_layer()
|
||||
.chain_config(chain_config)
|
||||
.build();
|
||||
|
||||
harness
|
||||
.mock_execution_layer
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.server
|
||||
.all_payloads_valid();
|
||||
|
||||
harness.advance_slot();
|
||||
|
||||
// Phase 1: slots 1 to merge_slot-1 — blocks with default execution payloads.
|
||||
let mut state = harness.get_current_state();
|
||||
for slot_num in 1..merge_slot {
|
||||
let slot = Slot::new(slot_num);
|
||||
harness.advance_slot();
|
||||
harness
|
||||
.build_and_import_block_with_payload(
|
||||
&mut state,
|
||||
slot,
|
||||
ExecutionPayloadBellatrix::default(),
|
||||
)
|
||||
.await;
|
||||
state = harness.get_current_state();
|
||||
}
|
||||
|
||||
// Phase 2: slot merge_slot — the merge transition block with a real payload.
|
||||
{
|
||||
let slot = Slot::new(merge_slot);
|
||||
harness.advance_slot();
|
||||
|
||||
// Advance state to compute correct timestamp and randao.
|
||||
let mut pre_state = state.clone();
|
||||
complete_state_advance(&mut pre_state, None, slot, &harness.spec)
|
||||
.expect("should advance state");
|
||||
pre_state
|
||||
.build_caches(&harness.spec)
|
||||
.expect("should build caches");
|
||||
|
||||
let timestamp = pre_state
|
||||
.genesis_time()
|
||||
.safe_add(
|
||||
slot.as_u64()
|
||||
.safe_mul(harness.spec.seconds_per_slot)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let prev_randao = *pre_state.get_randao_mix(pre_state.current_epoch()).unwrap();
|
||||
|
||||
let mut transition_payload = ExecutionPayloadBellatrix {
|
||||
parent_hash: ExecutionBlockHash::zero(),
|
||||
fee_recipient: Address::repeat_byte(42),
|
||||
receipts_root: Hash256::repeat_byte(42),
|
||||
state_root: Hash256::repeat_byte(43),
|
||||
logs_bloom: vec![0; 256].try_into().unwrap(),
|
||||
prev_randao,
|
||||
block_number: 1,
|
||||
gas_limit: 30_000_000,
|
||||
gas_used: 0,
|
||||
timestamp,
|
||||
extra_data: VariableList::empty(),
|
||||
base_fee_per_gas: Uint256::from(1u64),
|
||||
block_hash: ExecutionBlockHash::zero(),
|
||||
transactions: VariableList::empty(),
|
||||
};
|
||||
transition_payload.block_hash =
|
||||
ExecutionBlockHash::from_root(transition_payload.tree_hash_root());
|
||||
|
||||
// Insert the transition payload into the mock EL so subsequent blocks can chain.
|
||||
{
|
||||
let mock_el = harness.mock_execution_layer.as_ref().unwrap();
|
||||
let mut block_gen = mock_el.server.execution_block_generator();
|
||||
block_gen.insert_block_without_checks(execution_layer::test_utils::Block::PoS(
|
||||
ExecutionPayload::Bellatrix(transition_payload.clone()),
|
||||
));
|
||||
}
|
||||
|
||||
harness
|
||||
.build_and_import_block_with_payload(&mut state, slot, transition_payload)
|
||||
.await;
|
||||
state = harness.get_current_state();
|
||||
|
||||
assert!(
|
||||
is_merge_transition_complete(&state),
|
||||
"merge should be complete after slot {merge_slot}"
|
||||
);
|
||||
}
|
||||
|
||||
// Phase 3: slots merge_slot+1 to total_slots — use harness with attestations.
|
||||
let post_merge_slots = (total_slots - merge_slot) as usize;
|
||||
harness.extend_slots(post_merge_slots).await;
|
||||
|
||||
// ---- Verification: check all blocks in the store against plan invariants ----
|
||||
|
||||
let mut prev_payload_block_hash: Option<ExecutionBlockHash> = None;
|
||||
|
||||
for slot_num in 1..=total_slots {
|
||||
let slot = Slot::new(slot_num);
|
||||
let block_root = harness
|
||||
.chain
|
||||
.block_root_at_slot(slot, WhenSlotSkipped::Prev)
|
||||
.unwrap()
|
||||
.unwrap_or_else(|| panic!("missing block at slot {slot_num}"));
|
||||
let block = store
|
||||
.get_blinded_block(&block_root)
|
||||
.unwrap()
|
||||
.unwrap_or_else(|| panic!("block not in store at slot {slot_num}"));
|
||||
let payload = block
|
||||
.message()
|
||||
.body()
|
||||
.execution_payload()
|
||||
.expect("bellatrix block should have execution payload");
|
||||
|
||||
if slot_num < merge_slot {
|
||||
// Slots 1 to merge_slot-1: payload must be default.
|
||||
assert!(
|
||||
payload.is_default_with_empty_roots(),
|
||||
"slot {slot_num} should have default payload"
|
||||
);
|
||||
} else if slot_num == merge_slot {
|
||||
// Merge transition block: first non-default payload.
|
||||
assert!(
|
||||
!payload.is_default_with_empty_roots(),
|
||||
"slot {slot_num} (merge) should have non-default payload"
|
||||
);
|
||||
prev_payload_block_hash = Some(payload.block_hash());
|
||||
} else {
|
||||
// Post-merge: non-default payload with valid parent_hash chain.
|
||||
assert!(
|
||||
!payload.is_default_with_empty_roots(),
|
||||
"slot {slot_num} should have non-default payload"
|
||||
);
|
||||
assert_eq!(
|
||||
payload.parent_hash(),
|
||||
prev_payload_block_hash.unwrap(),
|
||||
"slot {slot_num} payload parent_hash should chain from previous payload"
|
||||
);
|
||||
prev_payload_block_hash = Some(payload.block_hash());
|
||||
}
|
||||
}
|
||||
|
||||
// Verify finalization.
|
||||
let finalized_epoch = harness
|
||||
.chain
|
||||
.canonical_head
|
||||
.cached_head()
|
||||
.finalized_checkpoint()
|
||||
.epoch;
|
||||
assert!(
|
||||
finalized_epoch > 0,
|
||||
"chain should have finalized past genesis"
|
||||
);
|
||||
}
|
||||
|
||||
fn get_finalized_epoch_boundary_blocks(
|
||||
dump: &[BeaconSnapshot<MinimalEthSpec, BlindedPayload<MinimalEthSpec>>],
|
||||
) -> HashSet<SignedBeaconBlockHash> {
|
||||
|
||||
Reference in New Issue
Block a user