Disallow attesting to optimistic head (#3140)

## Issue Addressed

NA

## Proposed Changes

Disallow the production of attestations and retrieval of unaggregated attestations when they reference an optimistic head. Add tests to this end.

I also moved `BeaconChain::produce_unaggregated_attestation_for_block` to the `BeaconChainHarness`. It was only being used during tests, so it's nice to stop pretending it's production code. I also needed something that could produce attestations to optimistic blocks in order to simulate scenarios where the justified checkpoint is determined invalid (if no one would attest to an optimistic block, we could never justify it and then flip it to invalid).

## Additional Info

- ~~Blocked on #3126~~
This commit is contained in:
Paul Hauner
2022-04-13 03:54:42 +00:00
parent 7366266bd1
commit b49b4291a3
13 changed files with 354 additions and 125 deletions

View File

@@ -85,7 +85,7 @@ impl ForkChoiceTestDefinition {
self.finalized_checkpoint,
junk_shuffling_id.clone(),
junk_shuffling_id,
ExecutionStatus::Unknown(ExecutionBlockHash::zero()),
ExecutionStatus::Optimistic(ExecutionBlockHash::zero()),
)
.expect("should create fork choice struct");
@@ -189,9 +189,9 @@ impl ForkChoiceTestDefinition {
justified_checkpoint,
finalized_checkpoint,
// All blocks are imported optimistically.
execution_status: ExecutionStatus::Unknown(ExecutionBlockHash::from_root(
root,
)),
execution_status: ExecutionStatus::Optimistic(
ExecutionBlockHash::from_root(root),
),
};
fork_choice.process_block(block).unwrap_or_else(|e| {
panic!(

View File

@@ -387,7 +387,7 @@ impl ProtoArray {
ExecutionStatus::Irrelevant(_) => return Ok(()),
// The block has an unknown status, set it to valid since any ancestor of a valid
// payload can be considered valid.
ExecutionStatus::Unknown(payload_block_hash) => {
ExecutionStatus::Optimistic(payload_block_hash) => {
node.execution_status = ExecutionStatus::Valid(payload_block_hash);
if let Some(parent_index) = node.parent {
parent_index
@@ -458,7 +458,7 @@ impl ProtoArray {
match node.execution_status {
ExecutionStatus::Valid(hash)
| ExecutionStatus::Invalid(hash)
| ExecutionStatus::Unknown(hash) => {
| ExecutionStatus::Optimistic(hash) => {
// If we're no longer processing the `head_block_root` and the last valid
// ancestor is unknown, exit this loop and proceed to invalidate and
// descendants of `head_block_root`/`latest_valid_ancestor_root`.
@@ -516,7 +516,7 @@ impl ProtoArray {
payload_block_hash: *hash,
})
}
ExecutionStatus::Unknown(hash) => {
ExecutionStatus::Optimistic(hash) => {
invalidated_indices.insert(index);
node.execution_status = ExecutionStatus::Invalid(*hash);
@@ -580,7 +580,7 @@ impl ProtoArray {
payload_block_hash: *hash,
})
}
ExecutionStatus::Unknown(hash) | ExecutionStatus::Invalid(hash) => {
ExecutionStatus::Optimistic(hash) | ExecutionStatus::Invalid(hash) => {
node.execution_status = ExecutionStatus::Invalid(*hash)
}
ExecutionStatus::Irrelevant(_) => {

View File

@@ -1,5 +1,5 @@
use crate::error::Error;
use crate::proto_array::{InvalidationOperation, Iter, ProposerBoost, ProtoArray};
use crate::proto_array::{InvalidationOperation, Iter, ProposerBoost, ProtoArray, ProtoNode};
use crate::ssz_container::SszContainer;
use serde_derive::{Deserialize, Serialize};
use ssz::{Decode, Encode};
@@ -28,7 +28,7 @@ pub enum ExecutionStatus {
/// An EL has determined that the payload is invalid.
Invalid(ExecutionBlockHash),
/// An EL has not yet verified the execution payload.
Unknown(ExecutionBlockHash),
Optimistic(ExecutionBlockHash),
/// The block is either prior to the merge fork, or after the merge fork but before the terminal
/// PoW block has been found.
///
@@ -52,30 +52,48 @@ impl ExecutionStatus {
match self {
ExecutionStatus::Valid(hash)
| ExecutionStatus::Invalid(hash)
| ExecutionStatus::Unknown(hash) => Some(*hash),
| ExecutionStatus::Optimistic(hash) => Some(*hash),
ExecutionStatus::Irrelevant(_) => None,
}
}
/// Returns `true` if the block:
///
/// - Has execution enabled
/// - Has a valid payload, OR
/// - Does not have execution enabled.
///
/// Whenever this function returns `true`, the block is *fully valid*.
pub fn is_valid_or_irrelevant(&self) -> bool {
matches!(
self,
ExecutionStatus::Valid(_) | ExecutionStatus::Irrelevant(_)
)
}
/// Returns `true` if the block:
///
/// - Has execution enabled, AND
/// - Has a valid payload
pub fn is_valid(&self) -> bool {
///
/// This function will return `false` for any block from a slot prior to the Bellatrix fork.
/// This means that some blocks that are perfectly valid will still receive a `false` response.
/// See `Self::is_valid_or_irrelevant` for a function that will always return `true` given any
/// perfectly valid block.
pub fn is_valid_and_post_bellatrix(&self) -> bool {
matches!(self, ExecutionStatus::Valid(_))
}
/// Returns `true` if the block:
///
/// - Has execution enabled
/// - Has execution enabled, AND
/// - Has a payload that has not yet been verified by an EL.
pub fn is_not_verified(&self) -> bool {
matches!(self, ExecutionStatus::Unknown(_))
pub fn is_optimistic(&self) -> bool {
matches!(self, ExecutionStatus::Optimistic(_))
}
/// Returns `true` if the block:
///
/// - Has execution enabled
/// - Has execution enabled, AND
/// - Has an invalid payload.
pub fn is_invalid(&self) -> bool {
matches!(self, ExecutionStatus::Invalid(_))
@@ -294,9 +312,13 @@ impl ProtoArrayForkChoice {
self.proto_array.indices.contains_key(block_root)
}
pub fn get_block(&self, block_root: &Hash256) -> Option<Block> {
fn get_proto_node(&self, block_root: &Hash256) -> Option<&ProtoNode> {
let block_index = self.proto_array.indices.get(block_root)?;
let block = self.proto_array.nodes.get(*block_index)?;
self.proto_array.nodes.get(*block_index)
}
pub fn get_block(&self, block_root: &Hash256) -> Option<Block> {
let block = self.get_proto_node(block_root)?;
let parent_root = block
.parent
.and_then(|i| self.proto_array.nodes.get(i))
@@ -325,6 +347,12 @@ impl ProtoArrayForkChoice {
}
}
/// Returns the `block.execution_status` field, if the block is present.
pub fn get_block_execution_status(&self, block_root: &Hash256) -> Option<ExecutionStatus> {
let block = self.get_proto_node(block_root)?;
Some(block.execution_status)
}
/// 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)?;