diff --git a/consensus/proto_array/src/proto_array_fork_choice.rs b/consensus/proto_array/src/proto_array_fork_choice.rs index e54ef1f835..f0cba5c49d 100644 --- a/consensus/proto_array/src/proto_array_fork_choice.rs +++ b/consensus/proto_array/src/proto_array_fork_choice.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use std::{ - collections::{BTreeSet, HashMap}, + collections::{BTreeSet, HashMap, HashSet}, fmt, time::Duration, }; @@ -1110,80 +1110,74 @@ impl ProtoArrayForkChoice { justified_checkpoint, finalized_checkpoint, ); + let viable_indices = viable.iter().copied().collect::>(); 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 - .iter() - .any(|&j| self.proto_array.nodes.get(j).and_then(|n| n.parent()) == Some(i)); - if has_viable_child { - continue; - } - let node = self + let mut leaves = Vec::new(); + let mut stack = vec![IndexedForkChoiceNode { + root: *justified_root, + proto_node_index: start_index, + payload_status: PayloadStatus::Pending, + }]; + + while let Some(fc_node) = stack.pop() { + let proto_node = self .proto_array .nodes - .get(i) - .ok_or_else(|| format!("invalid viable node index {i}"))?; + .get(fc_node.proto_node_index) + .ok_or_else(|| format!("invalid viable node index {}", fc_node.proto_node_index))?; - 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)); + let children = if proto_node.payload_received().is_ok() { + if fc_node.payload_status == PayloadStatus::Pending { + let mut children = vec![fc_node.with_status(PayloadStatus::Empty)]; + if proto_node.payload_received().is_ok_and(|received| received) { + children.push(fc_node.with_status(PayloadStatus::Full)); + } + children + } else { + self.proto_array + .nodes + .iter() + .enumerate() + .filter(|(child_index, child_node)| { + viable_indices.contains(child_index) + && child_node.parent() == Some(fc_node.proto_node_index) + && child_node.get_parent_payload_status() == fc_node.payload_status + }) + .map(|(child_index, child_node)| IndexedForkChoiceNode { + root: child_node.root(), + proto_node_index: child_index, + payload_status: PayloadStatus::Pending, + }) + .collect() } } else { - // Pre-Gloas: use Pending status (no payload split). - let fc_node = IndexedForkChoiceNode { - root: node.root(), - proto_node_index: i, - payload_status: PayloadStatus::Pending, - }; + self.proto_array + .nodes + .iter() + .enumerate() + .filter(|(child_index, child_node)| { + viable_indices.contains(child_index) + && child_node.parent() == Some(fc_node.proto_node_index) + }) + .map(|(child_index, child_node)| IndexedForkChoiceNode { + root: child_node.root(), + proto_node_index: child_index, + payload_status: PayloadStatus::Pending, + }) + .collect() + }; + + if children.is_empty() { let weight = self .proto_array .get_weight::( &fc_node, - node, + proto_node, apply_proposer_boost, proposer_boost_root, current_slot, @@ -1191,9 +1185,12 @@ impl ProtoArrayForkChoice { spec, ) .map_err(|e| format!("get_weight failed: {e:?}"))?; - leaves.push((node.root(), PayloadStatus::Pending, weight)); + leaves.push((fc_node.root, fc_node.payload_status, weight)); + } else { + stack.extend(children); } } + Ok(leaves) }