mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 12:47:05 +00:00
Gloas alpha spec 8 (#9315)
https://github.com/ethereum/consensus-specs/releases/tag/v1.7.0-alpha.8 Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu> Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use crate::PayloadStatus;
|
||||
use safe_arith::ArithError;
|
||||
use types::{Checkpoint, Epoch, ExecutionBlockHash, Hash256, Slot};
|
||||
|
||||
@@ -62,6 +63,10 @@ pub enum Error {
|
||||
},
|
||||
NoViableChildren,
|
||||
OnBlockRequiresProposerIndex,
|
||||
InvalidPayloadStatus {
|
||||
block_root: Hash256,
|
||||
payload_status: PayloadStatus,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<ArithError> for Error {
|
||||
|
||||
@@ -556,7 +556,11 @@ impl ForkChoiceTestDefinition {
|
||||
node_v29.payload_data_availability_votes =
|
||||
BitVector::from_bytes(smallvec::smallvec![fill; 64])
|
||||
.expect("valid 512-bit bitvector");
|
||||
// Per spec, is_payload_timely/is_payload_data_available require
|
||||
// Mark all PTC members as having participated.
|
||||
node_v29.ptc_participation =
|
||||
BitVector::from_bytes(smallvec::smallvec![0xFF; 64])
|
||||
.expect("valid 512-bit bitvector");
|
||||
// Per spec, payload_timeliness/payload_data_availability require
|
||||
// the payload to be in payload_states (payload_received).
|
||||
node_v29.payload_received = is_timely || is_data_available;
|
||||
}
|
||||
|
||||
@@ -155,6 +155,10 @@ pub struct ProtoNode {
|
||||
/// Tiebreak derived as: `num_set_bits() > ptc_size / 2`.
|
||||
#[superstruct(only(V29))]
|
||||
pub payload_data_availability_votes: BitVector<U512>,
|
||||
/// Tracks which PTC members have cast a vote.
|
||||
/// Bit i set means PTC member i has submitted a payload attestation.
|
||||
#[superstruct(only(V29))]
|
||||
pub ptc_participation: BitVector<U512>,
|
||||
/// Whether the execution payload for this block has been received and validated locally.
|
||||
/// Maps to `root in store.payload_states` in the spec.
|
||||
#[superstruct(only(V29), partial_getter(copy))]
|
||||
@@ -193,31 +197,60 @@ impl ProtoNode {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_payload_timely<E: EthSpec>(&self) -> bool {
|
||||
/// Checks if `timely` matches our view of payload timeliness.
|
||||
/// Returns whether the execution payload for the node is considered `timely`
|
||||
/// (or not `timely` when `timely` is `false`), taking into consideration local
|
||||
/// availability and PTC votes.
|
||||
pub fn payload_timeliness<E: EthSpec>(&self, timely: bool) -> Result<bool, Error> {
|
||||
let Ok(node) = self.as_v29() else {
|
||||
return false;
|
||||
return Err(Error::InvalidNodeVariant {
|
||||
block_root: self.root(),
|
||||
});
|
||||
};
|
||||
|
||||
// Equivalent to `if root not in store.payload_states` in the spec.
|
||||
// Equivalent to `if not is_payload_verified(store, root)` in the spec.
|
||||
if !node.payload_received {
|
||||
return false;
|
||||
return Ok(!timely);
|
||||
}
|
||||
|
||||
node.payload_timeliness_votes.num_set_bits() > E::payload_timely_threshold()
|
||||
let matching_votes = if timely {
|
||||
node.payload_timeliness_votes.num_set_bits()
|
||||
} else {
|
||||
// We take into consideration only participating ptc votes. An unset bit
|
||||
// in `payload_timeliness_votes` could be an absent vote or a no vote.
|
||||
node.ptc_participation
|
||||
.num_set_bits()
|
||||
.saturating_sub(node.payload_timeliness_votes.num_set_bits())
|
||||
};
|
||||
Ok(matching_votes > E::payload_timely_threshold())
|
||||
}
|
||||
|
||||
pub fn is_payload_data_available<E: EthSpec>(&self) -> bool {
|
||||
/// Checks if `available` matches our view of payload data availability.
|
||||
/// Return whether the blob data for the node is considered `available`
|
||||
/// (or not, when `available` is `False`), taking into consideration local
|
||||
/// availability and PTC votes.
|
||||
pub fn payload_data_availability<E: EthSpec>(&self, available: bool) -> Result<bool, Error> {
|
||||
let Ok(node) = self.as_v29() else {
|
||||
return false;
|
||||
return Err(Error::InvalidNodeVariant {
|
||||
block_root: self.root(),
|
||||
});
|
||||
};
|
||||
|
||||
// Equivalent to `if root not in store.payload_states` in the spec.
|
||||
// Equivalent to `if not is_payload_verified(store, root)` in the spec.
|
||||
if !node.payload_received {
|
||||
return false;
|
||||
return Ok(!available);
|
||||
}
|
||||
|
||||
node.payload_data_availability_votes.num_set_bits()
|
||||
> E::data_availability_timely_threshold()
|
||||
let matching_votes = if available {
|
||||
node.payload_data_availability_votes.num_set_bits()
|
||||
} else {
|
||||
// We take into consideration only participating ptc votes. An unset bit
|
||||
// in `payload_data_availability_votes` could be an absent vote or a no vote.
|
||||
node.ptc_participation
|
||||
.num_set_bits()
|
||||
.saturating_sub(node.payload_data_availability_votes.num_set_bits())
|
||||
};
|
||||
Ok(matching_votes > E::data_availability_timely_threshold())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,6 +638,7 @@ impl ProtoArray {
|
||||
execution_payload_parent_hash,
|
||||
payload_timeliness_votes: BitVector::default(),
|
||||
payload_data_availability_votes: BitVector::default(),
|
||||
ptc_participation: BitVector::default(),
|
||||
payload_received: false,
|
||||
proposer_index,
|
||||
// Spec: `record_block_timeliness` + `get_forkchoice_store`.
|
||||
@@ -1501,12 +1535,46 @@ impl ProtoArray {
|
||||
}
|
||||
}
|
||||
|
||||
/// Called by the proposer to decide whether to build on the full or empty
|
||||
/// parent pending node. Returns false if the PTC has voted the data as unavailable.
|
||||
pub fn should_build_on_full<E: EthSpec>(
|
||||
&self,
|
||||
fc_node: &IndexedForkChoiceNode,
|
||||
proto_node: &ProtoNode,
|
||||
) -> Result<bool, Error> {
|
||||
if fc_node.payload_status == PayloadStatus::Pending {
|
||||
return Err(Error::InvalidPayloadStatus {
|
||||
block_root: proto_node.root(),
|
||||
payload_status: fc_node.payload_status,
|
||||
});
|
||||
}
|
||||
|
||||
if fc_node.payload_status == PayloadStatus::Empty {
|
||||
return Ok(false);
|
||||
}
|
||||
// Check that false votes have not achieved an absolute majority. This allows the payload to be
|
||||
// considered available when either a majority have voted true or not enough votes have
|
||||
// been cast either way.
|
||||
Ok(!proto_node.payload_data_availability::<E>(false)?)
|
||||
}
|
||||
|
||||
pub fn should_extend_payload<E: EthSpec>(
|
||||
&self,
|
||||
fc_node: &IndexedForkChoiceNode,
|
||||
proto_node: &ProtoNode,
|
||||
proposer_boost_root: Hash256,
|
||||
) -> Result<bool, Error> {
|
||||
let Ok(node) = proto_node.as_v29() else {
|
||||
return Err(Error::InvalidNodeVariant {
|
||||
block_root: fc_node.root,
|
||||
});
|
||||
};
|
||||
|
||||
// Spec equivalent to `if not is_payload_verified(store, root): return False`
|
||||
if !node.payload_received {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Per spec: `proposer_root == Root()` is one of the `or` conditions that
|
||||
// makes `should_extend_payload` return True.
|
||||
if proposer_boost_root.is_zero() {
|
||||
@@ -1531,11 +1599,10 @@ impl ProtoArray {
|
||||
.ok_or(Error::InvalidNodeIndex(parent_index))?
|
||||
.root();
|
||||
|
||||
Ok(
|
||||
(proto_node.is_payload_timely::<E>() && proto_node.is_payload_data_available::<E>())
|
||||
|| proposer_boost_parent_root != fc_node.root
|
||||
|| proposer_boost_node.is_parent_node_full(),
|
||||
)
|
||||
Ok((proto_node.payload_timeliness::<E>(true)?
|
||||
&& proto_node.payload_data_availability::<E>(true)?)
|
||||
|| proposer_boost_parent_root != fc_node.root
|
||||
|| proposer_boost_node.is_parent_node_full())
|
||||
}
|
||||
|
||||
/// Update the tree with new finalization information. The tree is only actually pruned if both
|
||||
|
||||
@@ -640,6 +640,9 @@ impl ProtoArrayForkChoice {
|
||||
.map_err(|e| {
|
||||
format!("process_payload_attestation: data availability set failed: {e:?}")
|
||||
})?;
|
||||
v29.ptc_participation
|
||||
.set(ptc_index, true)
|
||||
.map_err(|e| format!("process_payload_attestation: participation set failed: {e:?}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1006,6 +1009,33 @@ impl ProtoArrayForkChoice {
|
||||
})
|
||||
}
|
||||
|
||||
/// Called by the proposer to decide whether to build on the full or empty
|
||||
/// parent. Returns false if the PTC has voted the data as unavailable.
|
||||
pub fn should_build_on_full<E: EthSpec>(
|
||||
&self,
|
||||
block_root: &Hash256,
|
||||
parent_payload_status: PayloadStatus,
|
||||
) -> Result<bool, String> {
|
||||
let block_index = self
|
||||
.proto_array
|
||||
.indices
|
||||
.get(block_root)
|
||||
.ok_or_else(|| format!("Unknown block root: {block_root:?}"))?;
|
||||
let proto_node = self
|
||||
.proto_array
|
||||
.nodes
|
||||
.get(*block_index)
|
||||
.ok_or_else(|| format!("Missing node at index: {block_index}"))?;
|
||||
let fc_node = IndexedForkChoiceNode {
|
||||
root: proto_node.root(),
|
||||
proto_node_index: *block_index,
|
||||
payload_status: parent_payload_status,
|
||||
};
|
||||
self.proto_array
|
||||
.should_build_on_full::<E>(&fc_node, proto_node)
|
||||
.map_err(|e| format!("{e:?}"))
|
||||
}
|
||||
|
||||
/// Returns whether the proposer should extend the parent's execution payload chain.
|
||||
///
|
||||
/// This checks timeliness, data availability, and proposer boost conditions per the spec.
|
||||
|
||||
Reference in New Issue
Block a user