mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-02 04:14:33 +00:00
Resolve merge conflicts
This commit is contained in:
@@ -68,6 +68,7 @@ fn build_chain(num_blocks: u64, gloas: bool) -> (ProtoArrayForkChoice, types::Ch
|
||||
},
|
||||
execution_payload_block_hash: if is_gloas { Some(get_hash(i)) } else { None },
|
||||
proposer_index: Some(0),
|
||||
payload_received: false,
|
||||
};
|
||||
|
||||
fork_choice
|
||||
|
||||
@@ -50,7 +50,6 @@ pub enum Error {
|
||||
block_root: Hash256,
|
||||
parent_root: Hash256,
|
||||
},
|
||||
InvalidEpochOffset(u64),
|
||||
Arith(ArithError),
|
||||
InvalidNodeVariant {
|
||||
block_root: Hash256,
|
||||
|
||||
@@ -132,6 +132,15 @@ pub enum Operation {
|
||||
#[serde(default)]
|
||||
proposer_boost_root: Option<Hash256>,
|
||||
},
|
||||
/// Assert the result of `should_build_on_full` for the parent `block_root`, where
|
||||
/// `parent_payload_status` is the status the proposer would build on and `proposal_slot`
|
||||
/// is the slot being proposed.
|
||||
AssertShouldBuildOnFull {
|
||||
block_root: Hash256,
|
||||
parent_payload_status: PayloadStatus,
|
||||
proposal_slot: Slot,
|
||||
expected: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -329,6 +338,7 @@ impl ForkChoiceTestDefinition {
|
||||
execution_payload_parent_hash,
|
||||
execution_payload_block_hash,
|
||||
proposer_index: Some(0),
|
||||
payload_received: false,
|
||||
};
|
||||
fork_choice
|
||||
.process_block::<MainnetEthSpec>(block, slot, &spec, Duration::ZERO)
|
||||
@@ -629,6 +639,30 @@ impl ForkChoiceTestDefinition {
|
||||
assert_eq!(
|
||||
actual, expected,
|
||||
"latest_parent_full_block mismatch at op index {}",
|
||||
op_index,
|
||||
);
|
||||
}
|
||||
Operation::AssertShouldBuildOnFull {
|
||||
block_root,
|
||||
parent_payload_status,
|
||||
proposal_slot,
|
||||
expected,
|
||||
} => {
|
||||
let actual = fork_choice
|
||||
.should_build_on_full::<MainnetEthSpec>(
|
||||
&block_root,
|
||||
parent_payload_status,
|
||||
proposal_slot,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"should_build_on_full op at index {} returned error: {}",
|
||||
op_index, e
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
actual, expected,
|
||||
"should_build_on_full mismatch at op index {}",
|
||||
op_index
|
||||
);
|
||||
}
|
||||
|
||||
@@ -971,6 +971,90 @@ pub fn get_gloas_proposer_boost_flips_ancestor_test_definition() -> ForkChoiceTe
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests the slot check in `should_build_on_full`. When the parent is from an earlier slot the
|
||||
/// function returns `true` and ignores PTC data-availability votes. It only checks those votes
|
||||
/// when the parent is from the immediately preceding slot.
|
||||
pub fn get_gloas_should_build_on_full_test_definition() -> ForkChoiceTestDefinition {
|
||||
let mut ops = vec![];
|
||||
|
||||
// Block 1 at slot 1, child of genesis.
|
||||
ops.push(Operation::ProcessBlock {
|
||||
slot: Slot::new(1),
|
||||
root: get_root(1),
|
||||
parent_root: get_root(0),
|
||||
justified_checkpoint: get_checkpoint(0),
|
||||
finalized_checkpoint: get_checkpoint(0),
|
||||
execution_payload_parent_hash: Some(get_hash(0)),
|
||||
execution_payload_block_hash: Some(get_hash(1)),
|
||||
});
|
||||
|
||||
// PTC has voted the payload data unavailable. `is_timely` sets `payload_received` so the votes
|
||||
// are consulted, and clearing the data-availability bits gives the "false" votes a majority.
|
||||
ops.push(Operation::SetPayloadTiebreak {
|
||||
block_root: get_root(1),
|
||||
is_timely: true,
|
||||
is_data_available: false,
|
||||
});
|
||||
|
||||
// When the parent is `Empty` `should_build_on_full` returns `false`. This check runs before
|
||||
// the slot check, so the result is `false` for both the previous-slot case (block slot 1, proposal slot 2)
|
||||
// and an earlier-slot case (proposal slot 3).
|
||||
ops.push(Operation::AssertShouldBuildOnFull {
|
||||
block_root: get_root(1),
|
||||
parent_payload_status: PayloadStatus::Empty,
|
||||
proposal_slot: Slot::new(2),
|
||||
expected: false,
|
||||
});
|
||||
ops.push(Operation::AssertShouldBuildOnFull {
|
||||
block_root: get_root(1),
|
||||
parent_payload_status: PayloadStatus::Empty,
|
||||
proposal_slot: Slot::new(3),
|
||||
expected: false,
|
||||
});
|
||||
|
||||
// `Full` parent from the immediately preceding slot (block slot 1, proposal slot 2). The PTC
|
||||
// votes are consulted, and since data is unavailable the proposer does not build on full.
|
||||
ops.push(Operation::AssertShouldBuildOnFull {
|
||||
block_root: get_root(1),
|
||||
parent_payload_status: PayloadStatus::Full,
|
||||
proposal_slot: Slot::new(2),
|
||||
expected: false,
|
||||
});
|
||||
|
||||
// `Full` parent from an *earlier* slot (block slot 1, proposal slot 3). The slot check
|
||||
// short-circuits to `true` without consulting the (unavailable) PTC votes.
|
||||
ops.push(Operation::AssertShouldBuildOnFull {
|
||||
block_root: get_root(1),
|
||||
parent_payload_status: PayloadStatus::Full,
|
||||
proposal_slot: Slot::new(3),
|
||||
expected: true,
|
||||
});
|
||||
|
||||
// Flip the PTC view to *available* and re-check the previous-slot case. The votes now permit
|
||||
// building on full.
|
||||
ops.push(Operation::SetPayloadTiebreak {
|
||||
block_root: get_root(1),
|
||||
is_timely: true,
|
||||
is_data_available: true,
|
||||
});
|
||||
ops.push(Operation::AssertShouldBuildOnFull {
|
||||
block_root: get_root(1),
|
||||
parent_payload_status: PayloadStatus::Full,
|
||||
proposal_slot: Slot::new(2),
|
||||
expected: true,
|
||||
});
|
||||
|
||||
ForkChoiceTestDefinition {
|
||||
finalized_block_slot: Slot::new(0),
|
||||
justified_checkpoint: get_checkpoint(0),
|
||||
finalized_checkpoint: get_checkpoint(0),
|
||||
operations: ops,
|
||||
execution_payload_parent_hash: Some(get_hash(42)),
|
||||
execution_payload_block_hash: Some(get_hash(0)),
|
||||
spec: Some(gloas_spec()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1160,6 +1244,12 @@ mod tests {
|
||||
test.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_build_on_full_slot_check() {
|
||||
let test = get_gloas_should_build_on_full_test_definition();
|
||||
test.run();
|
||||
}
|
||||
|
||||
/// Test that execution payload invalidation propagates across the V17→V29 fork
|
||||
/// boundary: after invalidating a V17 parent, head must not select any descendant.
|
||||
///
|
||||
|
||||
@@ -8,8 +8,8 @@ mod ssz_container;
|
||||
pub use crate::justified_balances::JustifiedBalances;
|
||||
pub use crate::proto_array::{InvalidationOperation, calculate_committee_fraction};
|
||||
pub use crate::proto_array_fork_choice::{
|
||||
Block, DisallowedReOrgOffsets, DoNotReOrg, ExecutionStatus, LatestMessage, PayloadStatus,
|
||||
ProposerHeadError, ProposerHeadInfo, ProtoArrayForkChoice, ReOrgThreshold,
|
||||
Block, DoNotReOrg, ExecutionStatus, LatestMessage, PayloadStatus, ProposerHeadError,
|
||||
ProposerHeadInfo, ProtoArrayForkChoice, ReOrgThreshold,
|
||||
};
|
||||
pub use error::Error;
|
||||
|
||||
|
||||
@@ -1596,10 +1596,13 @@ 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.
|
||||
/// For a parent from an earlier slot the `Empty` or `Full` node has already been resolved
|
||||
/// by attestation weight in `get_head`.
|
||||
pub fn should_build_on_full<E: EthSpec>(
|
||||
&self,
|
||||
fc_node: &IndexedForkChoiceNode,
|
||||
proto_node: &ProtoNode,
|
||||
current_slot: Slot,
|
||||
) -> Result<bool, Error> {
|
||||
if fc_node.payload_status == PayloadStatus::Pending {
|
||||
return Err(Error::InvalidPayloadStatus {
|
||||
@@ -1611,10 +1614,23 @@ impl ProtoArray {
|
||||
if fc_node.payload_status == PayloadStatus::Empty {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if proto_node.slot().saturating_add(1u64) != current_slot {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// 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)?)
|
||||
if proto_node.payload_data_availability::<E>(false)? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if proto_node.payload_timeliness::<E>(false)? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn should_extend_payload<E: EthSpec>(
|
||||
|
||||
@@ -242,6 +242,8 @@ pub struct Block {
|
||||
pub execution_payload_parent_hash: Option<ExecutionBlockHash>,
|
||||
pub execution_payload_block_hash: Option<ExecutionBlockHash>,
|
||||
pub proposer_index: Option<u64>,
|
||||
/// Whether the block's execution payload envelope has been received. Always `false` pre-Gloas.
|
||||
pub payload_received: bool,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
@@ -385,10 +387,6 @@ pub enum DoNotReOrg {
|
||||
MissingHeadFinalizedCheckpoint,
|
||||
ParentDistance,
|
||||
HeadDistance,
|
||||
ShufflingUnstable,
|
||||
DisallowedOffset {
|
||||
offset: u64,
|
||||
},
|
||||
JustificationAndFinalizationNotCompetitive,
|
||||
ChainNotFinalizing {
|
||||
epochs_since_finalization: u64,
|
||||
@@ -413,10 +411,6 @@ impl std::fmt::Display for DoNotReOrg {
|
||||
Self::MissingHeadFinalizedCheckpoint => write!(f, "finalized checkpoint missing"),
|
||||
Self::ParentDistance => write!(f, "parent too far from head"),
|
||||
Self::HeadDistance => write!(f, "head too far from current slot"),
|
||||
Self::ShufflingUnstable => write!(f, "shuffling unstable at epoch boundary"),
|
||||
Self::DisallowedOffset { offset } => {
|
||||
write!(f, "re-orgs disabled at offset {offset}")
|
||||
}
|
||||
Self::JustificationAndFinalizationNotCompetitive => {
|
||||
write!(f, "justification or finalization not competitive")
|
||||
}
|
||||
@@ -462,31 +456,6 @@ impl std::fmt::Display for DoNotReOrg {
|
||||
#[serde(transparent)]
|
||||
pub struct ReOrgThreshold(pub u64);
|
||||
|
||||
/// New-type for disallowed re-org slots.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct DisallowedReOrgOffsets {
|
||||
// Vecs are faster than hashmaps for small numbers of items.
|
||||
offsets: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Default for DisallowedReOrgOffsets {
|
||||
fn default() -> Self {
|
||||
DisallowedReOrgOffsets { offsets: vec![0] }
|
||||
}
|
||||
}
|
||||
|
||||
impl DisallowedReOrgOffsets {
|
||||
pub fn new<E: EthSpec>(offsets: Vec<u64>) -> Result<Self, Error> {
|
||||
for &offset in &offsets {
|
||||
if offset >= E::slots_per_epoch() {
|
||||
return Err(Error::InvalidEpochOffset(offset));
|
||||
}
|
||||
}
|
||||
Ok(Self { offsets })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct ProtoArrayForkChoice {
|
||||
pub(crate) proto_array: ProtoArray,
|
||||
@@ -535,6 +504,7 @@ impl ProtoArrayForkChoice {
|
||||
execution_payload_parent_hash,
|
||||
execution_payload_block_hash,
|
||||
proposer_index: Some(proposer_index),
|
||||
payload_received: false,
|
||||
};
|
||||
|
||||
proto_array
|
||||
@@ -724,7 +694,6 @@ impl ProtoArrayForkChoice {
|
||||
justified_balances: &JustifiedBalances,
|
||||
re_org_head_threshold: ReOrgThreshold,
|
||||
re_org_parent_threshold: ReOrgThreshold,
|
||||
disallowed_offsets: &DisallowedReOrgOffsets,
|
||||
max_epochs_since_finalization: Epoch,
|
||||
) -> Result<ProposerHeadInfo, ProposerHeadError<Error>> {
|
||||
let info = self.get_proposer_head_info::<E>(
|
||||
@@ -733,7 +702,6 @@ impl ProtoArrayForkChoice {
|
||||
justified_balances,
|
||||
re_org_head_threshold,
|
||||
re_org_parent_threshold,
|
||||
disallowed_offsets,
|
||||
max_epochs_since_finalization,
|
||||
)?;
|
||||
|
||||
@@ -784,7 +752,6 @@ impl ProtoArrayForkChoice {
|
||||
justified_balances: &JustifiedBalances,
|
||||
re_org_head_threshold: ReOrgThreshold,
|
||||
re_org_parent_threshold: ReOrgThreshold,
|
||||
disallowed_offsets: &DisallowedReOrgOffsets,
|
||||
max_epochs_since_finalization: Epoch,
|
||||
) -> Result<ProposerHeadInfo, ProposerHeadError<Error>> {
|
||||
let mut nodes = self
|
||||
@@ -823,18 +790,6 @@ impl ProtoArrayForkChoice {
|
||||
return Err(DoNotReOrg::ParentDistance.into());
|
||||
}
|
||||
|
||||
// Check shuffling stability.
|
||||
let shuffling_stable = re_org_block_slot % E::slots_per_epoch() != 0;
|
||||
if !shuffling_stable {
|
||||
return Err(DoNotReOrg::ShufflingUnstable.into());
|
||||
}
|
||||
|
||||
// Check allowed slot offsets.
|
||||
let offset = (re_org_block_slot % E::slots_per_epoch()).as_u64();
|
||||
if disallowed_offsets.offsets.contains(&offset) {
|
||||
return Err(DoNotReOrg::DisallowedOffset { offset }.into());
|
||||
}
|
||||
|
||||
// Check FFG.
|
||||
let ffg_competitive = parent_node.unrealized_justified_checkpoint()
|
||||
== head_node.unrealized_justified_checkpoint()
|
||||
@@ -1007,6 +962,7 @@ impl ProtoArrayForkChoice {
|
||||
execution_payload_parent_hash: block.execution_payload_parent_hash().ok(),
|
||||
execution_payload_block_hash: block.execution_payload_block_hash().ok(),
|
||||
proposer_index: block.proposer_index().ok(),
|
||||
payload_received: block.payload_received().unwrap_or(false),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1016,6 +972,7 @@ impl ProtoArrayForkChoice {
|
||||
&self,
|
||||
block_root: &Hash256,
|
||||
parent_payload_status: PayloadStatus,
|
||||
current_slot: Slot,
|
||||
) -> Result<bool, String> {
|
||||
let block_index = self
|
||||
.proto_array
|
||||
@@ -1033,7 +990,7 @@ impl ProtoArrayForkChoice {
|
||||
payload_status: parent_payload_status,
|
||||
};
|
||||
self.proto_array
|
||||
.should_build_on_full::<E>(&fc_node, proto_node)
|
||||
.should_build_on_full::<E>(&fc_node, proto_node, current_slot)
|
||||
.map_err(|e| format!("{e:?}"))
|
||||
}
|
||||
|
||||
@@ -1445,6 +1402,7 @@ mod test_compute_deltas {
|
||||
execution_payload_parent_hash: None,
|
||||
execution_payload_block_hash: None,
|
||||
proposer_index: Some(0),
|
||||
payload_received: false,
|
||||
},
|
||||
genesis_slot + 1,
|
||||
&spec,
|
||||
@@ -1473,6 +1431,7 @@ mod test_compute_deltas {
|
||||
execution_payload_parent_hash: None,
|
||||
execution_payload_block_hash: None,
|
||||
proposer_index: Some(0),
|
||||
payload_received: false,
|
||||
},
|
||||
genesis_slot + 1,
|
||||
&spec,
|
||||
@@ -1609,6 +1568,7 @@ mod test_compute_deltas {
|
||||
execution_payload_parent_hash: None,
|
||||
execution_payload_block_hash: None,
|
||||
proposer_index: Some(0),
|
||||
payload_received: false,
|
||||
},
|
||||
Slot::from(block.slot),
|
||||
&spec,
|
||||
|
||||
Reference in New Issue
Block a user