mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-20 22:38:34 +00:00
Don't panic in forkchoiceUpdated handler (#3165)
## Issue Addressed Fix a panic due to misuse of the Tokio executor when processing a forkchoiceUpdated response. We were previously calling `process_invalid_execution_payload` from the async function `update_execution_engine_forkchoice_async`, which resulted in a panic because `process_invalid_execution_payload` contains a call to fork choice, which ultimately calls `block_on`. An example backtrace can be found here: https://gist.github.com/michaelsproul/ac5da03e203d6ffac672423eaf52fb20 ## Proposed Changes Wrap the call to `process_invalid_execution_payload` in a `spawn_blocking` so that `block_on` is no longer called from an async context. ## Additional Info - I've been thinking about how to catch bugs like this with static analysis (a new Clippy lint). - The payload validation tests have been re-worked to support distinct responses from the mock EE for newPayload and forkchoiceUpdated. Three new tests have been added covering the `Invalid`, `InvalidBlockHash` and `InvalidTerminalBlock` cases. - I think we need a bunch more tests of different legal and illegal variations
This commit is contained in:
@@ -2208,7 +2208,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
/// This method is generally much more efficient than importing each block using
|
||||
/// `Self::process_block`.
|
||||
pub fn process_chain_segment(
|
||||
&self,
|
||||
self: &Arc<Self>,
|
||||
chain_segment: Vec<SignedBeaconBlock<T::EthSpec>>,
|
||||
) -> ChainSegmentResult<T::EthSpec> {
|
||||
let mut filtered_chain_segment = Vec::with_capacity(chain_segment.len());
|
||||
@@ -2402,7 +2402,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
/// Returns an `Err` if the given block was invalid, or an error was encountered during
|
||||
/// verification.
|
||||
pub fn process_block<B: IntoFullyVerifiedBlock<T>>(
|
||||
&self,
|
||||
self: &Arc<Self>,
|
||||
unverified_block: B,
|
||||
) -> Result<Hash256, BlockError<T::EthSpec>> {
|
||||
// Start the Prometheus timer.
|
||||
@@ -3234,7 +3234,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
///
|
||||
/// See the documentation of `InvalidationOperation` for information about defining `op`.
|
||||
pub fn process_invalid_execution_payload(
|
||||
&self,
|
||||
self: &Arc<Self>,
|
||||
op: &InvalidationOperation,
|
||||
) -> Result<(), Error> {
|
||||
debug!(
|
||||
@@ -3302,7 +3302,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
}
|
||||
|
||||
/// Execute the fork choice algorithm and enthrone the result as the canonical head.
|
||||
pub fn fork_choice(&self) -> Result<(), Error> {
|
||||
pub fn fork_choice(self: &Arc<Self>) -> Result<(), Error> {
|
||||
metrics::inc_counter(&metrics::FORK_CHOICE_REQUESTS);
|
||||
let _timer = metrics::start_timer(&metrics::FORK_CHOICE_TIMES);
|
||||
|
||||
@@ -3315,7 +3315,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
result
|
||||
}
|
||||
|
||||
fn fork_choice_internal(&self) -> Result<(), Error> {
|
||||
fn fork_choice_internal(self: &Arc<Self>) -> Result<(), Error> {
|
||||
// Atomically obtain the head block root and the finalized block.
|
||||
let (beacon_block_root, finalized_block) = {
|
||||
let mut fork_choice = self.fork_choice.write();
|
||||
@@ -3718,7 +3718,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn prepare_beacon_proposer_blocking(&self) -> Result<(), Error> {
|
||||
pub fn prepare_beacon_proposer_blocking(self: &Arc<Self>) -> Result<(), Error> {
|
||||
let current_slot = self.slot()?;
|
||||
|
||||
// Avoids raising an error before Bellatrix.
|
||||
@@ -3750,7 +3750,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
/// 1. We're in the tail-end of the slot (as defined by PAYLOAD_PREPARATION_LOOKAHEAD_FACTOR)
|
||||
/// 2. The head block is one slot (or less) behind the prepare slot (e.g., we're preparing for
|
||||
/// the next slot and the block at the current slot is already known).
|
||||
pub async fn prepare_beacon_proposer_async(&self, current_slot: Slot) -> Result<(), Error> {
|
||||
pub async fn prepare_beacon_proposer_async(
|
||||
self: &Arc<Self>,
|
||||
current_slot: Slot,
|
||||
) -> Result<(), Error> {
|
||||
let prepare_slot = current_slot + 1;
|
||||
let prepare_epoch = prepare_slot.epoch(T::EthSpec::slots_per_epoch());
|
||||
|
||||
@@ -3952,7 +3955,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
}
|
||||
|
||||
pub fn update_execution_engine_forkchoice_blocking(
|
||||
&self,
|
||||
self: &Arc<Self>,
|
||||
current_slot: Slot,
|
||||
) -> Result<(), Error> {
|
||||
// Avoids raising an error before Bellatrix.
|
||||
@@ -3973,7 +3976,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
}
|
||||
|
||||
pub async fn update_execution_engine_forkchoice_async(
|
||||
&self,
|
||||
self: &Arc<Self>,
|
||||
current_slot: Slot,
|
||||
) -> Result<(), Error> {
|
||||
let next_slot = current_slot + 1;
|
||||
@@ -4091,7 +4094,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
drop(forkchoice_lock);
|
||||
|
||||
match forkchoice_updated_response {
|
||||
Ok(status) => match &status {
|
||||
Ok(status) => match status {
|
||||
PayloadStatus::Valid => {
|
||||
// Ensure that fork choice knows that the block is no longer optimistic.
|
||||
if let Err(e) = self
|
||||
@@ -4134,13 +4137,24 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
);
|
||||
// The execution engine has stated that all blocks between the
|
||||
// `head_execution_block_hash` and `latest_valid_hash` are invalid.
|
||||
self.process_invalid_execution_payload(
|
||||
&InvalidationOperation::InvalidateMany {
|
||||
head_block_root,
|
||||
always_invalidate_head: true,
|
||||
latest_valid_ancestor: *latest_valid_hash,
|
||||
},
|
||||
)?;
|
||||
let chain = self.clone();
|
||||
execution_layer
|
||||
.executor()
|
||||
.spawn_blocking_handle(
|
||||
move || {
|
||||
chain.process_invalid_execution_payload(
|
||||
&InvalidationOperation::InvalidateMany {
|
||||
head_block_root,
|
||||
always_invalidate_head: true,
|
||||
latest_valid_ancestor: latest_valid_hash,
|
||||
},
|
||||
)
|
||||
},
|
||||
"process_invalid_execution_payload_many",
|
||||
)
|
||||
.ok_or(BeaconChainError::RuntimeShutdown)?
|
||||
.await
|
||||
.map_err(BeaconChainError::ProcessInvalidExecutionPayload)??;
|
||||
|
||||
Err(BeaconChainError::ExecutionForkChoiceUpdateInvalid { status })
|
||||
}
|
||||
@@ -4156,11 +4170,22 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
//
|
||||
// Using a `None` latest valid ancestor will result in only the head block
|
||||
// being invalidated (no ancestors).
|
||||
self.process_invalid_execution_payload(
|
||||
&InvalidationOperation::InvalidateOne {
|
||||
block_root: head_block_root,
|
||||
},
|
||||
)?;
|
||||
let chain = self.clone();
|
||||
execution_layer
|
||||
.executor()
|
||||
.spawn_blocking_handle(
|
||||
move || {
|
||||
chain.process_invalid_execution_payload(
|
||||
&InvalidationOperation::InvalidateOne {
|
||||
block_root: head_block_root,
|
||||
},
|
||||
)
|
||||
},
|
||||
"process_invalid_execution_payload_single",
|
||||
)
|
||||
.ok_or(BeaconChainError::RuntimeShutdown)?
|
||||
.await
|
||||
.map_err(BeaconChainError::ProcessInvalidExecutionPayload)??;
|
||||
|
||||
Err(BeaconChainError::ExecutionForkChoiceUpdateInvalid { status })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user