Retrospective invalidation of exec. payloads for opt. sync (#2837)

## Issue Addressed

NA

## Proposed Changes

Adds the functionality to allow blocks to be validated/invalidated after their import as per the [optimistic sync spec](https://github.com/ethereum/consensus-specs/blob/dev/sync/optimistic.md#how-to-optimistically-import-blocks). This means:

- Updating `ProtoArray` to allow flipping the `execution_status` of ancestors/descendants based on payload validity updates.
- Creating separation between `execution_layer` and the `beacon_chain` by creating a `PayloadStatus` struct.
- Refactoring how the `execution_layer` selects a `PayloadStatus` from the multiple statuses returned from multiple EEs.
- Adding testing framework for optimistic imports.
- Add `ExecutionBlockHash(Hash256)` new-type struct to avoid confusion between *beacon block roots* and *execution payload hashes*.
- Add `merge` to [`FORKS`](c3a793fd73/Makefile (L17)) in the `Makefile` to ensure we test the beacon chain with merge settings.
    - Fix some tests here that were failing due to a missing execution layer.

## TODO

- [ ] Balance tests

Co-authored-by: Mark Mackey <mark@sigmaprime.io>
This commit is contained in:
Paul Hauner
2022-02-28 22:07:48 +00:00
parent 5e1f8a8480
commit 27e83b888c
50 changed files with 3358 additions and 768 deletions

View File

@@ -5,7 +5,10 @@ use serde_derive::{Deserialize, Serialize};
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use std::collections::HashMap;
use types::{AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, Hash256, Slot};
use types::{
AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256,
Slot,
};
pub const DEFAULT_PRUNE_THRESHOLD: usize = 256;
@@ -21,11 +24,11 @@ pub struct VoteTracker {
#[ssz(enum_behaviour = "union")]
pub enum ExecutionStatus {
/// An EL has determined that the payload is valid.
Valid(Hash256),
Valid(ExecutionBlockHash),
/// An EL has determined that the payload is invalid.
Invalid(Hash256),
Invalid(ExecutionBlockHash),
/// An EL has not yet verified the execution payload.
Unknown(Hash256),
Unknown(ExecutionBlockHash),
/// The block is either prior to the merge fork, or after the merge fork but before the terminal
/// PoW block has been found.
///
@@ -41,7 +44,7 @@ impl ExecutionStatus {
ExecutionStatus::Irrelevant(false)
}
pub fn block_hash(&self) -> Option<Hash256> {
pub fn block_hash(&self) -> Option<ExecutionBlockHash> {
match self {
ExecutionStatus::Valid(hash)
| ExecutionStatus::Invalid(hash)
@@ -49,6 +52,37 @@ impl ExecutionStatus {
ExecutionStatus::Irrelevant(_) => None,
}
}
/// Returns `true` if the block:
///
/// - Has execution enabled
/// - Has a valid payload
pub fn is_valid(&self) -> bool {
matches!(self, ExecutionStatus::Valid(_))
}
/// Returns `true` if the block:
///
/// - Has execution enabled
/// - Has a payload that has not yet been verified by an EL.
pub fn is_not_verified(&self) -> bool {
matches!(self, ExecutionStatus::Unknown(_))
}
/// Returns `true` if the block:
///
/// - Has execution enabled
/// - Has an invalid payload.
pub fn is_invalid(&self) -> bool {
matches!(self, ExecutionStatus::Invalid(_))
}
/// Returns `true` if the block:
///
/// - Does not have execution enabled (before or after Bellatrix fork)
pub fn is_irrelevant(&self) -> bool {
matches!(self, ExecutionStatus::Irrelevant(_))
}
}
/// A block that is to be applied to the fork choice.
@@ -150,6 +184,17 @@ impl ProtoArrayForkChoice {
})
}
/// See `ProtoArray::propagate_execution_payload_invalidation` for documentation.
pub fn process_execution_payload_invalidation(
&mut self,
head_block_root: Hash256,
latest_valid_ancestor_root: Option<ExecutionBlockHash>,
) -> Result<(), String> {
self.proto_array
.propagate_execution_payload_invalidation(head_block_root, latest_valid_ancestor_root)
.map_err(|e| format!("Failed to process invalid payload: {:?}", e))
}
pub fn process_attestation(
&mut self,
validator_index: usize,
@@ -267,25 +312,19 @@ impl ProtoArrayForkChoice {
}
}
/// Returns `true` if the `descendant_root` has an ancestor with `ancestor_root`. Always
/// returns `false` if either input roots are unknown.
///
/// ## Notes
///
/// Still returns `true` if `ancestor_root` is known and `ancestor_root == descendant_root`.
/// Returns the weight of a given block.
pub fn get_weight(&self, block_root: &Hash256) -> Option<u64> {
let block_index = self.proto_array.indices.get(block_root)?;
self.proto_array
.nodes
.get(*block_index)
.map(|node| node.weight)
}
/// See `ProtoArray` documentation.
pub fn is_descendant(&self, ancestor_root: Hash256, descendant_root: Hash256) -> bool {
self.proto_array
.indices
.get(&ancestor_root)
.and_then(|ancestor_index| self.proto_array.nodes.get(*ancestor_index))
.and_then(|ancestor| {
self.proto_array
.iter_block_roots(&descendant_root)
.take_while(|(_root, slot)| *slot >= ancestor.slot)
.find(|(_root, slot)| *slot == ancestor.slot)
.map(|(root, _slot)| root == ancestor_root)
})
.unwrap_or(false)
.is_descendant(ancestor_root, descendant_root)
}
pub fn latest_message(&self, validator_index: usize) -> Option<(Hash256, Epoch)> {