Merge remote-tracking branch 'origin/unstable' into capella-merge

This commit is contained in:
Michael Sproul
2023-02-14 12:07:27 +11:00
51 changed files with 1961 additions and 362 deletions

View File

@@ -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!(

View File

@@ -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> {
@@ -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
@@ -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)
@@ -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;
};
}
}
/// 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(

View File

@@ -358,12 +358,12 @@ impl ProtoArrayForkChoice {
}
/// See `ProtoArray::propagate_execution_payload_invalidation` for documentation.
pub fn process_execution_payload_invalidation(
pub fn process_execution_payload_invalidation<E: EthSpec>(
&mut self,
op: &InvalidationOperation,
) -> Result<(), String> {
self.proto_array
.propagate_execution_payload_invalidation(op)
.propagate_execution_payload_invalidation::<E>(op)
.map_err(|e| format!("Failed to process invalid payload: {:?}", e))
}
@@ -748,6 +748,15 @@ impl ProtoArrayForkChoice {
.is_descendant(ancestor_root, descendant_root)
}
/// See `ProtoArray` documentation.
pub fn is_finalized_checkpoint_or_descendant<E: EthSpec>(
&self,
descendant_root: Hash256,
) -> bool {
self.proto_array
.is_finalized_checkpoint_or_descendant::<E>(descendant_root)
}
pub fn latest_message(&self, validator_index: usize) -> Option<(Hash256, Epoch)> {
if validator_index < self.votes.0.len() {
let vote = &self.votes.0[validator_index];
@@ -928,6 +937,10 @@ mod test_compute_deltas {
epoch: genesis_epoch,
root: finalized_root,
};
let junk_checkpoint = Checkpoint {
epoch: Epoch::new(42),
root: Hash256::repeat_byte(42),
};
let mut fc = ProtoArrayForkChoice::new::<MainnetEthSpec>(
genesis_slot,
@@ -973,8 +986,10 @@ mod test_compute_deltas {
target_root: finalized_root,
current_epoch_shuffling_id: junk_shuffling_id.clone(),
next_epoch_shuffling_id: junk_shuffling_id,
justified_checkpoint: genesis_checkpoint,
finalized_checkpoint: genesis_checkpoint,
// Use the junk checkpoint for the next to values to prevent
// the loop-shortcutting mechanism from triggering.
justified_checkpoint: junk_checkpoint,
finalized_checkpoint: junk_checkpoint,
execution_status,
unrealized_justified_checkpoint: None,
unrealized_finalized_checkpoint: None,
@@ -993,6 +1008,11 @@ mod test_compute_deltas {
assert!(!fc.is_descendant(finalized_root, not_finalized_desc));
assert!(!fc.is_descendant(finalized_root, unknown));
assert!(fc.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(finalized_root));
assert!(fc.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(finalized_desc));
assert!(!fc.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(not_finalized_desc));
assert!(!fc.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(unknown));
assert!(!fc.is_descendant(finalized_desc, not_finalized_desc));
assert!(fc.is_descendant(finalized_desc, finalized_desc));
assert!(!fc.is_descendant(finalized_desc, finalized_root));
@@ -1004,6 +1024,171 @@ mod test_compute_deltas {
assert!(!fc.is_descendant(not_finalized_desc, unknown));
}
/// This test covers an interesting case where a block can be a descendant
/// of the finalized *block*, but not a descenant of the finalized
/// *checkpoint*.
///
/// ## Example
///
/// Consider this block tree which has three blocks (`A`, `B` and `C`):
///
/// ```ignore
/// [A] <--- [-] <--- [B]
/// |
/// |--[C]
/// ```
///
/// - `A` (slot 31) is the common descendant.
/// - `B` (slot 33) descends from `A`, but there is a single skip slot
/// between it and `A`.
/// - `C` (slot 32) descends from `A` and conflicts with `B`.
///
/// Imagine that the `B` chain is finalized at epoch 1. This means that the
/// finalized checkpoint points to the skipped slot at 32. The root of the
/// finalized checkpoint is `A`.
///
/// In this scenario, the block `C` has the finalized root (`A`) as an
/// ancestor whilst simultaneously conflicting with the finalized
/// checkpoint.
///
/// This means that to ensure a block does not conflict with finality we
/// must check to ensure that it's an ancestor of the finalized
/// *checkpoint*, not just the finalized *block*.
#[test]
fn finalized_descendant_edge_case() {
let get_block_root = Hash256::from_low_u64_be;
let genesis_slot = Slot::new(0);
let junk_state_root = Hash256::zero();
let junk_shuffling_id =
AttestationShufflingId::from_components(Epoch::new(0), Hash256::zero());
let execution_status = ExecutionStatus::irrelevant();
let genesis_checkpoint = Checkpoint {
epoch: Epoch::new(0),
root: get_block_root(0),
};
let mut fc = ProtoArrayForkChoice::new::<MainnetEthSpec>(
genesis_slot,
junk_state_root,
genesis_checkpoint,
genesis_checkpoint,
junk_shuffling_id.clone(),
junk_shuffling_id.clone(),
execution_status,
CountUnrealizedFull::default(),
)
.unwrap();
struct TestBlock {
slot: u64,
root: u64,
parent_root: u64,
}
let insert_block = |fc: &mut ProtoArrayForkChoice, block: TestBlock| {
fc.proto_array
.on_block::<MainnetEthSpec>(
Block {
slot: Slot::from(block.slot),
root: get_block_root(block.root),
parent_root: Some(get_block_root(block.parent_root)),
state_root: Hash256::zero(),
target_root: Hash256::zero(),
current_epoch_shuffling_id: junk_shuffling_id.clone(),
next_epoch_shuffling_id: junk_shuffling_id.clone(),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(0),
root: get_block_root(0),
},
finalized_checkpoint: genesis_checkpoint,
execution_status,
unrealized_justified_checkpoint: Some(genesis_checkpoint),
unrealized_finalized_checkpoint: Some(genesis_checkpoint),
},
Slot::from(block.slot),
)
.unwrap();
};
/*
* Start of interesting part of tests.
*/
// Produce the 0th epoch of blocks. They should all form a chain from
// the genesis block.
for i in 1..MainnetEthSpec::slots_per_epoch() {
insert_block(
&mut fc,
TestBlock {
slot: i,
root: i,
parent_root: i - 1,
},
)
}
let last_slot_of_epoch_0 = MainnetEthSpec::slots_per_epoch() - 1;
// Produce a block that descends from the last block of epoch -.
//
// This block will be non-canonical.
let non_canonical_slot = last_slot_of_epoch_0 + 1;
insert_block(
&mut fc,
TestBlock {
slot: non_canonical_slot,
root: non_canonical_slot,
parent_root: non_canonical_slot - 1,
},
);
// Produce a block that descends from the last block of the 0th epoch,
// that skips the 1st slot of the 1st epoch.
//
// This block will be canonical.
let canonical_slot = last_slot_of_epoch_0 + 2;
insert_block(
&mut fc,
TestBlock {
slot: canonical_slot,
root: canonical_slot,
parent_root: non_canonical_slot - 1,
},
);
let finalized_root = get_block_root(last_slot_of_epoch_0);
// Set the finalized checkpoint to finalize the first slot of epoch 1 on
// the canonical chain.
fc.proto_array.finalized_checkpoint = Checkpoint {
root: finalized_root,
epoch: Epoch::new(1),
};
assert!(
fc.proto_array
.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(finalized_root),
"the finalized checkpoint is the finalized checkpoint"
);
assert!(
fc.proto_array
.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(get_block_root(
canonical_slot
)),
"the canonical block is a descendant of the finalized checkpoint"
);
assert!(
!fc.proto_array
.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(get_block_root(
non_canonical_slot
)),
"although the non-canonical block is a descendant of the finalized block, \
it's not a descendant of the finalized checkpoint"
);
}
#[test]
fn zero_hash() {
let validator_count: usize = 16;