mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-14 10:22:38 +00:00
[Merge] Optimistic Sync: Stage 1 (#2686)
* Add payload verification status to fork choice * Pass payload verification status to import_block * Add valid back-propagation * Add head safety status latch to API * Remove ExecutionLayerStatus * Add execution info to client notifier * Update notifier logs * Change use of "hash" to refer to beacon block * Shutdown on invalid finalized block * Tidy, add comments * Fix failing FC tests * Allow blocks with unsafe head * Fix forkchoiceUpdate call on startup
This commit is contained in:
@@ -30,4 +30,8 @@ pub enum Error {
|
||||
head_justified_epoch: Epoch,
|
||||
head_finalized_epoch: Epoch,
|
||||
},
|
||||
InvalidAncestorOfValidPayload {
|
||||
ancestor_block_root: Hash256,
|
||||
ancestor_payload_block_hash: Hash256,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ mod ffg_updates;
|
||||
mod no_votes;
|
||||
mod votes;
|
||||
|
||||
use crate::proto_array_fork_choice::{Block, ProtoArrayForkChoice};
|
||||
use crate::proto_array_fork_choice::{Block, ExecutionStatus, ProtoArrayForkChoice};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use types::{AttestationShufflingId, Epoch, Hash256, Slot};
|
||||
|
||||
@@ -57,7 +57,7 @@ impl ForkChoiceTestDefinition {
|
||||
pub fn run(self) {
|
||||
let junk_shuffling_id =
|
||||
AttestationShufflingId::from_components(Epoch::new(0), Hash256::zero());
|
||||
let execution_block_hash = Hash256::zero();
|
||||
let execution_status = ExecutionStatus::irrelevant();
|
||||
let mut fork_choice = ProtoArrayForkChoice::new(
|
||||
self.finalized_block_slot,
|
||||
Hash256::zero(),
|
||||
@@ -66,7 +66,7 @@ impl ForkChoiceTestDefinition {
|
||||
self.finalized_root,
|
||||
junk_shuffling_id.clone(),
|
||||
junk_shuffling_id,
|
||||
execution_block_hash,
|
||||
execution_status,
|
||||
)
|
||||
.expect("should create fork choice struct");
|
||||
|
||||
@@ -141,7 +141,7 @@ impl ForkChoiceTestDefinition {
|
||||
),
|
||||
justified_epoch,
|
||||
finalized_epoch,
|
||||
execution_block_hash,
|
||||
execution_status,
|
||||
};
|
||||
fork_choice.process_block(block).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
|
||||
@@ -4,7 +4,7 @@ mod proto_array;
|
||||
mod proto_array_fork_choice;
|
||||
mod ssz_container;
|
||||
|
||||
pub use crate::proto_array_fork_choice::{Block, ProtoArrayForkChoice};
|
||||
pub use crate::proto_array_fork_choice::{Block, ExecutionStatus, ProtoArrayForkChoice};
|
||||
pub use error::Error;
|
||||
|
||||
pub mod core {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{error::Error, Block};
|
||||
use crate::{error::Error, Block, ExecutionStatus};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::four_byte_option_impl;
|
||||
use ssz_derive::{Decode, Encode};
|
||||
@@ -35,11 +35,9 @@ pub struct ProtoNode {
|
||||
best_child: Option<usize>,
|
||||
#[ssz(with = "four_byte_option_usize")]
|
||||
best_descendant: Option<usize>,
|
||||
/// It's necessary to track this so that we can refuse to propagate post-merge blocks without
|
||||
/// execution payloads, without confusing these with pre-merge blocks.
|
||||
///
|
||||
/// Relevant spec issue: https://github.com/ethereum/consensus-specs/issues/2618
|
||||
pub execution_block_hash: Hash256,
|
||||
/// Indicates if an execution node has marked this block as valid. Also contains the execution
|
||||
/// block hash.
|
||||
pub execution_status: ExecutionStatus,
|
||||
}
|
||||
|
||||
/// Only used for SSZ deserialization of the persisted fork choice during the database migration
|
||||
@@ -78,7 +76,11 @@ impl Into<ProtoNode> for LegacyProtoNode {
|
||||
weight: self.weight,
|
||||
best_child: self.best_child,
|
||||
best_descendant: self.best_descendant,
|
||||
execution_block_hash: Hash256::zero(),
|
||||
// We set the following execution value as if the block is a pre-merge-fork block. This
|
||||
// is safe as long as we never import a merge block with the old version of proto-array.
|
||||
// This will be safe since we can't actually process merge blocks until we've made this
|
||||
// change to fork choice.
|
||||
execution_status: ExecutionStatus::irrelevant(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,7 +226,7 @@ impl ProtoArray {
|
||||
weight: 0,
|
||||
best_child: None,
|
||||
best_descendant: None,
|
||||
execution_block_hash: block.execution_block_hash,
|
||||
execution_status: block.execution_status,
|
||||
};
|
||||
|
||||
self.indices.insert(node.root, node_index);
|
||||
@@ -232,11 +234,58 @@ impl ProtoArray {
|
||||
|
||||
if let Some(parent_index) = node.parent {
|
||||
self.maybe_update_best_child_and_descendant(parent_index, node_index)?;
|
||||
|
||||
if matches!(block.execution_status, ExecutionStatus::Valid(_)) {
|
||||
self.propagate_execution_payload_verification(parent_index)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn propagate_execution_payload_verification(
|
||||
&mut self,
|
||||
verified_node_index: usize,
|
||||
) -> Result<(), Error> {
|
||||
let mut index = verified_node_index;
|
||||
loop {
|
||||
let node = self
|
||||
.nodes
|
||||
.get_mut(index)
|
||||
.ok_or(Error::InvalidNodeIndex(index))?;
|
||||
let parent_index = match node.execution_status {
|
||||
// We have reached a node that we already know is valid. No need to iterate further
|
||||
// since we assume an ancestors have already been set to valid.
|
||||
ExecutionStatus::Valid(_) => return Ok(()),
|
||||
// We have reached an irrelevant node, this node is prior to a terminal execution
|
||||
// block. There's no need to iterate further, it's impossible for this block to have
|
||||
// any relevant ancestors.
|
||||
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) => {
|
||||
node.execution_status = ExecutionStatus::Valid(payload_block_hash);
|
||||
if let Some(parent_index) = node.parent {
|
||||
parent_index
|
||||
} else {
|
||||
// We have reached the root block, iteration complete.
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// An ancestor of the valid payload was invalid. This is a serious error which
|
||||
// indicates a consensus failure in the execution node. This is unrecoverable.
|
||||
ExecutionStatus::Invalid(ancestor_payload_block_hash) => {
|
||||
return Err(Error::InvalidAncestorOfValidPayload {
|
||||
ancestor_block_root: node.root,
|
||||
ancestor_payload_block_hash,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
index = parent_index;
|
||||
}
|
||||
}
|
||||
|
||||
/// Follows the best-descendant links to find the best-block (i.e., head-block).
|
||||
///
|
||||
/// ## Notes
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::error::Error;
|
||||
use crate::proto_array::ProtoArray;
|
||||
use crate::ssz_container::{LegacySszContainer, SszContainer};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::{Decode, Encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::collections::HashMap;
|
||||
@@ -15,6 +16,32 @@ pub struct VoteTracker {
|
||||
next_epoch: Epoch,
|
||||
}
|
||||
|
||||
/// Represents the verification status of an execution payload.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Encode, Decode, Serialize, Deserialize)]
|
||||
#[ssz(enum_behaviour = "union")]
|
||||
pub enum ExecutionStatus {
|
||||
/// An EL has determined that the payload is valid.
|
||||
Valid(Hash256),
|
||||
/// An EL has determined that the payload is invalid.
|
||||
Invalid(Hash256),
|
||||
/// An EL has not yet verified the execution payload.
|
||||
Unknown(Hash256),
|
||||
/// The block is either prior to the merge fork, or after the merge fork but before the terminal
|
||||
/// PoW block has been found.
|
||||
///
|
||||
/// # Note:
|
||||
///
|
||||
/// This `bool` only exists to satisfy our SSZ implementation which requires all variants
|
||||
/// to have a value. It can be set to anything.
|
||||
Irrelevant(bool), // TODO(merge): fix bool.
|
||||
}
|
||||
|
||||
impl ExecutionStatus {
|
||||
pub fn irrelevant() -> Self {
|
||||
ExecutionStatus::Irrelevant(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// A block that is to be applied to the fork choice.
|
||||
///
|
||||
/// A simplified version of `types::BeaconBlock`.
|
||||
@@ -29,7 +56,9 @@ pub struct Block {
|
||||
pub next_epoch_shuffling_id: AttestationShufflingId,
|
||||
pub justified_epoch: Epoch,
|
||||
pub finalized_epoch: Epoch,
|
||||
pub execution_block_hash: Hash256,
|
||||
/// Indicates if an execution node has marked this block as valid. Also contains the execution
|
||||
/// block hash.
|
||||
pub execution_status: ExecutionStatus,
|
||||
}
|
||||
|
||||
/// A Vec-wrapper which will grow to match any request.
|
||||
@@ -76,7 +105,7 @@ impl ProtoArrayForkChoice {
|
||||
finalized_root: Hash256,
|
||||
current_epoch_shuffling_id: AttestationShufflingId,
|
||||
next_epoch_shuffling_id: AttestationShufflingId,
|
||||
execution_block_hash: Hash256,
|
||||
execution_status: ExecutionStatus,
|
||||
) -> Result<Self, String> {
|
||||
let mut proto_array = ProtoArray {
|
||||
prune_threshold: DEFAULT_PRUNE_THRESHOLD,
|
||||
@@ -98,7 +127,7 @@ impl ProtoArrayForkChoice {
|
||||
next_epoch_shuffling_id,
|
||||
justified_epoch,
|
||||
finalized_epoch,
|
||||
execution_block_hash,
|
||||
execution_status,
|
||||
};
|
||||
|
||||
proto_array
|
||||
@@ -208,7 +237,7 @@ impl ProtoArrayForkChoice {
|
||||
next_epoch_shuffling_id: block.next_epoch_shuffling_id.clone(),
|
||||
justified_epoch: block.justified_epoch,
|
||||
finalized_epoch: block.finalized_epoch,
|
||||
execution_block_hash: block.execution_block_hash,
|
||||
execution_status: block.execution_status,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -372,7 +401,7 @@ mod test_compute_deltas {
|
||||
let unknown = Hash256::from_low_u64_be(4);
|
||||
let junk_shuffling_id =
|
||||
AttestationShufflingId::from_components(Epoch::new(0), Hash256::zero());
|
||||
let execution_block_hash = Hash256::zero();
|
||||
let execution_status = ExecutionStatus::irrelevant();
|
||||
|
||||
let mut fc = ProtoArrayForkChoice::new(
|
||||
genesis_slot,
|
||||
@@ -382,7 +411,7 @@ mod test_compute_deltas {
|
||||
finalized_root,
|
||||
junk_shuffling_id.clone(),
|
||||
junk_shuffling_id.clone(),
|
||||
execution_block_hash,
|
||||
execution_status,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -398,7 +427,7 @@ mod test_compute_deltas {
|
||||
next_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
justified_epoch: genesis_epoch,
|
||||
finalized_epoch: genesis_epoch,
|
||||
execution_block_hash,
|
||||
execution_status,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -414,7 +443,7 @@ mod test_compute_deltas {
|
||||
next_epoch_shuffling_id: junk_shuffling_id,
|
||||
justified_epoch: genesis_epoch,
|
||||
finalized_epoch: genesis_epoch,
|
||||
execution_block_hash,
|
||||
execution_status,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user