mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-27 17:53:42 +00:00
Re-issue ForkchoiceUpdate based on updated PayloadStatus (#9102)
Co-Authored-By: hopinheimer <knmanas6@gmail.com> Co-Authored-By: Michael Sproul <michael@sigmaprime.io> Co-Authored-By: Michael Sproul <michaelsproul@users.noreply.github.com>
This commit is contained in:
575
beacon_node/beacon_chain/tests/prepare_payload.rs
Normal file
575
beacon_node/beacon_chain/tests/prepare_payload.rs
Normal file
@@ -0,0 +1,575 @@
|
||||
#![cfg(not(debug_assertions))]
|
||||
#![allow(clippy::result_large_err)]
|
||||
|
||||
use beacon_chain::test_utils::{
|
||||
AttestationStrategy, BeaconChainHarness, BlockStrategy, DiskHarnessType, test_spec,
|
||||
};
|
||||
use beacon_chain::{ChainConfig, custody_context::NodeCustodyType};
|
||||
use bls::Keypair;
|
||||
use eth2::types::ProposerPreparationData;
|
||||
use fork_choice::PayloadStatus;
|
||||
use logging::create_test_tracing_subscriber;
|
||||
use ssz_types::VariableList;
|
||||
use state_processing::{
|
||||
per_block_processing::{apply_parent_execution_payload, withdrawals::get_expected_withdrawals},
|
||||
state_advance::complete_state_advance,
|
||||
};
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use store::database::interface::BeaconNodeBackend;
|
||||
use store::{HotColdDB, StoreConfig};
|
||||
use tempfile::{TempDir, tempdir};
|
||||
use types::*;
|
||||
|
||||
// Should ideally be divisible by 3.
|
||||
pub const LOW_VALIDATOR_COUNT: usize = 32;
|
||||
pub const HIGH_VALIDATOR_COUNT: usize = 64;
|
||||
|
||||
/// A cached set of keys.
|
||||
static KEYPAIRS: LazyLock<Vec<Keypair>> =
|
||||
LazyLock::new(|| types::test_utils::generate_deterministic_keypairs(HIGH_VALIDATOR_COUNT));
|
||||
|
||||
type E = MinimalEthSpec;
|
||||
type TestHarness = BeaconChainHarness<DiskHarnessType<E>>;
|
||||
|
||||
fn get_store(
|
||||
db_path: &TempDir,
|
||||
spec: Arc<ChainSpec>,
|
||||
) -> Arc<HotColdDB<E, BeaconNodeBackend<E>, BeaconNodeBackend<E>>> {
|
||||
let store_config = StoreConfig {
|
||||
prune_payloads: false,
|
||||
..StoreConfig::default()
|
||||
};
|
||||
get_store_generic(db_path, store_config, spec)
|
||||
}
|
||||
|
||||
fn get_store_generic(
|
||||
db_path: &TempDir,
|
||||
config: StoreConfig,
|
||||
spec: Arc<ChainSpec>,
|
||||
) -> Arc<HotColdDB<E, BeaconNodeBackend<E>, BeaconNodeBackend<E>>> {
|
||||
create_test_tracing_subscriber();
|
||||
let hot_path = db_path.path().join("chain_db");
|
||||
let cold_path = db_path.path().join("freezer_db");
|
||||
let blobs_path = db_path.path().join("blobs_db");
|
||||
|
||||
HotColdDB::open(
|
||||
&hot_path,
|
||||
&cold_path,
|
||||
&blobs_path,
|
||||
|_, _, _| Ok(()),
|
||||
config,
|
||||
spec,
|
||||
)
|
||||
.expect("disk store should initialize")
|
||||
}
|
||||
|
||||
fn get_harness(
|
||||
store: Arc<HotColdDB<E, BeaconNodeBackend<E>, BeaconNodeBackend<E>>>,
|
||||
validator_count: usize,
|
||||
) -> TestHarness {
|
||||
// Most tests expect to retain historic states, so we use this as the default.
|
||||
let chain_config = ChainConfig {
|
||||
archive: true,
|
||||
..ChainConfig::default()
|
||||
};
|
||||
get_harness_generic(
|
||||
store,
|
||||
validator_count,
|
||||
chain_config,
|
||||
NodeCustodyType::Fullnode,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_harness_generic(
|
||||
store: Arc<HotColdDB<E, BeaconNodeBackend<E>, BeaconNodeBackend<E>>>,
|
||||
validator_count: usize,
|
||||
chain_config: ChainConfig,
|
||||
node_custody_type: NodeCustodyType,
|
||||
) -> TestHarness {
|
||||
let harness = TestHarness::builder(MinimalEthSpec)
|
||||
.spec(store.get_chain_spec().clone())
|
||||
.keypairs(KEYPAIRS[0..validator_count].to_vec())
|
||||
.fresh_disk_store(store)
|
||||
.mock_execution_layer()
|
||||
.chain_config(chain_config)
|
||||
.node_custody_type(node_custody_type)
|
||||
.build();
|
||||
harness.advance_slot();
|
||||
harness
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_payload_on_full_parent_next_slot() {
|
||||
prepare_payload_generic(
|
||||
PayloadStatus::Full,
|
||||
Slot::new(3 * E::slots_per_epoch() + 1),
|
||||
Slot::new(3 * E::slots_per_epoch() + 2),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_payload_on_full_parent_one_epoch_skip() {
|
||||
prepare_payload_generic(
|
||||
PayloadStatus::Full,
|
||||
Slot::new(3 * E::slots_per_epoch() + 1),
|
||||
Slot::new(4 * E::slots_per_epoch()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_payload_on_full_parent_uneven_one_epoch_skip() {
|
||||
prepare_payload_generic(
|
||||
PayloadStatus::Full,
|
||||
Slot::new(3 * E::slots_per_epoch() + 1),
|
||||
Slot::new(5 * E::slots_per_epoch() - 1),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_payload_on_empty_parent_next_slot() {
|
||||
prepare_payload_generic(
|
||||
PayloadStatus::Empty,
|
||||
Slot::new(3 * E::slots_per_epoch() + 1),
|
||||
Slot::new(3 * E::slots_per_epoch() + 2),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_payload_on_empty_parent_one_epoch_skip() {
|
||||
prepare_payload_generic(
|
||||
PayloadStatus::Empty,
|
||||
Slot::new(3 * E::slots_per_epoch() + 1),
|
||||
Slot::new(4 * E::slots_per_epoch()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn prepare_payload_generic(
|
||||
parent_payload_status: PayloadStatus,
|
||||
parent_block_slot: Slot,
|
||||
prepare_slot: Slot,
|
||||
) {
|
||||
assert!(parent_block_slot > 0);
|
||||
|
||||
// Post-Gloas test.
|
||||
let spec = Arc::new(test_spec::<E>());
|
||||
if !spec.fork_name_at_slot::<E>(Slot::new(0)).gloas_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let num_blocks_produced = parent_block_slot.as_u64() - 1;
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store(&db_path, spec.clone());
|
||||
let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT);
|
||||
|
||||
harness
|
||||
.extend_chain(
|
||||
num_blocks_produced as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Advance the slot so the next extend_chain produces at a fresh slot.
|
||||
harness.advance_slot();
|
||||
|
||||
// Produce a block with a payload that affects withdrawals for the next slot.
|
||||
// A switch-to-compounding consolidation changes withdrawal credentials from 0x01 to 0x02,
|
||||
// which queues the validator's excess balance as a pending deposit and removes it from the
|
||||
// partial withdrawal sweep. We target an odd-indexed validator since odd validators are
|
||||
// created with eth1 withdrawal credentials in the interop genesis builder.
|
||||
let consolidation_request = harness.make_switch_to_compounding_request(1);
|
||||
|
||||
let execution_requests = ExecutionRequests::<E> {
|
||||
deposits: VariableList::empty(),
|
||||
withdrawals: VariableList::empty(),
|
||||
consolidations: VariableList::new(vec![consolidation_request]).unwrap(),
|
||||
};
|
||||
|
||||
// Inject the execution requests into the mock EL so the next payload includes them.
|
||||
harness
|
||||
.execution_block_generator()
|
||||
.set_next_execution_requests(execution_requests);
|
||||
|
||||
// Produce and import one more block. Its envelope will contain the consolidation request.
|
||||
// TODO(gloas): all this ugly plumbing could be avoided with some more "implicit" context
|
||||
// methods
|
||||
let state = harness.get_current_state();
|
||||
let (block_contents, opt_envelope, parent_block_state) = harness
|
||||
.make_block_with_envelope(state, parent_block_slot)
|
||||
.await;
|
||||
let envelope = opt_envelope.unwrap();
|
||||
let block_root = harness
|
||||
.process_block(
|
||||
parent_block_slot,
|
||||
block_contents.0.canonical_root(),
|
||||
block_contents.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// TODO(gloas): try a case where head is empty even though envelope is processed
|
||||
if parent_payload_status == PayloadStatus::Full {
|
||||
harness
|
||||
.process_envelope(
|
||||
block_root.into(),
|
||||
envelope.clone(),
|
||||
&parent_block_state,
|
||||
block_contents.0.state_root(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Verify that the withdrawals computed from the block's state differ from the withdrawals
|
||||
// computed from the block's state with its payload applied by
|
||||
// `apply_parent_execution_payload`.
|
||||
let cached_head = harness.chain.canonical_head.cached_head();
|
||||
let unadvanced_empty_state = &cached_head.snapshot.beacon_state;
|
||||
let parent_bid = unadvanced_empty_state
|
||||
.latest_execution_payload_bid()
|
||||
.unwrap();
|
||||
|
||||
let mut advanced_empty_state = unadvanced_empty_state.clone();
|
||||
complete_state_advance(&mut advanced_empty_state, None, prepare_slot, &spec).unwrap();
|
||||
|
||||
let mut unadvanced_full_state = unadvanced_empty_state.clone();
|
||||
apply_parent_execution_payload(
|
||||
&mut unadvanced_full_state,
|
||||
parent_bid,
|
||||
&envelope.message.execution_requests,
|
||||
&spec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut advanced_full_state = advanced_empty_state.clone();
|
||||
apply_parent_execution_payload(
|
||||
&mut advanced_full_state,
|
||||
parent_bid,
|
||||
&envelope.message.execution_requests,
|
||||
&spec,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let withdrawals_unadvanced_empty: Withdrawals<E> =
|
||||
get_expected_withdrawals(unadvanced_empty_state, &spec)
|
||||
.unwrap()
|
||||
.into();
|
||||
let withdrawals_advanced_empty: Withdrawals<E> =
|
||||
get_expected_withdrawals(&advanced_empty_state, &spec)
|
||||
.unwrap()
|
||||
.into();
|
||||
let withdrawals_unadvanced_full: Withdrawals<E> =
|
||||
get_expected_withdrawals(&unadvanced_full_state, &spec)
|
||||
.unwrap()
|
||||
.into();
|
||||
let withdrawals_advanced_full: Withdrawals<E> =
|
||||
get_expected_withdrawals(&advanced_full_state, &spec)
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
assert_ne!(
|
||||
withdrawals_advanced_empty, withdrawals_advanced_full,
|
||||
"Applying execution requests should change the expected withdrawals"
|
||||
);
|
||||
|
||||
let expect_state_advance_to_change_withdrawals =
|
||||
prepare_slot.epoch(E::slots_per_epoch()) > parent_block_slot.epoch(E::slots_per_epoch());
|
||||
if expect_state_advance_to_change_withdrawals {
|
||||
if parent_payload_status == fork_choice::PayloadStatus::Full {
|
||||
assert_ne!(
|
||||
withdrawals_unadvanced_full, withdrawals_advanced_full,
|
||||
"Advancing the state should change the withdrawals"
|
||||
);
|
||||
} else {
|
||||
assert_ne!(
|
||||
withdrawals_unadvanced_empty, withdrawals_advanced_empty,
|
||||
"Advancing the state should change the withdrawals"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Call `prepare_beacon_proposer` for the next slot and ensure that it primes the execution
|
||||
// layer payload attributes cache with the correct withdrawals (the ones taking into account
|
||||
// the applied execution_requests).
|
||||
let current_slot = prepare_slot - 1;
|
||||
let proposer_index = advanced_empty_state
|
||||
.get_beacon_proposer_index(prepare_slot, &spec)
|
||||
.expect("should get proposer index");
|
||||
|
||||
// Register the proposer so prepare_beacon_proposer doesn't skip it.
|
||||
let el = harness.chain.execution_layer.as_ref().unwrap();
|
||||
el.update_proposer_preparation(
|
||||
prepare_slot.epoch(E::slots_per_epoch()),
|
||||
[(
|
||||
&ProposerPreparationData {
|
||||
validator_index: proposer_index as u64,
|
||||
fee_recipient: Address::repeat_byte(42),
|
||||
},
|
||||
&None,
|
||||
)],
|
||||
)
|
||||
.await;
|
||||
|
||||
// Advance the slot clock to just before the prepare slot so the lookahead check passes.
|
||||
harness.advance_to_slot_lookahead(prepare_slot, harness.chain.config.prepare_payload_lookahead);
|
||||
|
||||
harness
|
||||
.chain
|
||||
.prepare_beacon_proposer(current_slot)
|
||||
.await
|
||||
.expect("prepare_beacon_proposer should succeed");
|
||||
|
||||
// Read the payload attributes from the EL cache and verify the withdrawals.
|
||||
let el = harness.chain.execution_layer.as_ref().unwrap();
|
||||
let head_root = harness.head_block_root();
|
||||
let attributes = el
|
||||
.payload_attributes(prepare_slot, head_root, parent_payload_status)
|
||||
.await
|
||||
.expect("should have cached payload attributes for prepare_slot");
|
||||
|
||||
let actual_withdrawals = attributes.withdrawals().unwrap();
|
||||
let expected_withdrawals: Vec<Withdrawal> = if parent_payload_status == PayloadStatus::Full {
|
||||
withdrawals_advanced_full.to_vec()
|
||||
} else {
|
||||
withdrawals_advanced_empty.to_vec()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
actual_withdrawals, &expected_withdrawals,
|
||||
"prepare_beacon_proposer should use withdrawals computed from the \
|
||||
{parent_payload_status:?} state"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_payload_on_genesis_next_slot() {
|
||||
prepare_payload_on_genesis_generic(Slot::new(1)).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_payload_on_genesis_skip_two_epochs() {
|
||||
prepare_payload_on_genesis_generic(Slot::new(2 * E::slots_per_epoch())).await;
|
||||
}
|
||||
|
||||
async fn prepare_payload_on_genesis_generic(prepare_slot: Slot) {
|
||||
// Post-Gloas test.
|
||||
let spec = Arc::new(test_spec::<E>());
|
||||
if !spec.fork_name_at_slot::<E>(Slot::new(0)).gloas_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Genesis is always considered Empty.
|
||||
let parent_payload_status = PayloadStatus::Empty;
|
||||
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store(&db_path, spec.clone());
|
||||
let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT);
|
||||
|
||||
// At genesis withdrawals are empty (because nothing has happened yet), so we don't assert
|
||||
// anything about the advanced vs unadvanced state. This test just exists to test that
|
||||
// calculating payload attributes at genesis works and doesn't error.
|
||||
let cached_head = harness.chain.canonical_head.cached_head();
|
||||
let unadvanced_state = &cached_head.snapshot.beacon_state;
|
||||
|
||||
let mut advanced_state = unadvanced_state.clone();
|
||||
complete_state_advance(&mut advanced_state, None, prepare_slot, &spec).unwrap();
|
||||
|
||||
let withdrawals_advanced: Withdrawals<E> = get_expected_withdrawals(&advanced_state, &spec)
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
// Call `prepare_beacon_proposer` for the next slot and ensure that it primes the execution
|
||||
// layer payload attributes cache with the correct withdrawals (the ones taking into account
|
||||
// the state advance).
|
||||
let current_slot = prepare_slot - 1;
|
||||
let proposer_index = advanced_state
|
||||
.get_beacon_proposer_index(prepare_slot, &spec)
|
||||
.unwrap();
|
||||
|
||||
// Register the proposer so prepare_beacon_proposer doesn't skip it.
|
||||
let el = harness.chain.execution_layer.as_ref().unwrap();
|
||||
el.update_proposer_preparation(
|
||||
prepare_slot.epoch(E::slots_per_epoch()),
|
||||
[(
|
||||
&ProposerPreparationData {
|
||||
validator_index: proposer_index as u64,
|
||||
fee_recipient: Address::repeat_byte(42),
|
||||
},
|
||||
&None,
|
||||
)],
|
||||
)
|
||||
.await;
|
||||
|
||||
// Advance the slot clock to just before the prepare slot so the lookahead check passes.
|
||||
harness.advance_to_slot_lookahead(prepare_slot, harness.chain.config.prepare_payload_lookahead);
|
||||
|
||||
harness
|
||||
.chain
|
||||
.prepare_beacon_proposer(current_slot)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Read the payload attributes from the EL cache and verify the withdrawals.
|
||||
let el = harness.chain.execution_layer.as_ref().unwrap();
|
||||
let head_root = harness.head_block_root();
|
||||
let attributes = el
|
||||
.payload_attributes(prepare_slot, head_root, parent_payload_status)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let actual_withdrawals = attributes.withdrawals().unwrap();
|
||||
let expected_withdrawals: Vec<Withdrawal> = withdrawals_advanced.to_vec();
|
||||
|
||||
assert_eq!(
|
||||
actual_withdrawals, &expected_withdrawals,
|
||||
"prepare_beacon_proposer should use withdrawals computed from the \
|
||||
{parent_payload_status:?} advanced genesis state"
|
||||
);
|
||||
assert!(actual_withdrawals.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_payload_on_fork_boundary_no_skip() {
|
||||
prepare_payload_on_fork_boundary(
|
||||
Slot::new(2 * E::slots_per_epoch()) - 1,
|
||||
Slot::new(2 * E::slots_per_epoch()),
|
||||
Epoch::new(2),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_payload_on_fork_boundary_skip_one_prior() {
|
||||
prepare_payload_on_fork_boundary(
|
||||
Slot::new(2 * E::slots_per_epoch()) - 2,
|
||||
Slot::new(2 * E::slots_per_epoch()),
|
||||
Epoch::new(2),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_payload_on_fork_boundary_skip_one_after() {
|
||||
prepare_payload_on_fork_boundary(
|
||||
Slot::new(2 * E::slots_per_epoch()) - 1,
|
||||
Slot::new(2 * E::slots_per_epoch()) + 1,
|
||||
Epoch::new(2),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_payload_on_fork_boundary_skip_whole_epoch() {
|
||||
prepare_payload_on_fork_boundary(
|
||||
Slot::new(E::slots_per_epoch()),
|
||||
Slot::new(2 * E::slots_per_epoch()),
|
||||
Epoch::new(2),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn prepare_payload_on_fork_boundary(
|
||||
parent_block_slot: Slot,
|
||||
prepare_slot: Slot,
|
||||
gloas_fork_epoch: Epoch,
|
||||
) {
|
||||
// Post-Gloas test.
|
||||
let mut spec = test_spec::<E>();
|
||||
if !spec.fork_name_at_slot::<E>(Slot::new(0)).gloas_enabled() {
|
||||
return;
|
||||
}
|
||||
spec.gloas_fork_epoch = Some(gloas_fork_epoch);
|
||||
let spec = Arc::new(spec);
|
||||
|
||||
// Pre-Gloas blocks are always considered Empty.
|
||||
let parent_payload_status = PayloadStatus::Empty;
|
||||
|
||||
let num_blocks_produced = parent_block_slot.as_u64();
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store(&db_path, spec.clone());
|
||||
let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT);
|
||||
|
||||
harness
|
||||
.extend_chain(
|
||||
num_blocks_produced as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Verify that the withdrawals computed from the block's state differ from the withdrawals
|
||||
// computed from the block's state with its payload applied by
|
||||
// `apply_parent_execution_payload`.
|
||||
let cached_head = harness.chain.canonical_head.cached_head();
|
||||
let unadvanced_state = &cached_head.snapshot.beacon_state;
|
||||
|
||||
let mut advanced_state = unadvanced_state.clone();
|
||||
complete_state_advance(&mut advanced_state, None, prepare_slot, &spec).unwrap();
|
||||
|
||||
let withdrawals_unadvanced: Withdrawals<E> = get_expected_withdrawals(unadvanced_state, &spec)
|
||||
.unwrap()
|
||||
.into();
|
||||
let withdrawals_advanced: Withdrawals<E> = get_expected_withdrawals(&advanced_state, &spec)
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let expect_state_advance_to_change_withdrawals = prepare_slot.epoch(E::slots_per_epoch()) > 0;
|
||||
if expect_state_advance_to_change_withdrawals {
|
||||
assert_ne!(
|
||||
withdrawals_unadvanced, withdrawals_advanced,
|
||||
"Advancing the state should change the withdrawals"
|
||||
);
|
||||
}
|
||||
|
||||
// Call `prepare_beacon_proposer` for the next slot and ensure that it primes the execution
|
||||
// layer payload attributes cache with the correct withdrawals (the ones taking into account
|
||||
// the applied execution_requests).
|
||||
let current_slot = prepare_slot - 1;
|
||||
let proposer_index = advanced_state
|
||||
.get_beacon_proposer_index(prepare_slot, &spec)
|
||||
.unwrap();
|
||||
|
||||
// Register the proposer so prepare_beacon_proposer doesn't skip it.
|
||||
let el = harness.chain.execution_layer.as_ref().unwrap();
|
||||
el.update_proposer_preparation(
|
||||
prepare_slot.epoch(E::slots_per_epoch()),
|
||||
[(
|
||||
&ProposerPreparationData {
|
||||
validator_index: proposer_index as u64,
|
||||
fee_recipient: Address::repeat_byte(42),
|
||||
},
|
||||
&None,
|
||||
)],
|
||||
)
|
||||
.await;
|
||||
|
||||
// Advance the slot clock to just before the prepare slot so the lookahead check passes.
|
||||
harness.advance_to_slot_lookahead(prepare_slot, harness.chain.config.prepare_payload_lookahead);
|
||||
|
||||
harness
|
||||
.chain
|
||||
.prepare_beacon_proposer(current_slot)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Read the payload attributes from the EL cache and verify the withdrawals.
|
||||
let el = harness.chain.execution_layer.as_ref().unwrap();
|
||||
let head_root = harness.head_block_root();
|
||||
let attributes = el
|
||||
.payload_attributes(prepare_slot, head_root, parent_payload_status)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let actual_withdrawals = attributes.withdrawals().unwrap();
|
||||
let expected_withdrawals: Vec<Withdrawal> = withdrawals_advanced.to_vec();
|
||||
|
||||
assert_eq!(
|
||||
actual_withdrawals, &expected_withdrawals,
|
||||
"prepare_beacon_proposer should use withdrawals computed from the \
|
||||
advanced state"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user