mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 20:57:10 +00:00
Resolve merge conflicts
This commit is contained in:
@@ -11,6 +11,7 @@ path = "src/bin.rs"
|
||||
[dependencies]
|
||||
ethereum_ssz = { workspace = true }
|
||||
ethereum_ssz_derive = { workspace = true }
|
||||
fixed_bytes = { workspace = true }
|
||||
safe_arith = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
|
||||
@@ -5,11 +5,12 @@ mod votes;
|
||||
|
||||
use crate::proto_array_fork_choice::{Block, ExecutionStatus, ProtoArrayForkChoice};
|
||||
use crate::{InvalidationOperation, JustifiedBalances};
|
||||
use fixed_bytes::FixedBytesExtended;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use types::{
|
||||
AttestationShufflingId, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, FixedBytesExtended,
|
||||
Hash256, MainnetEthSpec, Slot,
|
||||
AttestationShufflingId, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256,
|
||||
MainnetEthSpec, Slot,
|
||||
};
|
||||
|
||||
pub use execution_status::*;
|
||||
@@ -213,7 +214,12 @@ impl ForkChoiceTestDefinition {
|
||||
unrealized_finalized_checkpoint: None,
|
||||
};
|
||||
fork_choice
|
||||
.process_block::<MainnetEthSpec>(block, slot)
|
||||
.process_block::<MainnetEthSpec>(
|
||||
block,
|
||||
slot,
|
||||
self.justified_checkpoint,
|
||||
self.finalized_checkpoint,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"process_block op at index {} returned error: {:?}",
|
||||
@@ -273,7 +279,10 @@ impl ForkChoiceTestDefinition {
|
||||
}
|
||||
};
|
||||
fork_choice
|
||||
.process_execution_payload_invalidation::<MainnetEthSpec>(&op)
|
||||
.process_execution_payload_invalidation::<MainnetEthSpec>(
|
||||
&op,
|
||||
self.finalized_checkpoint,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
Operation::AssertWeight { block_root, weight } => assert_eq!(
|
||||
@@ -306,9 +315,10 @@ fn get_checkpoint(i: u64) -> Checkpoint {
|
||||
}
|
||||
|
||||
fn check_bytes_round_trip(original: &ProtoArrayForkChoice) {
|
||||
let bytes = original.as_bytes();
|
||||
let decoded =
|
||||
ProtoArrayForkChoice::from_bytes(&bytes).expect("fork choice should decode from bytes");
|
||||
// The checkpoint are ignored `ProtoArrayForkChoice::from_bytes` so any value is ok
|
||||
let bytes = original.as_bytes(Checkpoint::default(), Checkpoint::default());
|
||||
let decoded = ProtoArrayForkChoice::from_bytes(&bytes, original.balances.clone())
|
||||
.expect("fork choice should decode from bytes");
|
||||
assert!(
|
||||
*original == decoded,
|
||||
"fork choice should encode and decode without change"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use types::FixedBytesExtended;
|
||||
use fixed_bytes::FixedBytesExtended;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ mod proto_array_fork_choice;
|
||||
mod ssz_container;
|
||||
|
||||
pub use crate::justified_balances::JustifiedBalances;
|
||||
pub use crate::proto_array::{calculate_committee_fraction, InvalidationOperation};
|
||||
pub use crate::proto_array::{InvalidationOperation, calculate_committee_fraction};
|
||||
pub use crate::proto_array_fork_choice::{
|
||||
Block, DisallowedReOrgOffsets, DoNotReOrg, ExecutionStatus, ProposerHeadError,
|
||||
ProposerHeadInfo, ProtoArrayForkChoice, ReOrgThreshold,
|
||||
@@ -16,5 +16,5 @@ pub use error::Error;
|
||||
pub mod core {
|
||||
pub use super::proto_array::{ProposerBoost, ProtoArray, ProtoNode};
|
||||
pub use super::proto_array_fork_choice::VoteTracker;
|
||||
pub use super::ssz_container::{SszContainer, SszContainerV17};
|
||||
pub use super::ssz_container::{SszContainer, SszContainerV17, SszContainerV28};
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use crate::error::InvalidBestNodeInfo;
|
||||
use crate::{error::Error, Block, ExecutionStatus, JustifiedBalances};
|
||||
use crate::{Block, ExecutionStatus, JustifiedBalances, error::Error};
|
||||
use fixed_bytes::FixedBytesExtended;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ssz::four_byte_option_impl;
|
||||
use ssz::Encode;
|
||||
use ssz::four_byte_option_impl;
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use superstruct::superstruct;
|
||||
use tracing::info;
|
||||
use types::{
|
||||
AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash,
|
||||
FixedBytesExtended, Hash256, Slot,
|
||||
AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256,
|
||||
Slot,
|
||||
};
|
||||
|
||||
// Define a "legacy" implementation of `Option<usize>` which uses four bytes for encoding the union
|
||||
@@ -131,8 +132,6 @@ pub struct ProtoArray {
|
||||
/// Do not attempt to prune the tree unless it has at least this many nodes. Small prunes
|
||||
/// simply waste time.
|
||||
pub prune_threshold: usize,
|
||||
pub justified_checkpoint: Checkpoint,
|
||||
pub finalized_checkpoint: Checkpoint,
|
||||
pub nodes: Vec<ProtoNode>,
|
||||
pub indices: HashMap<Hash256, usize>,
|
||||
pub unsatisfied_inclusion_list_blocks: HashMap<Slot, Hash256>,
|
||||
@@ -157,8 +156,8 @@ impl ProtoArray {
|
||||
pub fn apply_score_changes<E: EthSpec>(
|
||||
&mut self,
|
||||
mut deltas: Vec<i64>,
|
||||
justified_checkpoint: Checkpoint,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
best_justified_checkpoint: Checkpoint,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
new_justified_balances: &JustifiedBalances,
|
||||
proposer_boost_root: Hash256,
|
||||
current_slot: Slot,
|
||||
@@ -171,13 +170,6 @@ impl ProtoArray {
|
||||
});
|
||||
}
|
||||
|
||||
if justified_checkpoint != self.justified_checkpoint
|
||||
|| finalized_checkpoint != self.finalized_checkpoint
|
||||
{
|
||||
self.justified_checkpoint = justified_checkpoint;
|
||||
self.finalized_checkpoint = finalized_checkpoint;
|
||||
}
|
||||
|
||||
// Default the proposer boost score to zero.
|
||||
let mut proposer_score = 0;
|
||||
|
||||
@@ -242,21 +234,18 @@ impl ProtoArray {
|
||||
// the delta by the new score amount (unless the block has an invalid execution status).
|
||||
//
|
||||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/fork-choice.md#get_latest_attesting_balance
|
||||
if let Some(proposer_score_boost) = spec.proposer_score_boost {
|
||||
if proposer_boost_root != Hash256::zero()
|
||||
if let Some(proposer_score_boost) = spec.proposer_score_boost
|
||||
&& proposer_boost_root != Hash256::zero()
|
||||
&& proposer_boost_root == node.root
|
||||
// Invalid nodes (or their ancestors) should not receive a proposer boost.
|
||||
&& !execution_status_is_invalid
|
||||
{
|
||||
proposer_score = calculate_committee_fraction::<E>(
|
||||
new_justified_balances,
|
||||
proposer_score_boost,
|
||||
)
|
||||
.ok_or(Error::ProposerBoostOverflow(node_index))?;
|
||||
node_delta = node_delta
|
||||
.checked_add(proposer_score as i64)
|
||||
.ok_or(Error::DeltaOverflow(node_index))?;
|
||||
}
|
||||
{
|
||||
proposer_score =
|
||||
calculate_committee_fraction::<E>(new_justified_balances, proposer_score_boost)
|
||||
.ok_or(Error::ProposerBoostOverflow(node_index))?;
|
||||
node_delta = node_delta
|
||||
.checked_add(proposer_score as i64)
|
||||
.ok_or(Error::DeltaOverflow(node_index))?;
|
||||
}
|
||||
|
||||
// Apply the delta to the node.
|
||||
@@ -318,6 +307,8 @@ impl ProtoArray {
|
||||
parent_index,
|
||||
node_index,
|
||||
current_slot,
|
||||
best_justified_checkpoint,
|
||||
best_finalized_checkpoint,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@@ -328,7 +319,13 @@ impl ProtoArray {
|
||||
/// Register a block with the fork choice.
|
||||
///
|
||||
/// It is only sane to supply a `None` parent for the genesis block.
|
||||
pub fn on_block<E: EthSpec>(&mut self, block: Block, current_slot: Slot) -> Result<(), Error> {
|
||||
pub fn on_block<E: EthSpec>(
|
||||
&mut self,
|
||||
block: Block,
|
||||
current_slot: Slot,
|
||||
best_justified_checkpoint: Checkpoint,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
) -> Result<(), Error> {
|
||||
// If the block is already known, simply ignore it.
|
||||
if self.indices.contains_key(&block.root) {
|
||||
return Ok(());
|
||||
@@ -379,6 +376,8 @@ impl ProtoArray {
|
||||
parent_index,
|
||||
node_index,
|
||||
current_slot,
|
||||
best_justified_checkpoint,
|
||||
best_finalized_checkpoint,
|
||||
)?;
|
||||
|
||||
if matches!(block.execution_status, ExecutionStatus::Valid(_)) {
|
||||
@@ -447,7 +446,7 @@ impl ProtoArray {
|
||||
return Err(Error::InvalidAncestorOfValidPayload {
|
||||
ancestor_block_root: node.root,
|
||||
ancestor_payload_block_hash,
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -461,6 +460,7 @@ impl ProtoArray {
|
||||
pub fn propagate_execution_payload_invalidation<E: EthSpec>(
|
||||
&mut self,
|
||||
op: &InvalidationOperation,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
) -> Result<(), Error> {
|
||||
let mut invalidated_indices: HashSet<usize> = <_>::default();
|
||||
let head_block_root = op.block_root();
|
||||
@@ -489,7 +489,10 @@ impl ProtoArray {
|
||||
let latest_valid_ancestor_is_descendant =
|
||||
latest_valid_ancestor_root.is_some_and(|ancestor_root| {
|
||||
self.is_descendant(ancestor_root, head_block_root)
|
||||
&& self.is_finalized_checkpoint_or_descendant::<E>(ancestor_root)
|
||||
&& self.is_finalized_checkpoint_or_descendant::<E>(
|
||||
ancestor_root,
|
||||
best_finalized_checkpoint,
|
||||
)
|
||||
});
|
||||
|
||||
// Collect all *ancestors* which were declared invalid since they reside between the
|
||||
@@ -556,7 +559,7 @@ impl ProtoArray {
|
||||
return Err(Error::ValidExecutionStatusBecameInvalid {
|
||||
block_root: node.root,
|
||||
payload_block_hash: *hash,
|
||||
})
|
||||
});
|
||||
}
|
||||
ExecutionStatus::Optimistic(hash) => {
|
||||
invalidated_indices.insert(index);
|
||||
@@ -613,27 +616,27 @@ impl ProtoArray {
|
||||
.get_mut(index)
|
||||
.ok_or(Error::InvalidNodeIndex(index))?;
|
||||
|
||||
if let Some(parent_index) = node.parent {
|
||||
if invalidated_indices.contains(&parent_index) {
|
||||
match &node.execution_status {
|
||||
ExecutionStatus::Valid(hash) => {
|
||||
return Err(Error::ValidExecutionStatusBecameInvalid {
|
||||
block_root: node.root,
|
||||
payload_block_hash: *hash,
|
||||
})
|
||||
}
|
||||
ExecutionStatus::Optimistic(hash) | ExecutionStatus::Invalid(hash) => {
|
||||
node.execution_status = ExecutionStatus::Invalid(*hash)
|
||||
}
|
||||
ExecutionStatus::Irrelevant(_) => {
|
||||
return Err(Error::IrrelevantDescendant {
|
||||
block_root: node.root,
|
||||
})
|
||||
}
|
||||
if let Some(parent_index) = node.parent
|
||||
&& invalidated_indices.contains(&parent_index)
|
||||
{
|
||||
match &node.execution_status {
|
||||
ExecutionStatus::Valid(hash) => {
|
||||
return Err(Error::ValidExecutionStatusBecameInvalid {
|
||||
block_root: node.root,
|
||||
payload_block_hash: *hash,
|
||||
});
|
||||
}
|
||||
ExecutionStatus::Optimistic(hash) | ExecutionStatus::Invalid(hash) => {
|
||||
node.execution_status = ExecutionStatus::Invalid(*hash)
|
||||
}
|
||||
ExecutionStatus::Irrelevant(_) => {
|
||||
return Err(Error::IrrelevantDescendant {
|
||||
block_root: node.root,
|
||||
});
|
||||
}
|
||||
|
||||
invalidated_indices.insert(index);
|
||||
}
|
||||
|
||||
invalidated_indices.insert(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,6 +655,8 @@ impl ProtoArray {
|
||||
&self,
|
||||
justified_root: &Hash256,
|
||||
current_slot: Slot,
|
||||
best_justified_checkpoint: Checkpoint,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
) -> Result<Hash256, Error> {
|
||||
let justified_index = self
|
||||
.indices
|
||||
@@ -685,12 +690,17 @@ impl ProtoArray {
|
||||
.ok_or(Error::InvalidBestDescendant(best_descendant_index))?;
|
||||
|
||||
// Perform a sanity check that the node is indeed valid to be the head.
|
||||
if !self.node_is_viable_for_head::<E>(best_node, current_slot) {
|
||||
if !self.node_is_viable_for_head::<E>(
|
||||
best_node,
|
||||
current_slot,
|
||||
best_justified_checkpoint,
|
||||
best_finalized_checkpoint,
|
||||
) {
|
||||
return Err(Error::InvalidBestNode(Box::new(InvalidBestNodeInfo {
|
||||
current_slot,
|
||||
start_root: *justified_root,
|
||||
justified_checkpoint: self.justified_checkpoint,
|
||||
finalized_checkpoint: self.finalized_checkpoint,
|
||||
justified_checkpoint: best_justified_checkpoint,
|
||||
finalized_checkpoint: best_finalized_checkpoint,
|
||||
head_root: best_node.root,
|
||||
head_justified_checkpoint: best_node.justified_checkpoint,
|
||||
head_finalized_checkpoint: best_node.finalized_checkpoint,
|
||||
@@ -787,6 +797,8 @@ impl ProtoArray {
|
||||
parent_index: usize,
|
||||
child_index: usize,
|
||||
current_slot: Slot,
|
||||
best_justified_checkpoint: Checkpoint,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
) -> Result<(), Error> {
|
||||
let child = self
|
||||
.nodes
|
||||
@@ -798,8 +810,12 @@ impl ProtoArray {
|
||||
.get(parent_index)
|
||||
.ok_or(Error::InvalidNodeIndex(parent_index))?;
|
||||
|
||||
let child_leads_to_viable_head =
|
||||
self.node_leads_to_viable_head::<E>(child, current_slot)?;
|
||||
let child_leads_to_viable_head = self.node_leads_to_viable_head::<E>(
|
||||
child,
|
||||
current_slot,
|
||||
best_justified_checkpoint,
|
||||
best_finalized_checkpoint,
|
||||
)?;
|
||||
|
||||
// These three variables are aliases to the three options that we may set the
|
||||
// `parent.best_child` and `parent.best_descendant` to.
|
||||
@@ -828,8 +844,12 @@ impl ProtoArray {
|
||||
.get(best_child_index)
|
||||
.ok_or(Error::InvalidBestDescendant(best_child_index))?;
|
||||
|
||||
let best_child_leads_to_viable_head =
|
||||
self.node_leads_to_viable_head::<E>(best_child, current_slot)?;
|
||||
let best_child_leads_to_viable_head = self.node_leads_to_viable_head::<E>(
|
||||
best_child,
|
||||
current_slot,
|
||||
best_justified_checkpoint,
|
||||
best_finalized_checkpoint,
|
||||
)?;
|
||||
|
||||
if child_leads_to_viable_head && !best_child_leads_to_viable_head {
|
||||
// The child leads to a viable head, but the current best-child doesn't.
|
||||
@@ -878,6 +898,8 @@ impl ProtoArray {
|
||||
&self,
|
||||
node: &ProtoNode,
|
||||
current_slot: Slot,
|
||||
best_justified_checkpoint: Checkpoint,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
) -> Result<bool, Error> {
|
||||
let best_descendant_is_viable_for_head =
|
||||
if let Some(best_descendant_index) = node.best_descendant {
|
||||
@@ -886,13 +908,23 @@ impl ProtoArray {
|
||||
.get(best_descendant_index)
|
||||
.ok_or(Error::InvalidBestDescendant(best_descendant_index))?;
|
||||
|
||||
self.node_is_viable_for_head::<E>(best_descendant, current_slot)
|
||||
self.node_is_viable_for_head::<E>(
|
||||
best_descendant,
|
||||
current_slot,
|
||||
best_justified_checkpoint,
|
||||
best_finalized_checkpoint,
|
||||
)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Ok(best_descendant_is_viable_for_head
|
||||
|| self.node_is_viable_for_head::<E>(node, current_slot))
|
||||
|| self.node_is_viable_for_head::<E>(
|
||||
node,
|
||||
current_slot,
|
||||
best_justified_checkpoint,
|
||||
best_finalized_checkpoint,
|
||||
))
|
||||
}
|
||||
|
||||
/// This is the equivalent to the `filter_block_tree` function in the eth2 spec:
|
||||
@@ -901,7 +933,13 @@ impl ProtoArray {
|
||||
///
|
||||
/// Any node that has a different finalized or justified epoch should not be viable for the
|
||||
/// head.
|
||||
fn node_is_viable_for_head<E: EthSpec>(&self, node: &ProtoNode, current_slot: Slot) -> bool {
|
||||
fn node_is_viable_for_head<E: EthSpec>(
|
||||
&self,
|
||||
node: &ProtoNode,
|
||||
current_slot: Slot,
|
||||
best_justified_checkpoint: Checkpoint,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
) -> bool {
|
||||
if node.execution_status.is_invalid() {
|
||||
return false;
|
||||
}
|
||||
@@ -938,12 +976,13 @@ impl ProtoArray {
|
||||
node_justified_checkpoint
|
||||
};
|
||||
|
||||
let correct_justified = self.justified_checkpoint.epoch == genesis_epoch
|
||||
|| voting_source.epoch == self.justified_checkpoint.epoch
|
||||
let correct_justified = best_justified_checkpoint.epoch == genesis_epoch
|
||||
|| voting_source.epoch == best_justified_checkpoint.epoch
|
||||
|| voting_source.epoch + 2 >= current_epoch;
|
||||
|
||||
let correct_finalized = self.finalized_checkpoint.epoch == genesis_epoch
|
||||
|| self.is_finalized_checkpoint_or_descendant::<E>(node.root);
|
||||
let correct_finalized = best_finalized_checkpoint.epoch == genesis_epoch
|
||||
|| self
|
||||
.is_finalized_checkpoint_or_descendant::<E>(node.root, best_finalized_checkpoint);
|
||||
|
||||
correct_justified && correct_finalized
|
||||
}
|
||||
@@ -998,10 +1037,13 @@ impl ProtoArray {
|
||||
///
|
||||
/// 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
|
||||
pub fn is_finalized_checkpoint_or_descendant<E: EthSpec>(
|
||||
&self,
|
||||
root: Hash256,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
) -> bool {
|
||||
let finalized_root = best_finalized_checkpoint.root;
|
||||
let finalized_slot = best_finalized_checkpoint
|
||||
.epoch
|
||||
.start_slot(E::slots_per_epoch());
|
||||
|
||||
@@ -1024,7 +1066,7 @@ impl ProtoArray {
|
||||
// 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] {
|
||||
if checkpoint == &self.finalized_checkpoint {
|
||||
if checkpoint == &best_finalized_checkpoint {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1033,7 +1075,7 @@ impl ProtoArray {
|
||||
node.unrealized_finalized_checkpoint,
|
||||
node.unrealized_justified_checkpoint,
|
||||
] {
|
||||
if checkpoint.is_some_and(|cp| cp == self.finalized_checkpoint) {
|
||||
if checkpoint.is_some_and(|cp| cp == best_finalized_checkpoint) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1081,12 +1123,18 @@ impl ProtoArray {
|
||||
/// For informational purposes like the beacon HTTP API, we use this as the list of known heads,
|
||||
/// even though some of them might not be viable. We do this to maintain consistency between the
|
||||
/// definition of "head" used by pruning (which does not consider viability) and fork choice.
|
||||
pub fn heads_descended_from_finalization<E: EthSpec>(&self) -> Vec<&ProtoNode> {
|
||||
pub fn heads_descended_from_finalization<E: EthSpec>(
|
||||
&self,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
) -> Vec<&ProtoNode> {
|
||||
self.nodes
|
||||
.iter()
|
||||
.filter(|node| {
|
||||
node.best_child.is_none()
|
||||
&& self.is_finalized_checkpoint_or_descendant::<E>(node.root)
|
||||
&& self.is_finalized_checkpoint_or_descendant::<E>(
|
||||
node.root,
|
||||
best_finalized_checkpoint,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::{
|
||||
JustifiedBalances,
|
||||
error::Error,
|
||||
proto_array::{
|
||||
calculate_committee_fraction, InvalidationOperation, Iter, ProposerBoost, ProtoArray,
|
||||
ProtoNode,
|
||||
InvalidationOperation, Iter, ProposerBoost, ProtoArray, ProtoNode,
|
||||
calculate_committee_fraction,
|
||||
},
|
||||
ssz_container::SszContainer,
|
||||
JustifiedBalances,
|
||||
};
|
||||
use fixed_bytes::FixedBytesExtended;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ssz::{Decode, Encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
@@ -15,8 +16,8 @@ use std::{
|
||||
fmt,
|
||||
};
|
||||
use types::{
|
||||
AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash,
|
||||
FixedBytesExtended, Hash256, Slot,
|
||||
AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256,
|
||||
Slot,
|
||||
};
|
||||
|
||||
pub const DEFAULT_PRUNE_THRESHOLD: usize = 256;
|
||||
@@ -160,6 +161,56 @@ pub struct Block {
|
||||
pub unrealized_finalized_checkpoint: Option<Checkpoint>,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
/// Compute the proposer shuffling decision root of a child block in `child_block_epoch`.
|
||||
///
|
||||
/// This function assumes that `child_block_epoch >= self.epoch`. It is the responsibility of
|
||||
/// the caller to check this condition, or else incorrect results will be produced.
|
||||
pub fn proposer_shuffling_root_for_child_block(
|
||||
&self,
|
||||
child_block_epoch: Epoch,
|
||||
spec: &ChainSpec,
|
||||
) -> Hash256 {
|
||||
let block_epoch = self.current_epoch_shuffling_id.shuffling_epoch;
|
||||
|
||||
// For child blocks in the Fulu fork epoch itself, we want to use the old logic. There is no
|
||||
// lookahead in the first Fulu epoch. So we check whether Fulu is enabled at
|
||||
// `child_block_epoch - 1`, i.e. whether `child_block_epoch > fulu_fork_epoch`.
|
||||
if !spec
|
||||
.fork_name_at_epoch(child_block_epoch.saturating_sub(1_u64))
|
||||
.fulu_enabled()
|
||||
{
|
||||
// Prior to Fulu the proposer shuffling decision root for the current epoch is the same
|
||||
// as the attestation shuffling for the *next* epoch, i.e. it is determined at the start
|
||||
// of the current epoch.
|
||||
if block_epoch == child_block_epoch {
|
||||
self.next_epoch_shuffling_id.shuffling_decision_block
|
||||
} else {
|
||||
// Otherwise, the child block epoch is greater, so its decision root is its parent
|
||||
// root itself (this block's root).
|
||||
self.root
|
||||
}
|
||||
} else {
|
||||
// After Fulu the proposer shuffling is determined with lookahead, so if the block
|
||||
// lies in the same epoch as its parent, its decision root is the same as the
|
||||
// parent's current epoch attester shuffling
|
||||
//
|
||||
// i.e. the block from the end of epoch N - 2.
|
||||
if child_block_epoch == block_epoch {
|
||||
self.current_epoch_shuffling_id.shuffling_decision_block
|
||||
} else if child_block_epoch == block_epoch + 1 {
|
||||
// If the block is the next epoch, then it instead shares its decision root with
|
||||
// the parent's *next epoch* attester shuffling.
|
||||
self.next_epoch_shuffling_id.shuffling_decision_block
|
||||
} else {
|
||||
// The child block lies in the future beyond the lookahead, at the point where this
|
||||
// block (its parent) will be the decision block.
|
||||
self.root
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Vec-wrapper which will grow to match any request.
|
||||
///
|
||||
/// E.g., a `get` or `insert` to an out-of-bounds element will cause the Vec to grow (using
|
||||
@@ -375,8 +426,6 @@ impl ProtoArrayForkChoice {
|
||||
) -> Result<Self, String> {
|
||||
let mut proto_array = ProtoArray {
|
||||
prune_threshold: DEFAULT_PRUNE_THRESHOLD,
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
nodes: Vec::with_capacity(1),
|
||||
indices: HashMap::with_capacity(1),
|
||||
unsatisfied_inclusion_list_blocks,
|
||||
@@ -401,7 +450,12 @@ impl ProtoArrayForkChoice {
|
||||
};
|
||||
|
||||
proto_array
|
||||
.on_block::<E>(block, current_slot)
|
||||
.on_block::<E>(
|
||||
block,
|
||||
current_slot,
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
)
|
||||
.map_err(|e| format!("Failed to add finalized block to proto_array: {:?}", e))?;
|
||||
|
||||
Ok(Self {
|
||||
@@ -425,9 +479,10 @@ impl ProtoArrayForkChoice {
|
||||
pub fn process_execution_payload_invalidation<E: EthSpec>(
|
||||
&mut self,
|
||||
op: &InvalidationOperation,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
) -> Result<(), String> {
|
||||
self.proto_array
|
||||
.propagate_execution_payload_invalidation::<E>(op)
|
||||
.propagate_execution_payload_invalidation::<E>(op, finalized_checkpoint)
|
||||
.map_err(|e| format!("Failed to process invalid payload: {:?}", e))
|
||||
}
|
||||
|
||||
@@ -451,13 +506,20 @@ impl ProtoArrayForkChoice {
|
||||
&mut self,
|
||||
block: Block,
|
||||
current_slot: Slot,
|
||||
justified_checkpoint: Checkpoint,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
) -> Result<(), String> {
|
||||
if block.parent_root.is_none() {
|
||||
return Err("Missing parent root".to_string());
|
||||
}
|
||||
|
||||
self.proto_array
|
||||
.on_block::<E>(block, current_slot)
|
||||
.on_block::<E>(
|
||||
block,
|
||||
current_slot,
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
)
|
||||
.map_err(|e| format!("process_block_error: {:?}", e))
|
||||
}
|
||||
|
||||
@@ -499,7 +561,12 @@ impl ProtoArrayForkChoice {
|
||||
*old_balances = new_balances.clone();
|
||||
|
||||
self.proto_array
|
||||
.find_head::<E>(&justified_checkpoint.root, current_slot)
|
||||
.find_head::<E>(
|
||||
&justified_checkpoint.root,
|
||||
current_slot,
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
)
|
||||
.map_err(|e| format!("find_head failed: {:?}", e))
|
||||
}
|
||||
|
||||
@@ -707,24 +774,22 @@ impl ProtoArrayForkChoice {
|
||||
|
||||
// If the invalid root was boosted, apply the weight to it and
|
||||
// ancestors.
|
||||
if let Some(proposer_score_boost) = spec.proposer_score_boost {
|
||||
if self.proto_array.previous_proposer_boost.root == node.root {
|
||||
// Compute the score based upon the current balances. We can't rely on
|
||||
// the `previous_proposr_boost.score` since it is set to zero with an
|
||||
// invalid node.
|
||||
let proposer_score = calculate_committee_fraction::<E>(
|
||||
&self.balances,
|
||||
proposer_score_boost,
|
||||
)
|
||||
.ok_or("Failed to compute proposer boost")?;
|
||||
// Store the score we've applied here so it can be removed in
|
||||
// a later call to `apply_score_changes`.
|
||||
self.proto_array.previous_proposer_boost.score = proposer_score;
|
||||
// Apply this boost to this node.
|
||||
restored_weight = restored_weight
|
||||
.checked_add(proposer_score)
|
||||
.ok_or("Overflow when adding boost to weight")?;
|
||||
}
|
||||
if let Some(proposer_score_boost) = spec.proposer_score_boost
|
||||
&& self.proto_array.previous_proposer_boost.root == node.root
|
||||
{
|
||||
// Compute the score based upon the current balances. We can't rely on
|
||||
// the `previous_proposr_boost.score` since it is set to zero with an
|
||||
// invalid node.
|
||||
let proposer_score =
|
||||
calculate_committee_fraction::<E>(&self.balances, proposer_score_boost)
|
||||
.ok_or("Failed to compute proposer boost")?;
|
||||
// Store the score we've applied here so it can be removed in
|
||||
// a later call to `apply_score_changes`.
|
||||
self.proto_array.previous_proposer_boost.score = proposer_score;
|
||||
// Apply this boost to this node.
|
||||
restored_weight = restored_weight
|
||||
.checked_add(proposer_score)
|
||||
.ok_or("Overflow when adding boost to weight")?;
|
||||
}
|
||||
|
||||
// Add the restored weight to the node and all ancestors.
|
||||
@@ -838,9 +903,10 @@ impl ProtoArrayForkChoice {
|
||||
pub fn is_finalized_checkpoint_or_descendant<E: EthSpec>(
|
||||
&self,
|
||||
descendant_root: Hash256,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
) -> bool {
|
||||
self.proto_array
|
||||
.is_finalized_checkpoint_or_descendant::<E>(descendant_root)
|
||||
.is_finalized_checkpoint_or_descendant::<E>(descendant_root, best_finalized_checkpoint)
|
||||
}
|
||||
|
||||
pub fn latest_message(&self, validator_index: usize) -> Option<(Hash256, Epoch)> {
|
||||
@@ -858,7 +924,7 @@ impl ProtoArrayForkChoice {
|
||||
}
|
||||
|
||||
/// See `ProtoArray::iter_nodes`
|
||||
pub fn iter_nodes(&self, block_root: &Hash256) -> Iter {
|
||||
pub fn iter_nodes(&self, block_root: &Hash256) -> Iter<'_> {
|
||||
self.proto_array.iter_nodes(block_root)
|
||||
}
|
||||
|
||||
@@ -866,18 +932,38 @@ impl ProtoArrayForkChoice {
|
||||
pub fn iter_block_roots(
|
||||
&self,
|
||||
block_root: &Hash256,
|
||||
) -> impl Iterator<Item = (Hash256, Slot)> + use<'_> {
|
||||
) -> impl Iterator<Item = (Hash256, Slot)> + '_ {
|
||||
self.proto_array.iter_block_roots(block_root)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> Vec<u8> {
|
||||
SszContainer::from(self).as_ssz_bytes()
|
||||
pub fn as_ssz_container(
|
||||
&self,
|
||||
justified_checkpoint: Checkpoint,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
) -> SszContainer {
|
||||
SszContainer::from_proto_array(self, justified_checkpoint, finalized_checkpoint)
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
|
||||
pub fn as_bytes(
|
||||
&self,
|
||||
justified_checkpoint: Checkpoint,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
) -> Vec<u8> {
|
||||
self.as_ssz_container(justified_checkpoint, finalized_checkpoint)
|
||||
.as_ssz_bytes()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8], balances: JustifiedBalances) -> Result<Self, String> {
|
||||
let container = SszContainer::from_ssz_bytes(bytes)
|
||||
.map_err(|e| format!("Failed to decode ProtoArrayForkChoice: {:?}", e))?;
|
||||
container
|
||||
Self::from_container(container, balances)
|
||||
}
|
||||
|
||||
pub fn from_container(
|
||||
container: SszContainer,
|
||||
balances: JustifiedBalances,
|
||||
) -> Result<Self, String> {
|
||||
(container, balances)
|
||||
.try_into()
|
||||
.map_err(|e| format!("Failed to initialize ProtoArrayForkChoice: {e:?}"))
|
||||
}
|
||||
@@ -897,8 +983,12 @@ impl ProtoArrayForkChoice {
|
||||
}
|
||||
|
||||
/// Returns all nodes that have zero children and are descended from the finalized checkpoint.
|
||||
pub fn heads_descended_from_finalization<E: EthSpec>(&self) -> Vec<&ProtoNode> {
|
||||
self.proto_array.heads_descended_from_finalization::<E>()
|
||||
pub fn heads_descended_from_finalization<E: EthSpec>(
|
||||
&self,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
) -> Vec<&ProtoNode> {
|
||||
self.proto_array
|
||||
.heads_descended_from_finalization::<E>(best_finalized_checkpoint)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1008,7 +1098,8 @@ fn compute_deltas(
|
||||
#[cfg(test)]
|
||||
mod test_compute_deltas {
|
||||
use super::*;
|
||||
use types::{FixedBytesExtended, MainnetEthSpec};
|
||||
use fixed_bytes::FixedBytesExtended;
|
||||
use types::MainnetEthSpec;
|
||||
|
||||
/// Gives a hash that is not the zero hash (unless i is `usize::MAX)`.
|
||||
fn hash_from_index(i: usize) -> Hash256 {
|
||||
@@ -1069,6 +1160,8 @@ mod test_compute_deltas {
|
||||
unrealized_finalized_checkpoint: Some(genesis_checkpoint),
|
||||
},
|
||||
genesis_slot + 1,
|
||||
genesis_checkpoint,
|
||||
genesis_checkpoint,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1092,6 +1185,8 @@ mod test_compute_deltas {
|
||||
unrealized_finalized_checkpoint: None,
|
||||
},
|
||||
genesis_slot + 1,
|
||||
genesis_checkpoint,
|
||||
genesis_checkpoint,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -1105,10 +1200,24 @@ 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_finalized_checkpoint_or_descendant::<MainnetEthSpec>(
|
||||
finalized_root,
|
||||
genesis_checkpoint
|
||||
));
|
||||
assert!(fc.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(
|
||||
finalized_desc,
|
||||
genesis_checkpoint
|
||||
));
|
||||
assert!(!fc.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(
|
||||
not_finalized_desc,
|
||||
genesis_checkpoint
|
||||
));
|
||||
assert!(
|
||||
!fc.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(
|
||||
unknown,
|
||||
genesis_checkpoint
|
||||
)
|
||||
);
|
||||
|
||||
assert!(!fc.is_descendant(finalized_desc, not_finalized_desc));
|
||||
assert!(fc.is_descendant(finalized_desc, finalized_desc));
|
||||
@@ -1205,6 +1314,8 @@ mod test_compute_deltas {
|
||||
unrealized_finalized_checkpoint: Some(genesis_checkpoint),
|
||||
},
|
||||
Slot::from(block.slot),
|
||||
genesis_checkpoint,
|
||||
genesis_checkpoint,
|
||||
)
|
||||
.unwrap();
|
||||
};
|
||||
@@ -1259,29 +1370,34 @@ mod test_compute_deltas {
|
||||
|
||||
// Set the finalized checkpoint to finalize the first slot of epoch 1 on
|
||||
// the canonical chain.
|
||||
fc.proto_array.finalized_checkpoint = Checkpoint {
|
||||
let finalized_checkpoint = Checkpoint {
|
||||
root: finalized_root,
|
||||
epoch: Epoch::new(1),
|
||||
};
|
||||
|
||||
assert!(
|
||||
fc.proto_array
|
||||
.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(finalized_root),
|
||||
.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(
|
||||
finalized_root,
|
||||
finalized_checkpoint
|
||||
),
|
||||
"the finalized checkpoint is the finalized checkpoint"
|
||||
);
|
||||
|
||||
assert!(
|
||||
fc.proto_array
|
||||
.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(get_block_root(
|
||||
canonical_slot
|
||||
)),
|
||||
.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(
|
||||
get_block_root(canonical_slot),
|
||||
finalized_checkpoint
|
||||
),
|
||||
"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
|
||||
)),
|
||||
.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(
|
||||
get_block_root(non_canonical_slot),
|
||||
finalized_checkpoint
|
||||
),
|
||||
"although the non-canonical block is a descendant of the finalized block, \
|
||||
it's not a descendant of the finalized checkpoint"
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::proto_array::ProposerBoost;
|
||||
use crate::{
|
||||
Error, JustifiedBalances,
|
||||
proto_array::{ProtoArray, ProtoNodeV17},
|
||||
proto_array_fork_choice::{ElasticList, ProtoArrayForkChoice, VoteTracker},
|
||||
Error, JustifiedBalances,
|
||||
};
|
||||
use ssz::{four_byte_option_impl, Encode};
|
||||
use ssz::{Encode, four_byte_option_impl};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::collections::HashMap;
|
||||
use superstruct::superstruct;
|
||||
@@ -14,32 +14,41 @@ use types::{Checkpoint, Hash256, Slot};
|
||||
// selector.
|
||||
four_byte_option_impl!(four_byte_option_checkpoint, Checkpoint);
|
||||
|
||||
pub type SszContainer = SszContainerV17;
|
||||
pub type SszContainer = SszContainerV28;
|
||||
|
||||
#[superstruct(variants(V17), variant_attributes(derive(Encode, Decode)), no_enum)]
|
||||
#[superstruct(
|
||||
variants(V17, V28),
|
||||
variant_attributes(derive(Encode, Decode, Clone)),
|
||||
no_enum
|
||||
)]
|
||||
pub struct SszContainer {
|
||||
pub votes: Vec<VoteTracker>,
|
||||
#[superstruct(only(V17))]
|
||||
pub balances: Vec<u64>,
|
||||
pub prune_threshold: usize,
|
||||
pub justified_checkpoint: Checkpoint,
|
||||
pub finalized_checkpoint: Checkpoint,
|
||||
#[superstruct(only(V17))]
|
||||
// Deprecated, remove in a future schema migration
|
||||
justified_checkpoint: Checkpoint,
|
||||
// Deprecated, remove in a future schema migration
|
||||
finalized_checkpoint: Checkpoint,
|
||||
pub nodes: Vec<ProtoNodeV17>,
|
||||
pub indices: Vec<(Hash256, usize)>,
|
||||
pub previous_proposer_boost: ProposerBoost,
|
||||
pub unsatisfied_inclusion_list_blocks: Vec<(Slot, Hash256)>,
|
||||
}
|
||||
|
||||
impl From<&ProtoArrayForkChoice> for SszContainer {
|
||||
fn from(from: &ProtoArrayForkChoice) -> Self {
|
||||
impl SszContainer {
|
||||
pub fn from_proto_array(
|
||||
from: &ProtoArrayForkChoice,
|
||||
justified_checkpoint: Checkpoint,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
) -> Self {
|
||||
let proto_array = &from.proto_array;
|
||||
|
||||
Self {
|
||||
votes: from.votes.0.clone(),
|
||||
balances: from.balances.effective_balances.clone(),
|
||||
prune_threshold: proto_array.prune_threshold,
|
||||
justified_checkpoint: proto_array.justified_checkpoint,
|
||||
finalized_checkpoint: proto_array.finalized_checkpoint,
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
nodes: proto_array.nodes.clone(),
|
||||
indices: proto_array.indices.iter().map(|(k, v)| (*k, *v)).collect(),
|
||||
previous_proposer_boost: proto_array.previous_proposer_boost,
|
||||
@@ -52,14 +61,12 @@ impl From<&ProtoArrayForkChoice> for SszContainer {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SszContainer> for ProtoArrayForkChoice {
|
||||
impl TryFrom<(SszContainer, JustifiedBalances)> for ProtoArrayForkChoice {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(from: SszContainer) -> Result<Self, Error> {
|
||||
fn try_from((from, balances): (SszContainer, JustifiedBalances)) -> Result<Self, Error> {
|
||||
let proto_array = ProtoArray {
|
||||
prune_threshold: from.prune_threshold,
|
||||
justified_checkpoint: from.justified_checkpoint,
|
||||
finalized_checkpoint: from.finalized_checkpoint,
|
||||
nodes: from.nodes,
|
||||
indices: from.indices.into_iter().collect::<HashMap<_, _>>(),
|
||||
previous_proposer_boost: from.previous_proposer_boost,
|
||||
@@ -72,7 +79,40 @@ impl TryFrom<SszContainer> for ProtoArrayForkChoice {
|
||||
Ok(Self {
|
||||
proto_array,
|
||||
votes: ElasticList(from.votes),
|
||||
balances: JustifiedBalances::from_effective_balances(from.balances)?,
|
||||
balances,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Convert V17 to V28 by dropping balances.
|
||||
impl From<SszContainerV17> for SszContainerV28 {
|
||||
fn from(v17: SszContainerV17) -> Self {
|
||||
Self {
|
||||
votes: v17.votes,
|
||||
prune_threshold: v17.prune_threshold,
|
||||
justified_checkpoint: v17.justified_checkpoint,
|
||||
finalized_checkpoint: v17.finalized_checkpoint,
|
||||
nodes: v17.nodes,
|
||||
indices: v17.indices,
|
||||
previous_proposer_boost: v17.previous_proposer_boost,
|
||||
unsatisfied_inclusion_list_blocks: v17.unsatisfied_inclusion_list_blocks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert V28 to V17 by re-adding balances.
|
||||
impl From<(SszContainerV28, JustifiedBalances)> for SszContainerV17 {
|
||||
fn from((v28, balances): (SszContainerV28, JustifiedBalances)) -> Self {
|
||||
Self {
|
||||
votes: v28.votes,
|
||||
balances: balances.effective_balances.clone(),
|
||||
prune_threshold: v28.prune_threshold,
|
||||
justified_checkpoint: v28.justified_checkpoint,
|
||||
finalized_checkpoint: v28.finalized_checkpoint,
|
||||
nodes: v28.nodes,
|
||||
indices: v28.indices,
|
||||
previous_proposer_boost: v28.previous_proposer_boost,
|
||||
unsatisfied_inclusion_list_blocks: v28.unsatisfied_inclusion_list_blocks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user