mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-31 05:07:12 +00:00
Resolve merge conflicts
This commit is contained in:
@@ -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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user