diff --git a/consensus/proto_array/src/proto_array.rs b/consensus/proto_array/src/proto_array.rs index f7203c688f..1d98422b1a 100644 --- a/consensus/proto_array/src/proto_array.rs +++ b/consensus/proto_array/src/proto_array.rs @@ -685,7 +685,7 @@ impl ProtoArray { /// Returns `true` if the proposer boost should be kept. Returns `false` if the /// boost should be subtracted (invalidated) because the parent is weak and there /// are no equivocating blocks at the parent's slot. - fn should_apply_proposer_boost( + pub(crate) fn should_apply_proposer_boost( &self, proposer_boost_root: Hash256, justified_balances: &JustifiedBalances, @@ -1328,7 +1328,7 @@ impl ProtoArray { /// Spec: `get_weight`. #[allow(clippy::too_many_arguments)] - fn get_weight( + pub(crate) fn get_weight( &self, fc_node: &IndexedForkChoiceNode, proto_node: &ProtoNode, diff --git a/consensus/proto_array/src/proto_array_fork_choice.rs b/consensus/proto_array/src/proto_array_fork_choice.rs index 4112168455..e54ef1f835 100644 --- a/consensus/proto_array/src/proto_array_fork_choice.rs +++ b/consensus/proto_array/src/proto_array_fork_choice.rs @@ -1089,7 +1089,10 @@ impl ProtoArrayForkChoice { current_slot: Slot, justified_checkpoint: Checkpoint, finalized_checkpoint: Checkpoint, - ) -> Result, String> { + proposer_boost_root: Hash256, + justified_balances: &JustifiedBalances, + spec: &ChainSpec, + ) -> Result, String> { let start_index = self .proto_array .indices @@ -1107,6 +1110,12 @@ impl ProtoArrayForkChoice { justified_checkpoint, finalized_checkpoint, ); + + let apply_proposer_boost = self + .proto_array + .should_apply_proposer_boost::(proposer_boost_root, justified_balances, spec) + .map_err(|e| format!("should_apply_proposer_boost failed: {e:?}"))?; + let mut leaves = Vec::with_capacity(viable.len()); for &i in &viable { let has_viable_child = viable @@ -1120,7 +1129,70 @@ impl ProtoArrayForkChoice { .nodes .get(i) .ok_or_else(|| format!("invalid viable node index {i}"))?; - leaves.push((node.root(), node.weight())); + + let is_gloas = node.payload_received().is_ok(); + if is_gloas { + // Gloas: expand into Empty/Full virtual children. + let empty_fc = IndexedForkChoiceNode { + root: node.root(), + proto_node_index: i, + payload_status: PayloadStatus::Empty, + }; + let empty_weight = self + .proto_array + .get_weight::( + &empty_fc, + node, + apply_proposer_boost, + proposer_boost_root, + current_slot, + justified_balances, + spec, + ) + .map_err(|e| format!("get_weight failed: {e:?}"))?; + leaves.push((node.root(), PayloadStatus::Empty, empty_weight)); + + if node.payload_received().is_ok_and(|r| r) { + let full_fc = IndexedForkChoiceNode { + root: node.root(), + proto_node_index: i, + payload_status: PayloadStatus::Full, + }; + let full_weight = self + .proto_array + .get_weight::( + &full_fc, + node, + apply_proposer_boost, + proposer_boost_root, + current_slot, + justified_balances, + spec, + ) + .map_err(|e| format!("get_weight failed: {e:?}"))?; + leaves.push((node.root(), PayloadStatus::Full, full_weight)); + } + } else { + // Pre-Gloas: use Pending status (no payload split). + let fc_node = IndexedForkChoiceNode { + root: node.root(), + proto_node_index: i, + payload_status: PayloadStatus::Pending, + }; + let weight = self + .proto_array + .get_weight::( + &fc_node, + node, + apply_proposer_boost, + proposer_boost_root, + current_slot, + justified_balances, + spec, + ) + .map_err(|e| format!("get_weight failed: {e:?}"))?; + leaves.push((node.root(), PayloadStatus::Pending, weight)); + } } Ok(leaves) } diff --git a/testing/ef_tests/src/cases/fork_choice.rs b/testing/ef_tests/src/cases/fork_choice.rs index 09c2dc990f..9a0dff0510 100644 --- a/testing/ef_tests/src/cases/fork_choice.rs +++ b/testing/ef_tests/src/cases/fork_choice.rs @@ -1,7 +1,8 @@ use super::*; use crate::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file}; use ::fork_choice::{ - AttestationFromBlock, ForkChoiceStore, PayloadVerificationStatus, ProposerHeadError, + AttestationFromBlock, ForkChoiceStore, PayloadStatus as FcPayloadStatus, + PayloadVerificationStatus, ProposerHeadError, }; use beacon_chain::beacon_proposer_cache::compute_proposer_duties_from_head; use beacon_chain::blob_verification::GossipBlobError; @@ -85,10 +86,10 @@ pub struct Checks { } #[derive(Debug, Clone, Deserialize)] -#[serde(deny_unknown_fields)] pub struct RootAndWeight { pub root: Hash256, pub weight: u64, + pub payload_status: Option, } #[derive(Debug, Clone, Deserialize)] @@ -624,10 +625,7 @@ impl Tester { .slot_clock .set_current_time(Duration::from_secs(tick)); - // Compute the slot time manually to ensure the slot clock is correct. let slot = self.tick_to_slot(tick).unwrap(); - assert_eq!(slot, self.harness.chain.slot().unwrap()); - self.harness .chain .canonical_head @@ -920,11 +918,15 @@ impl Tester { signature: AggregateSignature::from(&message.signature), }; + let current_slot = self.harness.chain.slot().map_err(|e| { + Error::InternalError(format!("reading current slot failed with {:?}", e)) + })?; + self.harness .chain .canonical_head .fork_choice_write_lock() - .on_payload_attestation(slot, &indexed, AttestationFromBlock::False, &ptc.0) + .on_payload_attestation(current_slot, &indexed, AttestationFromBlock::False, &ptc.0) .map_err(|e| { Error::InternalError(format!("payload attestation import failed with {:?}", e)) }) @@ -1220,6 +1222,8 @@ impl Tester { let justified = fork_choice.justified_checkpoint(); let finalized = fork_choice.finalized_checkpoint(); let current_slot = fork_choice.fc_store().get_current_slot(); + let proposer_boost_root = fork_choice.proposer_boost_root(); + let justified_balances = fork_choice.fc_store().justified_balances().clone(); let actual = fork_choice .proto_array() .filtered_block_tree_leaves_and_weights::( @@ -1227,6 +1231,9 @@ impl Tester { current_slot, justified, finalized, + proposer_boost_root, + &justified_balances, + &self.spec, ) .map_err(|e| { Error::InternalError(format!( @@ -1235,10 +1242,15 @@ impl Tester { })?; drop(fork_choice); - let mut actual_sorted = actual; + let mut actual_sorted: Vec<(Hash256, u8, u64)> = actual + .into_iter() + .map(|(root, status, weight)| (root, status as u8, weight)) + .collect(); actual_sorted.sort(); - let mut expected_sorted: Vec<(Hash256, u64)> = - expected.iter().map(|x| (x.root, x.weight)).collect(); + let mut expected_sorted: Vec<(Hash256, u8, u64)> = expected + .iter() + .map(|x| (x.root, x.payload_status.unwrap_or(FcPayloadStatus::Pending as u8), x.weight)) + .collect(); expected_sorted.sort(); check_equal( diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index acea0cbdc1..43a666b37c 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -748,7 +748,6 @@ impl Handler for ForkChoiceHandler { pub struct ForkChoiceComplianceHandler { handler_name: String, - only_fork: Option, _phantom: PhantomData, } @@ -756,7 +755,6 @@ impl ForkChoiceComplianceHandler { pub fn new(handler_name: &str) -> Self { Self { handler_name: handler_name.into(), - only_fork: None, _phantom: PhantomData, } }