Merge remote-tracking branch 'origin/unstable' into capella-update

This commit is contained in:
Michael Sproul
2022-12-14 13:00:41 +11:00
81 changed files with 3532 additions and 427 deletions

View File

@@ -2980,6 +2980,22 @@ pub fn serve<T: BeaconChainTypes>(
})
});
// POST lighthouse/ui/validator_metrics
let post_lighthouse_ui_validator_metrics = warp::path("lighthouse")
.and(warp::path("ui"))
.and(warp::path("validator_metrics"))
.and(warp::path::end())
.and(warp::body::json())
.and(chain_filter.clone())
.and_then(
|request_data: ui::ValidatorMetricsRequestData, chain: Arc<BeaconChain<T>>| {
blocking_json_task(move || {
ui::post_validator_monitor_metrics(request_data, chain)
.map(api_types::GenericResponse::from)
})
},
);
// GET lighthouse/syncing
let get_lighthouse_syncing = warp::path("lighthouse")
.and(warp::path("syncing"))
@@ -3431,6 +3447,7 @@ pub fn serve<T: BeaconChainTypes>(
.or(get_validator_sync_committee_contribution.boxed())
.or(get_lighthouse_health.boxed())
.or(get_lighthouse_ui_health.boxed())
.or(get_lighthouse_ui_validator_count.boxed())
.or(get_lighthouse_syncing.boxed())
.or(get_lighthouse_nat.boxed())
.or(get_lighthouse_peers.boxed())
@@ -3448,7 +3465,6 @@ pub fn serve<T: BeaconChainTypes>(
.or(get_lighthouse_attestation_performance.boxed())
.or(get_lighthouse_block_packing_efficiency.boxed())
.or(get_lighthouse_merge_readiness.boxed())
.or(get_lighthouse_ui_validator_count.boxed())
.or(get_events.boxed()),
)
.boxed()
@@ -3473,7 +3489,8 @@ pub fn serve<T: BeaconChainTypes>(
.or(post_lighthouse_liveness.boxed())
.or(post_lighthouse_database_reconstruct.boxed())
.or(post_lighthouse_database_historical_blocks.boxed())
.or(post_lighthouse_block_rewards.boxed()),
.or(post_lighthouse_block_rewards.boxed())
.or(post_lighthouse_ui_validator_metrics.boxed()),
))
.recover(warp_utils::reject::handle_rejection)
.with(slog_logging(log.clone()))

View File

