mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-20 14:28:37 +00:00
O(n) children index, fix load_parent for gloas blocks
- Build parent->children index once per find_head call, replacing O(n) scans in filter_block_tree and get_node_children with O(1) lookups. - Skip zero block_hash in is_parent_block_full check — default/zero hashes don't indicate a real payload relationship. - Fall back to block state_root for genesis when envelope not stored. - Store execution payload envelope in EF test harness during on_execution_payload step.
This commit is contained in:
@@ -1964,15 +1964,27 @@ fn load_parent<T: BeaconChainTypes, B: AsBlock<T::EthSpec>>(
|
||||
if block.as_block().fork_name_unchecked().gloas_enabled()
|
||||
&& let Ok(parent_bid_block_hash) = parent_block.payload_bid_block_hash()
|
||||
{
|
||||
if block.as_block().is_parent_block_full(parent_bid_block_hash) {
|
||||
if !parent_bid_block_hash.into_root().is_zero()
|
||||
&& block.as_block().is_parent_block_full(parent_bid_block_hash)
|
||||
{
|
||||
// TODO(gloas): loading the envelope here is not very efficient
|
||||
// TODO(gloas): check parent payload existence prior to this point?
|
||||
let envelope = chain.store.get_payload_envelope(&root)?.ok_or_else(|| {
|
||||
BeaconChainError::DBInconsistent(format!(
|
||||
"Missing envelope for parent block {root:?}",
|
||||
))
|
||||
})?;
|
||||
(StatePayloadStatus::Full, envelope.message.state_root)
|
||||
let envelope = chain.store.get_payload_envelope(&root)?;
|
||||
let state_root = if let Some(env) = envelope {
|
||||
env.message.state_root
|
||||
} else {
|
||||
// The envelope may not be stored yet for the genesis/anchor
|
||||
// block. Fall back to the block's state_root which is the
|
||||
// post-payload state for the anchor per get_forkchoice_store.
|
||||
if parent_block.slot() == chain.spec.genesis_slot {
|
||||
parent_block.state_root()
|
||||
} else {
|
||||
return Err(BeaconChainError::DBInconsistent(format!(
|
||||
"Missing envelope for parent block {root:?}",
|
||||
))
|
||||
.into());
|
||||
}
|
||||
};
|
||||
(StatePayloadStatus::Full, state_root)
|
||||
} else {
|
||||
(StatePayloadStatus::Pending, parent_block.state_root())
|
||||
}
|
||||
|
||||
@@ -1188,6 +1188,26 @@ impl ProtoArray {
|
||||
Ok((best_fc_node.root, best_fc_node.payload_status))
|
||||
}
|
||||
|
||||
/// Build a parent->children index. Invalid nodes are excluded
|
||||
/// (they aren't in store.blocks in the spec).
|
||||
fn build_children_index(&self) -> Vec<Vec<usize>> {
|
||||
let mut children = vec![vec![]; self.nodes.len()];
|
||||
for (i, node) in self.nodes.iter().enumerate() {
|
||||
if node
|
||||
.execution_status()
|
||||
.is_ok_and(|status| status.is_invalid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(parent) = node.parent() {
|
||||
if parent < children.len() {
|
||||
children[parent].push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
children
|
||||
}
|
||||
|
||||
/// Spec: `get_filtered_block_tree`.
|
||||
///
|
||||
/// Returns the set of node indices on viable branches — those with at least
|
||||
@@ -1198,6 +1218,7 @@ impl ProtoArray {
|
||||
current_slot: Slot,
|
||||
best_justified_checkpoint: Checkpoint,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
children_index: &[Vec<usize>],
|
||||
) -> HashSet<usize> {
|
||||
let mut viable = HashSet::new();
|
||||
self.filter_block_tree::<E>(
|
||||
@@ -1205,6 +1226,7 @@ impl ProtoArray {
|
||||
current_slot,
|
||||
best_justified_checkpoint,
|
||||
best_finalized_checkpoint,
|
||||
children_index,
|
||||
&mut viable,
|
||||
);
|
||||
viable
|
||||
@@ -1217,25 +1239,17 @@ impl ProtoArray {
|
||||
current_slot: Slot,
|
||||
best_justified_checkpoint: Checkpoint,
|
||||
best_finalized_checkpoint: Checkpoint,
|
||||
children_index: &[Vec<usize>],
|
||||
viable: &mut HashSet<usize>,
|
||||
) -> bool {
|
||||
let Some(node) = self.nodes.get(node_index) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Skip invalid children — they aren't in store.blocks in the spec.
|
||||
let children: Vec<usize> = self
|
||||
.nodes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, child)| {
|
||||
child.parent() == Some(node_index)
|
||||
&& !child
|
||||
.execution_status()
|
||||
.is_ok_and(|status| status.is_invalid())
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
let children = children_index
|
||||
.get(node_index)
|
||||
.map(|c| c.as_slice())
|
||||
.unwrap_or(&[]);
|
||||
|
||||
if !children.is_empty() {
|
||||
// Evaluate ALL children (no short-circuit) to mark all viable branches.
|
||||
@@ -1247,6 +1261,7 @@ impl ProtoArray {
|
||||
current_slot,
|
||||
best_justified_checkpoint,
|
||||
best_finalized_checkpoint,
|
||||
children_index,
|
||||
viable,
|
||||
)
|
||||
})
|
||||
@@ -1291,12 +1306,16 @@ impl ProtoArray {
|
||||
payload_status: PayloadStatus::Pending,
|
||||
};
|
||||
|
||||
// Build parent->children index once for O(1) lookups.
|
||||
let children_index = self.build_children_index();
|
||||
|
||||
// Spec: `get_filtered_block_tree`.
|
||||
let viable_nodes = self.get_filtered_block_tree::<E>(
|
||||
start_index,
|
||||
current_slot,
|
||||
best_justified_checkpoint,
|
||||
best_finalized_checkpoint,
|
||||
&children_index,
|
||||
);
|
||||
|
||||
// Compute once rather than per-child per-level.
|
||||
@@ -1305,7 +1324,7 @@ impl ProtoArray {
|
||||
|
||||
loop {
|
||||
let children: Vec<_> = self
|
||||
.get_node_children(&head)?
|
||||
.get_node_children(&head, &children_index)?
|
||||
.into_iter()
|
||||
.filter(|(fc_node, _)| viable_nodes.contains(&fc_node.proto_node_index))
|
||||
.collect();
|
||||
@@ -1468,6 +1487,7 @@ impl ProtoArray {
|
||||
fn get_node_children(
|
||||
&self,
|
||||
node: &IndexedForkChoiceNode,
|
||||
children_index: &[Vec<usize>],
|
||||
) -> Result<Vec<(IndexedForkChoiceNode, ProtoNode)>, Error> {
|
||||
if node.payload_status == PayloadStatus::Pending {
|
||||
let proto_node = self
|
||||
@@ -1481,23 +1501,25 @@ impl ProtoArray {
|
||||
}
|
||||
Ok(children)
|
||||
} else {
|
||||
Ok(self
|
||||
.nodes
|
||||
let child_indices = children_index
|
||||
.get(node.proto_node_index)
|
||||
.map(|c| c.as_slice())
|
||||
.unwrap_or(&[]);
|
||||
Ok(child_indices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, child_node)| {
|
||||
child_node.parent() == Some(node.proto_node_index)
|
||||
&& child_node.get_parent_payload_status() == node.payload_status
|
||||
})
|
||||
.map(|(child_index, child_node)| {
|
||||
(
|
||||
.filter_map(|&child_index| {
|
||||
let child_node = self.nodes.get(child_index)?;
|
||||
if child_node.get_parent_payload_status() != node.payload_status {
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
IndexedForkChoiceNode {
|
||||
root: child_node.root(),
|
||||
proto_node_index: child_index,
|
||||
payload_status: PayloadStatus::Pending,
|
||||
},
|
||||
child_node.clone(),
|
||||
)
|
||||
))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -449,8 +449,7 @@ impl<E: EthSpec> Case for ForkChoiceTest<E> {
|
||||
execution_payload,
|
||||
valid,
|
||||
} => {
|
||||
tester
|
||||
.process_execution_payload(execution_payload.beacon_block_root(), *valid)?;
|
||||
tester.process_execution_payload(execution_payload, *valid)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -993,7 +992,27 @@ impl<E: EthSpec> Tester<E> {
|
||||
check_equal("proposer_head", proposer_head, expected_proposer_head)
|
||||
}
|
||||
|
||||
pub fn process_execution_payload(&self, block_root: Hash256, valid: bool) -> Result<(), Error> {
|
||||
pub fn process_execution_payload(
|
||||
&self,
|
||||
signed_envelope: &SignedExecutionPayloadEnvelope<E>,
|
||||
valid: bool,
|
||||
) -> Result<(), Error> {
|
||||
let block_root = signed_envelope.message.beacon_block_root;
|
||||
|
||||
// Store the envelope in the database so that child blocks extending
|
||||
// the FULL path can load the parent's post-payload state.
|
||||
if valid {
|
||||
self.harness
|
||||
.chain
|
||||
.store
|
||||
.put_payload_envelope(&block_root, signed_envelope.clone())
|
||||
.map_err(|e| {
|
||||
Error::InternalError(format!(
|
||||
"Failed to store payload envelope for {block_root:?}: {e:?}",
|
||||
))
|
||||
})?;
|
||||
}
|
||||
|
||||
let result = self
|
||||
.harness
|
||||
.chain
|
||||
|
||||
Reference in New Issue
Block a user