mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-10 12:11:59 +00:00
Don't return errors when fork choice fails (#3370)
## Issue Addressed
NA
## Proposed Changes
There are scenarios where the only viable head will have an invalid execution payload, in this scenario the `get_head` function on `proto_array` will return an error. We must recover from this scenario by importing blocks from the network.
This PR stops `BeaconChain::recompute_head` from returning an error so that we can't accidentally start down-scoring peers or aborting block import just because the current head has an invalid payload.
## Reviewer Notes
The following changes are included:
1. Allow `fork_choice.get_head` to fail gracefully in `BeaconChain::process_block` when trying to update the `early_attester_cache`; simply don't add the block to the cache rather than aborting the entire process.
1. Don't return an error from `BeaconChain::recompute_head_at_current_slot` and `BeaconChain::recompute_head` to defensively prevent calling functions from aborting any process just because the fork choice function failed to run.
- This should have practically no effect, since most callers were still continuing if recomputing the head failed.
- The outlier is that the API will return 200 rather than a 500 when fork choice fails.
1. Add the `ProtoArrayForkChoice::set_all_blocks_to_optimistic` function to recover from the scenario where we've rebooted and the persisted fork choice has an invalid head.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
use crate::error::Error;
|
||||
use crate::proto_array::{InvalidationOperation, Iter, ProposerBoost, ProtoArray, ProtoNode};
|
||||
use crate::proto_array::{
|
||||
calculate_proposer_boost, InvalidationOperation, Iter, ProposerBoost, ProtoArray, ProtoNode,
|
||||
};
|
||||
use crate::ssz_container::SszContainer;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::{Decode, Encode};
|
||||
@@ -303,6 +305,106 @@ impl ProtoArrayForkChoice {
|
||||
.map_err(|e| format!("find_head failed: {:?}", e))
|
||||
}
|
||||
|
||||
/// For all nodes, regardless of their relationship to the finalized block, set their execution
|
||||
/// status to be optimistic.
|
||||
///
|
||||
/// In practice this means forgetting any `VALID` or `INVALID` statuses.
|
||||
pub fn set_all_blocks_to_optimistic<E: EthSpec>(
|
||||
&mut self,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), String> {
|
||||
// Iterate backwards through all nodes in the `proto_array`. Whilst it's not strictly
|
||||
// required to do this process in reverse, it seems natural when we consider how LMD votes
|
||||
// are counted.
|
||||
//
|
||||
// This function will touch all blocks, even those that do not descend from the finalized
|
||||
// block. Since this function is expected to run at start-up during very rare
|
||||
// circumstances we prefer simplicity over efficiency.
|
||||
for node_index in (0..self.proto_array.nodes.len()).rev() {
|
||||
let node = self
|
||||
.proto_array
|
||||
.nodes
|
||||
.get_mut(node_index)
|
||||
.ok_or("unreachable index out of bounds in proto_array nodes")?;
|
||||
|
||||
match node.execution_status {
|
||||
ExecutionStatus::Invalid(block_hash) => {
|
||||
node.execution_status = ExecutionStatus::Optimistic(block_hash);
|
||||
|
||||
// Restore the weight of the node, it would have been set to `0` in
|
||||
// `apply_score_changes` when it was invalidated.
|
||||
let mut restored_weight: u64 = self
|
||||
.votes
|
||||
.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(validator_index, vote)| {
|
||||
if vote.current_root == node.root {
|
||||
// Any voting validator that does not have a balance should be
|
||||
// ignored. This is consistent with `compute_deltas`.
|
||||
self.balances.get(validator_index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sum();
|
||||
|
||||
// If the invalid root was boosted, apply the weight to it and
|
||||
// ancestors.
|
||||
if let Some(proposer_score_boost) = spec.proposer_score_boost {
|
||||
if self.proto_array.previous_proposer_boost.root == node.root {
|
||||
// Compute the score based upon the current balances. We can't rely on
|
||||
// the `previous_proposr_boost.score` since it is set to zero with an
|
||||
// invalid node.
|
||||
let proposer_score =
|
||||
calculate_proposer_boost::<E>(&self.balances, proposer_score_boost)
|
||||
.ok_or("Failed to compute proposer boost")?;
|
||||
// Store the score we've applied here so it can be removed in
|
||||
// a later call to `apply_score_changes`.
|
||||
self.proto_array.previous_proposer_boost.score = proposer_score;
|
||||
// Apply this boost to this node.
|
||||
restored_weight = restored_weight
|
||||
.checked_add(proposer_score)
|
||||
.ok_or("Overflow when adding boost to weight")?;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the restored weight to the node and all ancestors.
|
||||
if restored_weight > 0 {
|
||||
let mut node_or_ancestor = node;
|
||||
loop {
|
||||
node_or_ancestor.weight = node_or_ancestor
|
||||
.weight
|
||||
.checked_add(restored_weight)
|
||||
.ok_or("Overflow when adding weight to ancestor")?;
|
||||
|
||||
if let Some(parent_index) = node_or_ancestor.parent {
|
||||
node_or_ancestor = self
|
||||
.proto_array
|
||||
.nodes
|
||||
.get_mut(parent_index)
|
||||
.ok_or(format!("Missing parent index: {}", parent_index))?;
|
||||
} else {
|
||||
// This is either the finalized block or a block that does not
|
||||
// descend from the finalized block.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// There are no balance changes required if the node was either valid or
|
||||
// optimistic.
|
||||
ExecutionStatus::Valid(block_hash) | ExecutionStatus::Optimistic(block_hash) => {
|
||||
node.execution_status = ExecutionStatus::Optimistic(block_hash)
|
||||
}
|
||||
// An irrelevant node cannot become optimistic, this is a no-op.
|
||||
ExecutionStatus::Irrelevant(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn maybe_prune(&mut self, finalized_root: Hash256) -> Result<(), String> {
|
||||
self.proto_array
|
||||
.maybe_prune(finalized_root)
|
||||
|
||||
Reference in New Issue
Block a user