@@ -5,7 +5,7 @@ use beacon_chain::{
};
use lighthouse_network::{PubsubMessage, SignedBeaconBlockAndBlobsSidecar};
use network::NetworkMessage;
use slog::{crit, error, info, warn, Logger};
use slog::{error, info, warn, Logger};
use slot_clock::SlotClock;
use std::sync::Arc;
use tokio::sync::mpsc::UnboundedSender;
@@ -90,10 +90,10 @@ pub async fn publish_block<T: BeaconChainTypes>(
//
// Check to see the thresholds are non-zero to avoid logging errors with small
// slot times (e.g., during testing)
let crit_threshold = chain.slot_clock.unagg_attestation_production_delay();
let error_threshold = crit_threshold / 2;
if delay >= crit_threshold {
crit!(
let too_late_threshold = chain.slot_clock.unagg_attestation_production_delay();
let delayed_threshold = too_late_threshold / 2;
if delay >= too_late_threshold {
error!(
log,
"Block was broadcast too late";
"msg" => "system may be overloaded, block likely to be orphaned",
@@ -101,7 +101,7 @@ pub async fn publish_block<T: BeaconChainTypes>(
"slot" => block.slot(),
"root" => ?root,
)
} else if delay >= error_threshold {
} else if delay >= delayed_threshold {
error!(
log,
"Block broadcast was delayed";

View File

@@ -1,10 +1,11 @@
use beacon_chain::{BeaconChain, BeaconChainError, BeaconChainTypes};
use beacon_chain::{metrics, BeaconChain, BeaconChainError, BeaconChainTypes};
use eth2::types::ValidatorStatus;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use warp_utils::reject::beacon_chain_error;
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct ValidatorCountResponse {
pub active_ongoing: u64,
pub active_exiting: u64,
@@ -69,3 +70,126 @@ pub fn get_validator_count<T: BeaconChainTypes>(
exited_slashed,
})
}
#[derive(PartialEq, Serialize, Deserialize)]
pub struct ValidatorMetricsRequestData {
indices: Vec<u64>,
}
#[derive(PartialEq, Serialize, Deserialize)]
pub struct ValidatorMetrics {
attestation_hits: u64,
attestation_misses: u64,
attestation_hit_percentage: f64,
attestation_head_hits: u64,
attestation_head_misses: u64,
attestation_head_hit_percentage: f64,
attestation_target_hits: u64,
attestation_target_misses: u64,
attestation_target_hit_percentage: f64,
}
#[derive(PartialEq, Serialize, Deserialize)]
pub struct ValidatorMetricsResponse {
validators: HashMap<String, ValidatorMetrics>,
}
pub fn post_validator_monitor_metrics<T: BeaconChainTypes>(
request_data: ValidatorMetricsRequestData,
chain: Arc<BeaconChain<T>>,
) -> Result<ValidatorMetricsResponse, warp::Rejection> {
let validator_ids = chain
.validator_monitor
.read()
.get_all_monitored_validators()
.iter()
.cloned()
.collect::<HashSet<String>>();
let indices = request_data
.indices
.iter()
.map(|index| index.to_string())
.collect::<HashSet<String>>();
let ids = validator_ids
.intersection(&indices)
.collect::<HashSet<&String>>();
let mut validators = HashMap::new();
for id in ids {
let attestation_hits = metrics::get_int_counter(
&metrics::VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_ATTESTER_HIT,
&[id],
)
.map(|counter| counter.get())
.unwrap_or(0);
let attestation_misses = metrics::get_int_counter(
&metrics::VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_ATTESTER_MISS,
&[id],
)
.map(|counter| counter.get())
.unwrap_or(0);
let attestations = attestation_hits + attestation_misses;
let attestation_hit_percentage: f64 = if attestations == 0 {
0.0
} else {
(100 * attestation_hits / attestations) as f64
};
let attestation_head_hits = metrics::get_int_counter(
&metrics::VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_HEAD_ATTESTER_HIT,
&[id],
)
.map(|counter| counter.get())
.unwrap_or(0);
let attestation_head_misses = metrics::get_int_counter(
&metrics::VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_HEAD_ATTESTER_MISS,
&[id],
)
.map(|counter| counter.get())
.unwrap_or(0);
let head_attestations = attestation_head_hits + attestation_head_misses;
let attestation_head_hit_percentage: f64 = if head_attestations == 0 {
0.0
} else {
(100 * attestation_head_hits / head_attestations) as f64
};
let attestation_target_hits = metrics::get_int_counter(
&metrics::VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_TARGET_ATTESTER_HIT,
&[id],
)
.map(|counter| counter.get())
.unwrap_or(0);
let attestation_target_misses = metrics::get_int_counter(
&metrics::VALIDATOR_MONITOR_PREV_EPOCH_ON_CHAIN_TARGET_ATTESTER_MISS,
&[id],
)
.map(|counter| counter.get())
.unwrap_or(0);
let target_attestations = attestation_target_hits + attestation_target_misses;
let attestation_target_hit_percentage: f64 = if target_attestations == 0 {
0.0
} else {
(100 * attestation_target_hits / target_attestations) as f64
};
let metrics = ValidatorMetrics {
attestation_hits,
attestation_misses,
attestation_hit_percentage,
attestation_head_hits,
attestation_head_misses,
attestation_head_hit_percentage,
attestation_target_hits,
attestation_target_misses,
attestation_target_hit_percentage,
};
validators.insert(id.clone(), metrics);
}
Ok(ValidatorMetricsResponse { validators })
}

View File

@@ -1,5 +1,5 @@
use beacon_chain::{
test_utils::{BeaconChainHarness, EphemeralHarnessType},
test_utils::{BeaconChainHarness, BoxedMutator, EphemeralHarnessType},
BeaconChain, BeaconChainTypes,
};
use directory::DEFAULT_ROOT_DIR;
@@ -12,6 +12,7 @@ use lighthouse_network::{
types::{EnrAttestationBitfield, EnrSyncCommitteeBitfield, SyncState},
ConnectedPoint, Enr, NetworkGlobals, PeerId, PeerManager,
};
use logging::test_logger;
use network::{NetworkReceivers, NetworkSenders};
use sensitive_url::SensitiveUrl;
use slog::Logger;
@@ -19,6 +20,7 @@ use std::future::Future;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
use std::time::Duration;
use store::MemoryStore;
use tokio::sync::oneshot;
use types::{ChainSpec, EthSpec};
@@ -47,13 +49,30 @@ pub struct ApiServer<E: EthSpec, SFut: Future<Output = ()>> {
pub external_peer_id: PeerId,
}
type Mutator<E> = BoxedMutator<E, MemoryStore<E>, MemoryStore<E>>;
impl<E: EthSpec> InteractiveTester<E> {
pub async fn new(spec: Option<ChainSpec>, validator_count: usize) -> Self {
let harness = BeaconChainHarness::builder(E::default())
Self::new_with_mutator(spec, validator_count, None).await
}
pub async fn new_with_mutator(
spec: Option<ChainSpec>,
validator_count: usize,
mutator: Option<Mutator<E>>,
) -> Self {
let mut harness_builder = BeaconChainHarness::builder(E::default())
.spec_or_default(spec)
.deterministic_keypairs(validator_count)
.fresh_ephemeral_store()
.build();
.logger(test_logger())
.mock_execution_layer()
.fresh_ephemeral_store();
if let Some(mutator) = mutator {
harness_builder = harness_builder.initial_mutator(mutator);
}
let harness = harness_builder.build();
let ApiServer {
server,

View File

@@ -1,9 +1,22 @@
//! Generic tests that make use of the (newer) `InteractiveApiTester`
use crate::common::*;
use beacon_chain::test_utils::{AttestationStrategy, BlockStrategy};
use beacon_chain::{
chain_config::ReOrgThreshold,
test_utils::{AttestationStrategy, BlockStrategy},
};
use eth2::types::DepositContractData;
use execution_layer::{ForkChoiceState, PayloadAttributes};
use parking_lot::Mutex;
use slot_clock::SlotClock;
use state_processing::state_advance::complete_state_advance;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tree_hash::TreeHash;
use types::{EthSpec, FullPayload, MainnetEthSpec, Slot};
use types::{
Address, Epoch, EthSpec, ExecPayload, ExecutionBlockHash, ForkName, FullPayload,
MainnetEthSpec, ProposerPreparationData, Slot,
};
type E = MainnetEthSpec;
@@ -33,6 +46,495 @@ async fn deposit_contract_custom_network() {
assert_eq!(result, expected);
}
/// Data structure for tracking fork choice updates received by the mock execution layer.
#[derive(Debug, Default)]
struct ForkChoiceUpdates {
updates: HashMap<ExecutionBlockHash, Vec<ForkChoiceUpdateMetadata>>,
}
#[derive(Debug, Clone)]
struct ForkChoiceUpdateMetadata {
received_at: Duration,
state: ForkChoiceState,
payload_attributes: Option<PayloadAttributes>,
}
impl ForkChoiceUpdates {
fn insert(&mut self, update: ForkChoiceUpdateMetadata) {
self.updates
.entry(update.state.head_block_hash)
.or_insert_with(Vec::new)
.push(update);
}
fn contains_update_for(&self, block_hash: ExecutionBlockHash) -> bool {
self.updates.contains_key(&block_hash)
}
/// Find the first fork choice update for `head_block_hash` with payload attributes for a
/// block proposal at `proposal_timestamp`.
fn first_update_with_payload_attributes(
&self,
head_block_hash: ExecutionBlockHash,
proposal_timestamp: u64,
) -> Option<ForkChoiceUpdateMetadata> {
self.updates
.get(&head_block_hash)?
.iter()
.find(|update| {
update
.payload_attributes
.as_ref()
.map_or(false, |payload_attributes| {
payload_attributes.timestamp == proposal_timestamp
})
})
.cloned()
}
}
pub struct ReOrgTest {
head_slot: Slot,
/// Number of slots between parent block and canonical head.
parent_distance: u64,
/// Number of slots between head block and block proposal slot.
head_distance: u64,
re_org_threshold: u64,
max_epochs_since_finalization: u64,
percent_parent_votes: usize,
percent_empty_votes: usize,
percent_head_votes: usize,
should_re_org: bool,
misprediction: bool,
}
impl Default for ReOrgTest {
/// Default config represents a regular easy re-org.
fn default() -> Self {
Self {
head_slot: Slot::new(30),
parent_distance: 1,
head_distance: 1,
re_org_threshold: 20,
max_epochs_since_finalization: 2,
percent_parent_votes: 100,
percent_empty_votes: 100,
percent_head_votes: 0,
should_re_org: true,
misprediction: false,
}
}
}
// Test that the beacon node will try to perform proposer boost re-orgs on late blocks when
// configured.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn proposer_boost_re_org_zero_weight() {
proposer_boost_re_org_test(ReOrgTest::default()).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn proposer_boost_re_org_epoch_boundary() {
proposer_boost_re_org_test(ReOrgTest {
head_slot: Slot::new(31),
should_re_org: false,
..Default::default()
})
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn proposer_boost_re_org_slot_after_epoch_boundary() {
proposer_boost_re_org_test(ReOrgTest {
head_slot: Slot::new(33),
..Default::default()
})
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn proposer_boost_re_org_bad_ffg() {
proposer_boost_re_org_test(ReOrgTest {
head_slot: Slot::new(64 + 22),
should_re_org: false,
..Default::default()
})
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn proposer_boost_re_org_no_finality() {
proposer_boost_re_org_test(ReOrgTest {
head_slot: Slot::new(96),
percent_parent_votes: 100,
percent_empty_votes: 0,
percent_head_votes: 100,
should_re_org: false,
..Default::default()
})
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn proposer_boost_re_org_finality() {
proposer_boost_re_org_test(ReOrgTest {
head_slot: Slot::new(129),
..Default::default()
})
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn proposer_boost_re_org_parent_distance() {
proposer_boost_re_org_test(ReOrgTest {
head_slot: Slot::new(30),
parent_distance: 2,
should_re_org: false,
..Default::default()
})
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn proposer_boost_re_org_head_distance() {
proposer_boost_re_org_test(ReOrgTest {
head_slot: Slot::new(29),
head_distance: 2,
should_re_org: false,
..Default::default()
})
.await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn proposer_boost_re_org_very_unhealthy() {
proposer_boost_re_org_test(ReOrgTest {
head_slot: Slot::new(31),
parent_distance: 2,
head_distance: 2,
percent_parent_votes: 10,
percent_empty_votes: 10,
percent_head_votes: 10,
should_re_org: false,
..Default::default()
})
.await;
}
/// The head block is late but still receives 30% of the committee vote, leading to a misprediction.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn proposer_boost_re_org_weight_misprediction() {
proposer_boost_re_org_test(ReOrgTest {
head_slot: Slot::new(30),
percent_empty_votes: 70,
percent_head_votes: 30,
should_re_org: false,
misprediction: true,
..Default::default()
})
.await;
}
/// Run a proposer boost re-org test.
///
/// - `head_slot`: the slot of the canonical head to be reorged
/// - `reorg_threshold`: committee percentage value for reorging
/// - `num_empty_votes`: percentage of comm of attestations for the parent block
/// - `num_head_votes`: number of attestations for the head block
/// - `should_re_org`: whether the proposer should build on the parent rather than the head
pub async fn proposer_boost_re_org_test(
ReOrgTest {
head_slot,
parent_distance,
head_distance,
re_org_threshold,
max_epochs_since_finalization,
percent_parent_votes,
percent_empty_votes,
percent_head_votes,
should_re_org,
misprediction,
}: ReOrgTest,
) {
assert!(head_slot > 0);
// We require a network with execution enabled so we can check EL message timings.
let mut spec = ForkName::Merge.make_genesis_spec(E::default_spec());
spec.terminal_total_difficulty = 1.into();
// Ensure there are enough validators to have `attesters_per_slot`.
let attesters_per_slot = 10;
let validator_count = E::slots_per_epoch() as usize * attesters_per_slot;
let all_validators = (0..validator_count).collect::<Vec<usize>>();
let num_initial = head_slot.as_u64().checked_sub(parent_distance + 1).unwrap();
// Check that the required vote percentages can be satisfied exactly using `attesters_per_slot`.
assert_eq!(100 % attesters_per_slot, 0);
let percent_per_attester = 100 / attesters_per_slot;
assert_eq!(percent_parent_votes % percent_per_attester, 0);
assert_eq!(percent_empty_votes % percent_per_attester, 0);
assert_eq!(percent_head_votes % percent_per_attester, 0);
let num_parent_votes = Some(attesters_per_slot * percent_parent_votes / 100);
let num_empty_votes = Some(attesters_per_slot * percent_empty_votes / 100);
let num_head_votes = Some(attesters_per_slot * percent_head_votes / 100);
let tester = InteractiveTester::<E>::new_with_mutator(
Some(spec),
validator_count,
Some(Box::new(move |builder| {
builder
.proposer_re_org_threshold(Some(ReOrgThreshold(re_org_threshold)))
.proposer_re_org_max_epochs_since_finalization(Epoch::new(
max_epochs_since_finalization,
))
})),
)
.await;
let harness = &tester.harness;
let mock_el = harness.mock_execution_layer.as_ref().unwrap();
let execution_ctx = mock_el.server.ctx.clone();
let slot_clock = &harness.chain.slot_clock;
// Move to terminal block.
mock_el.server.all_payloads_valid();
execution_ctx
.execution_block_generator
.write()
.move_to_terminal_block()
.unwrap();
// Send proposer preparation data for all validators.
let proposer_preparation_data = all_validators
.iter()
.map(|i| ProposerPreparationData {
validator_index: *i as u64,
fee_recipient: Address::from_low_u64_be(*i as u64),
})
.collect::<Vec<_>>();
harness
.chain
.execution_layer
.as_ref()
.unwrap()
.update_proposer_preparation(
head_slot.epoch(E::slots_per_epoch()) + 1,
&proposer_preparation_data,
)
.await;
// Create some chain depth.
harness.advance_slot();
harness
.extend_chain(
num_initial as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
)
.await;
// Start collecting fork choice updates.
let forkchoice_updates = Arc::new(Mutex::new(ForkChoiceUpdates::default()));
let forkchoice_updates_inner = forkchoice_updates.clone();
let chain_inner = harness.chain.clone();
execution_ctx
.hook
.lock()
.set_forkchoice_updated_hook(Box::new(move |state, payload_attributes| {
let received_at = chain_inner.slot_clock.now_duration().unwrap();
let state = ForkChoiceState::from(state);
let payload_attributes = payload_attributes.map(Into::into);
let update = ForkChoiceUpdateMetadata {
received_at,
state,
payload_attributes,
};
forkchoice_updates_inner.lock().insert(update);
None
}));
// We set up the following block graph, where B is a block that arrives late and is re-orged
// by C.
//
// A | B | - |
// ^ | - | C |
let slot_a = Slot::new(num_initial + 1);
let slot_b = slot_a + parent_distance;
let slot_c = slot_b + head_distance;
harness.advance_slot();
let (block_a_root, block_a, state_a) = harness
.add_block_at_slot(slot_a, harness.get_current_state())
.await
.unwrap();
// Attest to block A during slot A.
let (block_a_parent_votes, _) = harness.make_attestations_with_limit(
&all_validators,
&state_a,
state_a.canonical_root(),
block_a_root,
slot_a,
num_parent_votes,
);
harness.process_attestations(block_a_parent_votes);
// Attest to block A during slot B.
for _ in 0..parent_distance {
harness.advance_slot();
}
let (block_a_empty_votes, block_a_attesters) = harness.make_attestations_with_limit(
&all_validators,
&state_a,
state_a.canonical_root(),
block_a_root,
slot_b,
num_empty_votes,
);
harness.process_attestations(block_a_empty_votes);
let remaining_attesters = all_validators
.iter()
.copied()
.filter(|index| !block_a_attesters.contains(index))
.collect::<Vec<_>>();
// Produce block B and process it halfway through the slot.
let (block_b, mut state_b) = harness.make_block(state_a.clone(), slot_b).await;
let block_b_root = block_b.canonical_root();
let obs_time = slot_clock.start_of(slot_b).unwrap() + slot_clock.slot_duration() / 2;
slot_clock.set_current_time(obs_time);
harness.chain.block_times_cache.write().set_time_observed(
block_b_root,
slot_b,
obs_time,
None,
None,
);
harness.process_block_result(block_b.clone()).await.unwrap();
// Add attestations to block B.
let (block_b_head_votes, _) = harness.make_attestations_with_limit(
&remaining_attesters,
&state_b,
state_b.canonical_root(),
block_b_root.into(),
slot_b,
num_head_votes,
);
harness.process_attestations(block_b_head_votes);
let payload_lookahead = harness.chain.config.prepare_payload_lookahead;
let fork_choice_lookahead = Duration::from_millis(500);
while harness.get_current_slot() != slot_c {
let current_slot = harness.get_current_slot();
let next_slot = current_slot + 1;
// Simulate the scheduled call to prepare proposers at 8 seconds into the slot.
harness.advance_to_slot_lookahead(next_slot, payload_lookahead);
harness
.chain
.prepare_beacon_proposer(current_slot)
.await
.unwrap();
// Simulate the scheduled call to fork choice + prepare proposers 500ms before the
// next slot.
harness.advance_to_slot_lookahead(next_slot, fork_choice_lookahead);
harness.chain.recompute_head_at_slot(next_slot).await;
harness
.chain
.prepare_beacon_proposer(current_slot)
.await
.unwrap();
harness.advance_slot();
harness.chain.per_slot_task().await;
}
// Produce block C.
// Advance state_b so we can get the proposer.
complete_state_advance(&mut state_b, None, slot_c, &harness.chain.spec).unwrap();
let proposer_index = state_b
.get_beacon_proposer_index(slot_c, &harness.chain.spec)
.unwrap();
let randao_reveal = harness
.sign_randao_reveal(&state_b, proposer_index, slot_c)
.into();
let unsigned_block_c = tester
.client
.get_validator_blocks(slot_c, &randao_reveal, None)
.await
.unwrap()
.data;
let block_c = harness.sign_beacon_block(unsigned_block_c, &state_b);
if should_re_org {
// Block C should build on A.
assert_eq!(block_c.parent_root(), block_a_root.into());
} else {
// Block C should build on B.
assert_eq!(block_c.parent_root(), block_b_root);
}
// Applying block C should cause it to become head regardless (re-org or continuation).
let block_root_c = harness
.process_block_result(block_c.clone())
.await
.unwrap()
.into();
assert_eq!(harness.head_block_root(), block_root_c);
// Check the fork choice updates that were sent.
let forkchoice_updates = forkchoice_updates.lock();
let block_a_exec_hash = block_a.message().execution_payload().unwrap().block_hash();
let block_b_exec_hash = block_b.message().execution_payload().unwrap().block_hash();
let block_c_timestamp = block_c.message().execution_payload().unwrap().timestamp();
// If we re-orged then no fork choice update for B should have been sent.
assert_eq!(
should_re_org,
!forkchoice_updates.contains_update_for(block_b_exec_hash),
"{block_b_exec_hash:?}"
);
// Check the timing of the first fork choice update with payload attributes for block C.
let c_parent_hash = if should_re_org {
block_a_exec_hash
} else {
block_b_exec_hash
};
let first_update = forkchoice_updates
.first_update_with_payload_attributes(c_parent_hash, block_c_timestamp)
.unwrap();
let payload_attribs = first_update.payload_attributes.as_ref().unwrap();
let lookahead = slot_clock
.start_of(slot_c)
.unwrap()
.checked_sub(first_update.received_at)
.unwrap();
if !misprediction {
assert_eq!(
lookahead, payload_lookahead,
"lookahead={lookahead:?}, timestamp={}, prev_randao={:?}",
payload_attribs.timestamp, payload_attribs.prev_randao,
);
} else {
// On a misprediction we issue the first fcU 500ms before creating a block!
assert_eq!(
lookahead, fork_choice_lookahead,
"timestamp={}, prev_randao={:?}",
payload_attribs.timestamp, payload_attribs.prev_randao,
);
}
}
// Test that running fork choice before proposing results in selection of the correct head.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
pub async fn fork_choice_before_proposal() {