diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs index 1fcf0c6574..29f8f610d7 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/import.rs @@ -266,6 +266,30 @@ impl BeaconChain { .on_valid_payload_envelope_received(block_root) .map_err(|e| BlockError::InternalError(format!("{e:?}")))?; + // Check if the envelope's payload satisfies the inclusion list for this slot. + // This is used by fork choice's `should_extend_payload` tiebreaker to decide + // whether Full or Empty wins when weights are equal. + let il_slot = signed_envelope.message().slot(); + let il_satisfied = { + let il_cache = self.inclusion_list_cache.read(); + if let Some(il_transactions) = + il_cache.get_inclusion_list_transactions(il_slot, false) + { + let envelope_message = signed_envelope.message(); + let payload = envelope_message.payload(); + let payload_transactions = payload.transactions(); + let payload_tx_set: std::collections::HashSet<_> = + payload_transactions.iter().collect(); + il_transactions.iter().all(|tx| payload_tx_set.contains(tx)) + } else { + // No inclusion list for this slot — considered satisfied + true + } + }; + + fork_choice + .record_payload_inclusion_list_satisfaction(block_root, il_satisfied); + // TODO(gloas) emit SSE event if the payload became the new head payload // It is important NOT to return errors here before the database commit, because the envelope diff --git a/consensus/proto_array/src/proto_array.rs b/consensus/proto_array/src/proto_array.rs index 4dc1232bc8..605403e177 100644 --- a/consensus/proto_array/src/proto_array.rs +++ b/consensus/proto_array/src/proto_array.rs @@ -1509,17 +1509,28 @@ impl ProtoArray { proto_node: &ProtoNode, proposer_boost_root: Hash256, ) -> Result { - // Per spec: is_payload_inclusion_list_satisfied returns false unless the - // root is in the map AND the value is true. - if !self - .payload_inclusion_list_satisfaction - .get(&fc_node.root) - .copied() - .unwrap_or(false) + // Per spec (Gloas): if not is_payload_verified(store, root): return False + // In our implementation, payload_received == True means the envelope was received. + if !proto_node + .payload_received() + .map_err(|_| Error::InvalidNodeVariant { block_root: fc_node.root })? { return Ok(false); } + // Per spec (Heze/FOCIL): is_payload_inclusion_list_satisfied check. + // Only applies when the IL satisfaction map has entries (i.e., heze is active). + // For gloas-only slots where no IL exists, the map won't contain the root, + // but we should NOT gate on this — only check if the root IS in the map. + if let Some(&satisfied) = self + .payload_inclusion_list_satisfaction + .get(&fc_node.root) + { + if !satisfied { + 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() {