Resolve merge conflicts

This commit is contained in:
Eitan Seri-Levi
2026-01-02 08:52:14 -06:00
918 changed files with 49304 additions and 37273 deletions

View File

@@ -34,12 +34,12 @@
use crate::persisted_fork_choice::PersistedForkChoice;
use crate::shuffling_cache::BlockShufflingIds;
use crate::{
beacon_chain::{BeaconForkChoice, BeaconStore, OverrideForkchoiceUpdate, FORK_CHOICE_DB_KEY},
BeaconChain, BeaconChainError as Error, BeaconChainTypes, BeaconSnapshot,
beacon_chain::{BeaconForkChoice, BeaconStore, FORK_CHOICE_DB_KEY, OverrideForkchoiceUpdate},
block_times_cache::BlockTimesCache,
events::ServerSentEventHandler,
metrics,
validator_monitor::{get_slot_delay_ms, timestamp_now},
BeaconChain, BeaconChainError as Error, BeaconChainTypes, BeaconSnapshot,
validator_monitor::get_slot_delay_ms,
};
use eth2::types::{EventKind, SseChainReorg, SseFinalizedCheckpoint, SseHead, SseLateHead};
use fork_choice::{
@@ -47,15 +47,19 @@ use fork_choice::{
ResetPayloadStatuses,
};
use itertools::process_results;
use lighthouse_tracing::SPAN_RECOMPUTE_HEAD;
use logging::crit;
use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockUpgradableReadGuard, RwLockWriteGuard};
use slot_clock::SlotClock;
use state_processing::AllCaches;
use std::sync::Arc;
use std::time::Duration;
use store::{iter::StateRootsIterator, KeyValueStore, KeyValueStoreOp, StoreItem};
use store::{
Error as StoreError, KeyValueStore, KeyValueStoreOp, StoreConfig, iter::StateRootsIterator,
};
use task_executor::{JoinHandle, ShutdownReason};
use tracing::{debug, error, info, warn};
use tracing::info_span;
use tracing::{debug, error, info, instrument, warn};
use types::*;
/// Simple wrapper around `RwLock` that uses private visibility to prevent any other modules from
@@ -73,11 +77,15 @@ impl<T> CanonicalHeadRwLock<T> {
Self::from(RwLock::new(item))
}
fn read(&self) -> RwLockReadGuard<T> {
fn read(&self) -> RwLockReadGuard<'_, T> {
self.0.read()
}
fn write(&self) -> RwLockWriteGuard<T> {
fn upgradable_read(&self) -> RwLockUpgradableReadGuard<'_, T> {
self.0.upgradable_read()
}
fn write(&self) -> RwLockWriteGuard<'_, T> {
self.0.write()
}
}
@@ -374,7 +382,7 @@ impl<T: BeaconChainTypes> CanonicalHead<T> {
///
/// This function is **not safe** to be public. See the module-level documentation for more
/// information about protecting from deadlocks.
fn cached_head_read_lock(&self) -> RwLockReadGuard<CachedHead<T::EthSpec>> {
fn cached_head_read_lock(&self) -> RwLockReadGuard<'_, CachedHead<T::EthSpec>> {
self.cached_head.read()
}
@@ -382,18 +390,28 @@ impl<T: BeaconChainTypes> CanonicalHead<T> {
///
/// This function is **not safe** to be public. See the module-level documentation for more
/// information about protecting from deadlocks.
fn cached_head_write_lock(&self) -> RwLockWriteGuard<CachedHead<T::EthSpec>> {
#[instrument(skip_all)]
fn cached_head_write_lock(&self) -> RwLockWriteGuard<'_, CachedHead<T::EthSpec>> {
self.cached_head.write()
}
/// Access a read-lock for fork choice.
pub fn fork_choice_read_lock(&self) -> RwLockReadGuard<BeaconForkChoice<T>> {
pub fn fork_choice_read_lock(&self) -> RwLockReadGuard<'_, BeaconForkChoice<T>> {
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_READ_LOCK_AQUIRE_TIMES);
self.fork_choice.read()
}
/// Access an upgradable read-lock for fork choice.
pub fn fork_choice_upgradable_read_lock(
&self,
) -> RwLockUpgradableReadGuard<'_, BeaconForkChoice<T>> {
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_UPGRADABLE_READ_LOCK_AQUIRE_TIMES);
self.fork_choice.upgradable_read()
}
/// Access a write-lock for fork choice.
pub fn fork_choice_write_lock(&self) -> RwLockWriteGuard<BeaconForkChoice<T>> {
#[instrument(skip_all)]
pub fn fork_choice_write_lock(&self) -> RwLockWriteGuard<'_, BeaconForkChoice<T>> {
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_WRITE_LOCK_AQUIRE_TIMES);
self.fork_choice.write()
}
@@ -476,6 +494,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Execute the fork choice algorithm and enthrone the result as the canonical head.
///
/// This method replaces the old `BeaconChain::fork_choice` method.
#[instrument(skip_all, level = "debug")]
pub async fn recompute_head_at_current_slot(self: &Arc<Self>) {
match self.slot() {
Ok(current_slot) => self.recompute_head_at_slot(current_slot).await,
@@ -499,13 +518,21 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// situation can be rectified. We avoid returning an error here so that calling functions
/// can't abort block import because an error is returned here.
pub async fn recompute_head_at_slot(self: &Arc<Self>, current_slot: Slot) {
let span = info_span!(
SPAN_RECOMPUTE_HEAD,
slot = %current_slot
);
metrics::inc_counter(&metrics::FORK_CHOICE_REQUESTS);
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_TIMES);
let chain = self.clone();
match self
.spawn_blocking_handle(
move || chain.recompute_head_at_slot_internal(current_slot),
move || {
let _guard = span.enter();
chain.recompute_head_at_slot_internal(current_slot)
},
"recompute_head_internal",
)
.await
@@ -723,15 +750,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let old_snapshot = &old_cached_head.snapshot;
// If the head changed, perform some updates.
if new_snapshot.beacon_block_root != old_snapshot.beacon_block_root {
if let Err(e) =
if new_snapshot.beacon_block_root != old_snapshot.beacon_block_root
&& let Err(e) =
self.after_new_head(&old_cached_head, &new_cached_head, new_head_proto_block)
{
crit!(
error = ?e,
"Error updating canonical head"
);
}
{
crit!(
error = ?e,
"Error updating canonical head"
);
}
// Drop the old cache head nice and early to try and free the memory as soon as possible.
@@ -741,15 +767,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
//
// The `after_finalization` function will take a write-lock on `fork_choice`, therefore it
// is a dead-lock risk to hold any other lock on fork choice at this point.
if new_view.finalized_checkpoint != old_view.finalized_checkpoint {
if let Err(e) =
if new_view.finalized_checkpoint != old_view.finalized_checkpoint
&& let Err(e) =
self.after_finalization(&new_cached_head, new_view, finalized_proto_block)
{
crit!(
error = ?e,
"Error updating finalization"
);
}
{
crit!(
error = ?e,
"Error updating finalization"
);
}
// The execution layer updates might attempt to take a write-lock on fork choice, so it's
@@ -765,6 +790,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
/// Perform updates to caches and other components after the canonical head has been changed.
#[instrument(skip_all)]
fn after_new_head(
self: &Arc<Self>,
old_cached_head: &CachedHead<T::EthSpec>,
@@ -808,7 +834,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let head_slot = new_snapshot.beacon_state.slot();
let dependent_root = new_snapshot
.beacon_state
.proposer_shuffling_decision_root(self.genesis_block_root);
.attester_shuffling_decision_root(self.genesis_block_root, RelativeEpoch::Next);
let prev_dependent_root = new_snapshot
.beacon_state
.attester_shuffling_decision_root(self.genesis_block_root, RelativeEpoch::Current);
@@ -877,23 +903,22 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
// Register a server-sent-event for a reorg (if necessary).
if let Some(depth) = reorg_distance {
if let Some(event_handler) = self
if let Some(depth) = reorg_distance
&& let Some(event_handler) = self
.event_handler
.as_ref()
.filter(|handler| handler.has_reorg_subscribers())
{
event_handler.register(EventKind::ChainReorg(SseChainReorg {
slot: head_slot,
depth: depth.as_u64(),
old_head_block: old_snapshot.beacon_block_root,
old_head_state: old_snapshot.beacon_state_root(),
new_head_block: new_snapshot.beacon_block_root,
new_head_state: new_snapshot.beacon_state_root(),
epoch: head_slot.epoch(T::EthSpec::slots_per_epoch()),
execution_optimistic: new_head_is_optimistic,
}));
}
{
event_handler.register(EventKind::ChainReorg(SseChainReorg {
slot: head_slot,
depth: depth.as_u64(),
old_head_block: old_snapshot.beacon_block_root,
old_head_state: old_snapshot.beacon_state_root(),
new_head_block: new_snapshot.beacon_block_root,
new_head_state: new_snapshot.beacon_state_root(),
epoch: head_slot.epoch(T::EthSpec::slots_per_epoch()),
execution_optimistic: new_head_is_optimistic,
}));
}
Ok(())
@@ -904,6 +929,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
///
/// This function will take a write-lock on `canonical_head.fork_choice`, therefore it would be
/// unwise to hold any lock on fork choice while calling this function.
#[instrument(skip_all)]
fn after_finalization(
self: &Arc<Self>,
new_cached_head: &CachedHead<T::EthSpec>,
@@ -916,13 +942,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.execution_status
.is_optimistic_or_invalid();
self.op_pool.prune_all(
&new_snapshot.beacon_block,
&new_snapshot.beacon_state,
self.epoch()?,
&self.spec,
);
self.observed_block_producers.write().prune(
new_view
.finalized_checkpoint
@@ -947,23 +966,23 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
self.attester_cache
.prune_below(new_view.finalized_checkpoint.epoch);
if let Some(event_handler) = self.event_handler.as_ref() {
if event_handler.has_finalized_subscribers() {
event_handler.register(EventKind::FinalizedCheckpoint(SseFinalizedCheckpoint {
epoch: new_view.finalized_checkpoint.epoch,
block: new_view.finalized_checkpoint.root,
// Provide the state root of the latest finalized block, rather than the
// specific state root at the first slot of the finalized epoch (which
// might be a skip slot).
state: finalized_proto_block.state_root,
execution_optimistic: finalized_block_is_optimistic,
}));
}
if let Some(event_handler) = self.event_handler.as_ref()
&& event_handler.has_finalized_subscribers()
{
event_handler.register(EventKind::FinalizedCheckpoint(SseFinalizedCheckpoint {
epoch: new_view.finalized_checkpoint.epoch,
block: new_view.finalized_checkpoint.root,
// Provide the state root of the latest finalized block, rather than the
// specific state root at the first slot of the finalized epoch (which
// might be a skip slot).
state: finalized_proto_block.state_root,
execution_optimistic: finalized_block_is_optimistic,
}));
}
// The store migration task requires the *state at the slot of the finalized epoch*,
// rather than the state of the latest finalized block. These two values will only
// differ when the first slot of the finalized epoch is a skip slot.
// The store migration task and op pool pruning require the *state at the first slot of the
// finalized epoch*, rather than the state of the latest finalized block. These two values
// will only differ when the first slot of the finalized epoch is a skip slot.
//
// Use the `StateRootsIterator` directly rather than `BeaconChain::state_root_at_slot`
// to ensure we use the same state that we just set as the head.
@@ -985,6 +1004,23 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
)?
.ok_or(Error::MissingFinalizedStateRoot(new_finalized_slot))?;
let update_cache = true;
let new_finalized_state = self
.store
.get_hot_state(&new_finalized_state_root, update_cache)?
.ok_or(Error::MissingBeaconState(new_finalized_state_root))?;
self.op_pool.prune_all(
&new_snapshot.beacon_block,
&new_snapshot.beacon_state,
&new_finalized_state,
self.epoch()?,
&self.spec,
);
// We just pass the state root to the finalization thread. It should be able to reload the
// state from the state_cache near instantly anyway. We could experiment with sending the
// state over a channel in future, but it's probably no quicker.
self.store_migrator.process_finalization(
new_finalized_state_root.into(),
new_view.finalized_checkpoint,
@@ -1005,25 +1041,30 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Persist fork choice to disk, writing immediately.
pub fn persist_fork_choice(&self) -> Result<(), Error> {
let _fork_choice_timer = metrics::start_timer(&metrics::PERSIST_FORK_CHOICE);
let batch = vec![self.persist_fork_choice_in_batch()];
let batch = vec![self.persist_fork_choice_in_batch()?];
self.store.hot_db.do_atomically(batch)?;
Ok(())
}
/// Return a database operation for writing fork choice to disk.
pub fn persist_fork_choice_in_batch(&self) -> KeyValueStoreOp {
Self::persist_fork_choice_in_batch_standalone(&self.canonical_head.fork_choice_read_lock())
pub fn persist_fork_choice_in_batch(&self) -> Result<KeyValueStoreOp, Error> {
Self::persist_fork_choice_in_batch_standalone(
&self.canonical_head.fork_choice_read_lock(),
self.store.get_config(),
)
.map_err(Into::into)
}
/// Return a database operation for writing fork choice to disk.
pub fn persist_fork_choice_in_batch_standalone(
fork_choice: &BeaconForkChoice<T>,
) -> KeyValueStoreOp {
store_config: &StoreConfig,
) -> Result<KeyValueStoreOp, StoreError> {
let persisted_fork_choice = PersistedForkChoice {
fork_choice: fork_choice.to_persisted(),
fork_choice_store: fork_choice.fc_store().to_persisted(),
};
persisted_fork_choice.as_kv_store_op(FORK_CHOICE_DB_KEY)
persisted_fork_choice.as_kv_store_op(FORK_CHOICE_DB_KEY, store_config)
}
}
@@ -1034,6 +1075,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
///
/// This function is called whilst holding a write-lock on the `canonical_head`. To ensure dead-lock
/// safety, **do not take any other locks inside this function**.
#[instrument(skip_all)]
fn check_finalized_payload_validity<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
finalized_proto_block: &ProtoBlock,
@@ -1117,6 +1159,7 @@ fn perform_debug_logging<T: BeaconChainTypes>(
}
}
#[instrument(skip_all)]
fn spawn_execution_layer_updates<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
forkchoice_update_params: ForkchoiceUpdateParameters,
@@ -1292,7 +1335,10 @@ fn observe_head_block_delays<E: EthSpec, S: SlotClock>(
slot_clock: &S,
event_handler: Option<&ServerSentEventHandler<E>>,
) {
let block_time_set_as_head = timestamp_now();
let Some(block_time_set_as_head) = slot_clock.now_duration() else {
// Practically unreachable: the slot clock's time should not be before the UNIX epoch.
return;
};
let head_block_root = head_block.root;
let head_block_slot = head_block.slot;
let head_block_is_optimistic = head_block.execution_status.is_optimistic_or_invalid();
@@ -1313,10 +1359,6 @@ fn observe_head_block_delays<E: EthSpec, S: SlotClock>(
// If a block comes in from over 4 slots ago, it is most likely a block from sync.
let block_from_sync = block_delay_total > slot_clock.slot_duration() * 4;
// Determine whether the block has been set as head too late for proper attestation
// production.
let late_head = block_delay_total >= slot_clock.unagg_attestation_production_delay();
// Do not store metrics if the block was > 4 slots old, this helps prevent noise during
// sync.
if !block_from_sync {
@@ -1415,6 +1457,14 @@ fn observe_head_block_delays<E: EthSpec, S: SlotClock>(
.as_millis() as i64,
);
// Consider the block late if the time it became attestable is after the attestation
// deadline. If the block was not made attestable, use the set-as-head time.
let attestable_delay = block_delays.attestable.unwrap_or(block_delay_total);
// Determine whether the block has been set as head too late for proper attestation
// production.
let late_head = attestable_delay >= slot_clock.unagg_attestation_production_delay();
// If the block was enshrined as head too late for attestations to be created for it,
// log a debug warning and increment a metric.
let format_delay = |delay: &Option<Duration>| {
@@ -1437,6 +1487,24 @@ fn observe_head_block_delays<E: EthSpec, S: SlotClock>(
set_as_head_time_ms = format_delay(&block_delays.set_as_head),
"Delayed head block"
);
if let Some(event_handler) = event_handler
&& event_handler.has_late_head_subscribers()
{
let peer_info = block_times_cache.get_peer_info(head_block_root);
event_handler.register(EventKind::LateHead(SseLateHead {
slot: head_block_slot,
block: head_block_root,
peer_id: peer_info.id,
peer_client: peer_info.client,
proposer_index: head_block_proposer_index,
proposer_graffiti: head_block_graffiti,
block_delay: block_delay_total,
observed_delay: block_delays.observed,
imported_delay: block_delays.imported,
set_as_head_delay: block_delays.set_as_head,
execution_optimistic: head_block_is_optimistic,
}));
}
} else {
debug!(
block_root = ?head_block_root,
@@ -1455,29 +1523,4 @@ fn observe_head_block_delays<E: EthSpec, S: SlotClock>(
);
}
}
if let Some(event_handler) = event_handler {
if !block_from_sync && late_head && event_handler.has_late_head_subscribers() {
let peer_info = block_times_cache.get_peer_info(head_block_root);
let block_delays = block_times_cache.get_block_delays(
head_block_root,
slot_clock
.start_of(head_block_slot)
.unwrap_or_else(|| Duration::from_secs(0)),
);
event_handler.register(EventKind::LateHead(SseLateHead {
slot: head_block_slot,
block: head_block_root,
peer_id: peer_info.id,
peer_client: peer_info.client,
proposer_index: head_block_proposer_index,
proposer_graffiti: head_block_graffiti,
block_delay: block_delay_total,
observed_delay: block_delays.observed,
imported_delay: block_delays.imported,
set_as_head_delay: block_delays.set_as_head,
execution_optimistic: head_block_is_optimistic,
}));
}
}
}