Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Fix edge-case when finding the finalized descendant #3924

Closed
wants to merge 16 commits into from
4 changes: 2 additions & 2 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::beacon_proposer_cache::compute_proposer_duties_from_head;
use crate::beacon_proposer_cache::BeaconProposerCache;
use crate::block_times_cache::BlockTimesCache;
use crate::block_verification::{
check_block_is_finalized_descendant, check_block_relevancy, get_block_root,
check_block_is_finalized_checkpoint_or_descendant, check_block_relevancy, get_block_root,
signature_verify_chain_segment, BlockError, ExecutionPendingBlock, GossipVerifiedBlock,
IntoExecutionPendingBlock, PayloadVerificationOutcome, POS_PANDA_BANNER,
};
Expand Down Expand Up @@ -2736,7 +2736,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let mut fork_choice = self.canonical_head.fork_choice_write_lock();

// Do not import a block that doesn't descend from the finalized root.
check_block_is_finalized_descendant(self, &fork_choice, &signed_block)?;
check_block_is_finalized_checkpoint_or_descendant(self, &fork_choice, &signed_block)?;

// Register the new block with the fork choice service.
{
Expand Down
6 changes: 3 additions & 3 deletions beacon_node/beacon_chain/src/block_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,7 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
// Do not process a block that doesn't descend from the finalized root.
//
// We check this *before* we load the parent so that we can return a more detailed error.
check_block_is_finalized_descendant(
check_block_is_finalized_checkpoint_or_descendant(
chain,
&chain.canonical_head.fork_choice_write_lock(),
&block,
Expand Down Expand Up @@ -1564,12 +1564,12 @@ fn check_block_against_finalized_slot<T: BeaconChainTypes>(
/// ## Warning
///
/// Taking a lock on the `chain.canonical_head.fork_choice` might cause a deadlock here.
pub fn check_block_is_finalized_descendant<T: BeaconChainTypes>(
pub fn check_block_is_finalized_checkpoint_or_descendant<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
fork_choice: &BeaconForkChoice<T>,
block: &Arc<SignedBeaconBlock<T::EthSpec>>,
) -> Result<(), BlockError<T::EthSpec>> {
if fork_choice.is_descendant_of_finalized(block.parent_root()) {
if fork_choice.is_finalized_checkpoint_or_descendant(block.parent_root()) {
Ok(())
} else {
// If fork choice does *not* consider the parent to be a descendant of the finalized block,
Expand Down
17 changes: 9 additions & 8 deletions consensus/fork_choice/src/fork_choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ where
op: &InvalidationOperation,
) -> Result<(), Error<T::Error>> {
self.proto_array
.process_execution_payload_invalidation(op)
.process_execution_payload_invalidation::<E>(op)
.map_err(Error::FailedToProcessInvalidExecutionPayload)
}

Expand Down Expand Up @@ -1282,7 +1282,7 @@ where

if store.best_justified_checkpoint().epoch > store.justified_checkpoint().epoch {
let store = &self.fc_store;
if self.is_descendant_of_finalized(store.best_justified_checkpoint().root) {
if self.is_finalized_checkpoint_or_descendant(store.best_justified_checkpoint().root) {
let store = &mut self.fc_store;
store
.set_justified_checkpoint(*store.best_justified_checkpoint())
Expand Down Expand Up @@ -1323,12 +1323,13 @@ where

/// Returns `true` if the block is known **and** a descendant of the finalized root.
pub fn contains_block(&self, block_root: &Hash256) -> bool {
self.proto_array.contains_block(block_root) && self.is_descendant_of_finalized(*block_root)
self.proto_array.contains_block(block_root)
&& self.is_finalized_checkpoint_or_descendant(*block_root)
}

/// Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root.
pub fn get_block(&self, block_root: &Hash256) -> Option<ProtoBlock> {
if self.is_descendant_of_finalized(*block_root) {
if self.is_finalized_checkpoint_or_descendant(*block_root) {
self.proto_array.get_block(block_root)
} else {
None
Expand All @@ -1337,7 +1338,7 @@ where

/// Returns an `ExecutionStatus` if the block is known **and** a descendant of the finalized root.
pub fn get_block_execution_status(&self, block_root: &Hash256) -> Option<ExecutionStatus> {
if self.is_descendant_of_finalized(*block_root) {
if self.is_finalized_checkpoint_or_descendant(*block_root) {
self.proto_array.get_block_execution_status(block_root)
} else {
None
Expand Down Expand Up @@ -1372,10 +1373,10 @@ where
})
}

/// Return `true` if `block_root` is equal to the finalized root, or a known descendant of it.
pub fn is_descendant_of_finalized(&self, block_root: Hash256) -> bool {
/// Return `true` if `block_root` is equal to the finalized checkpoint, or a known descendant of it.
pub fn is_finalized_checkpoint_or_descendant(&self, block_root: Hash256) -> bool {
self.proto_array
.is_descendant(self.fc_store.finalized_checkpoint().root, block_root)
.is_finalized_checkpoint_or_descendant::<E>(block_root)
}

/// Returns `Ok(true)` if `block_root` has been imported optimistically or deemed invalid.
Expand Down
2 changes: 1 addition & 1 deletion consensus/proto_array/src/fork_choice_test_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ impl ForkChoiceTestDefinition {
}
};
fork_choice
.process_execution_payload_invalidation(&op)
.process_execution_payload_invalidation::<MainnetEthSpec>(&op)
.unwrap()
}
Operation::AssertWeight { block_root, weight } => assert_eq!(
Expand Down
74 changes: 72 additions & 2 deletions consensus/proto_array/src/proto_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ impl ProtoArray {
/// Invalidate zero or more blocks, as specified by the `InvalidationOperation`.
///
/// See the documentation of `InvalidationOperation` for usage.
pub fn propagate_execution_payload_invalidation(
pub fn propagate_execution_payload_invalidation<E: EthSpec>(
&mut self,
op: &InvalidationOperation,
) -> Result<(), Error> {
Expand Down Expand Up @@ -482,7 +482,7 @@ impl ProtoArray {
let latest_valid_ancestor_is_descendant =
latest_valid_ancestor_root.map_or(false, |ancestor_root| {
self.is_descendant(ancestor_root, head_block_root)
&& self.is_descendant(self.finalized_checkpoint.root, ancestor_root)
&& self.is_finalized_checkpoint_or_descendant::<E>(ancestor_root)
});

// Collect all *ancestors* which were declared invalid since they reside between the
Expand Down Expand Up @@ -977,6 +977,12 @@ impl ProtoArray {
/// ## Notes
///
/// Still returns `true` if `ancestor_root` is known and `ancestor_root == descendant_root`.
///
/// ## Warning
///
/// Do not use this function to check if a block is a descendant of the
/// finalized checkpoint. Use `Self::is_finalized_checkpoint_or_descendant`
/// instead.
pub fn is_descendant(&self, ancestor_root: Hash256, descendant_root: Hash256) -> bool {
self.indices
.get(&ancestor_root)
Expand All @@ -990,6 +996,70 @@ impl ProtoArray {
.unwrap_or(false)
}

/// Returns `true` if `root` is equal to or a descendant of
/// `self.finalized_checkpoint`.
///
/// Notably, this function is checking ancestory of the finalized
/// *checkpoint* not the finalized *block*.
pub fn is_finalized_checkpoint_or_descendant<E: EthSpec>(&self, root: Hash256) -> bool {
let finalized_root = self.finalized_checkpoint.root;
let finalized_slot = self
.finalized_checkpoint
.epoch
.start_slot(E::slots_per_epoch());

let mut node = if let Some(node) = self
.indices
.get(&root)
.and_then(|index| self.nodes.get(*index))
{
node
} else {
// An unknown root is not a finalized descendant. This line can only
// be reached if the user supplies a root that is not known to fork
// choice.
return false;
};

// The finalized and justified checkpoints represent a list of known
// ancestors of `node` that are likely to coincide with the store's
// finalized checkpoint.
//
// Run this check once, outside of the loop rather than inside the loop.
// If the conditions don't match for this node then they're unlikely to
// start matching for its ancestors.
for checkpoint in &[
node.finalized_checkpoint,
node.justified_checkpoint,
node.unrealized_finalized_checkpoint,
node.unrealized_justified_checkpoint,
] {
if checkpoint.map_or(false, |cp| cp == self.finalized_checkpoint) {
return true;
}
}

loop {
// If `node` is less than or equal to the finalized slot then `node`
// must be the finalized block.
if node.slot <= finalized_slot {
return node.root == finalized_root;
}

// Since `node` is from a higher slot that the finalized checkpoint,
// replace `node` with the parent of `node`.
if let Some(parent) = node.parent.and_then(|index| self.nodes.get(index)) {
node = parent
} else {
// If `node` is not the finalized block and its parent does not
// exist in fork choice, then the parent must have been pruned.
// Proto-array only prunes blocks prior to the finalized block,
// so this means the parent conflicts with finality.
return false;
};
}
michaelsproul marked this conversation as resolved.
Show resolved Hide resolved
}

/// Returns the first *beacon block root* which contains an execution payload with the given
/// `block_hash`, if any.
pub fn execution_block_hash_to_beacon_block_root(
Expand Down
Loading