From 8559bf0e102e8535e3eb776826e38191b1e4dad2 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Thu, 30 Apr 2026 12:42:34 +0200 Subject: [PATCH] update il cache with is timely --- beacon_node/beacon_chain/src/beacon_chain.rs | 12 ++- .../src/block_production/gloas.rs | 2 +- .../beacon_chain/src/execution_payload.rs | 2 +- .../src/inclusion_list_verification.rs | 10 +++ .../tests/inclusion_list_verification.rs | 2 +- .../http_api/src/publish_inclusion_lists.rs | 5 +- .../gossip_methods.rs | 8 +- .../types/src/state/inclusion_list_cache.rs | 78 ++++++++++++------- 8 files changed, 79 insertions(+), 40 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 34d9f980bd..2558236009 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6532,7 +6532,7 @@ impl BeaconChain { let il_txs = self .inclusion_list_cache .read() - .get_inclusion_list_transactions(il_slot) + .get_inclusion_list_transactions(il_slot, false) .unwrap_or_default(); Some( il_txs @@ -7794,11 +7794,15 @@ impl BeaconChain { Ok(()) } - pub fn on_verified_inclusion_list(&self, signed_il: SignedInclusionList) { - info!("Adding verified inclusion list to the cache"); + pub fn on_verified_inclusion_list( + &self, + signed_il: SignedInclusionList, + is_timely: bool, + ) { + info!(is_timely, "Adding verified inclusion list to the cache"); self.inclusion_list_cache .write() - .on_inclusion_list(signed_il); + .on_inclusion_list(signed_il, is_timely); } pub fn inclusion_list_seen(&self, signed_il: &SignedInclusionList) -> bool { diff --git a/beacon_node/beacon_chain/src/block_production/gloas.rs b/beacon_node/beacon_chain/src/block_production/gloas.rs index d3e9c5b364..bc4f9bfd51 100644 --- a/beacon_node/beacon_chain/src/block_production/gloas.rs +++ b/beacon_node/beacon_chain/src/block_production/gloas.rs @@ -1126,7 +1126,7 @@ where let il_txs = chain .inclusion_list_cache .read() - .get_inclusion_list_transactions(il_slot) + .get_inclusion_list_transactions(il_slot, false) .unwrap_or_default(); Some( il_txs diff --git a/beacon_node/beacon_chain/src/execution_payload.rs b/beacon_node/beacon_chain/src/execution_payload.rs index c731d30811..4cd18d5e02 100644 --- a/beacon_node/beacon_chain/src/execution_payload.rs +++ b/beacon_node/beacon_chain/src/execution_payload.rs @@ -110,7 +110,7 @@ impl PayloadNotifier { chain .inclusion_list_cache .read() - .get_inclusion_list_transactions(il_slot) + .get_inclusion_list_transactions(il_slot, true) .unwrap_or(VariableList::new(vec![]).map_err(|_| { BlockError::InternalError("Cant create empty IL".to_string()) })?); diff --git a/beacon_node/beacon_chain/src/inclusion_list_verification.rs b/beacon_node/beacon_chain/src/inclusion_list_verification.rs index 5ca9e6672c..b4fc82e735 100644 --- a/beacon_node/beacon_chain/src/inclusion_list_verification.rs +++ b/beacon_node/beacon_chain/src/inclusion_list_verification.rs @@ -33,6 +33,7 @@ impl From for GossipInclusionListError { pub struct GossipVerifiedInclusionList { pub signed_il: SignedInclusionList, + pub is_timely: bool, } impl GossipVerifiedInclusionList { @@ -123,8 +124,17 @@ impl GossipVerifiedInclusionList { return Err(GossipInclusionListError::PriorInclusionListKnown); } + // Compute timeliness: timely if received before INCLUSION_LIST_DUE_BPS into the slot + // INCLUSION_LIST_DUE_BPS = 6667 basis points = 66.67% of slot duration + let slot_duration_ms = chain.spec.get_slot_duration().as_millis() as u64; + let inclusion_list_due_ms = slot_duration_ms * 6667 / 10000; + let il_delay_ms = + get_slot_delay_ms(timestamp_now(), message_slot, &chain.slot_clock).as_millis() as u64; + let is_timely = il_delay_ms <= inclusion_list_due_ms; + Ok(Self { signed_il: signed_il.clone(), + is_timely, }) } } diff --git a/beacon_node/beacon_chain/tests/inclusion_list_verification.rs b/beacon_node/beacon_chain/tests/inclusion_list_verification.rs index e3fcb790af..49ba074f1e 100644 --- a/beacon_node/beacon_chain/tests/inclusion_list_verification.rs +++ b/beacon_node/beacon_chain/tests/inclusion_list_verification.rs @@ -170,7 +170,7 @@ impl InclusionListGossipTester { self.harness .chain - .on_verified_inclusion_list(self.inclusion_list.clone()); + .on_verified_inclusion_list(self.inclusion_list.clone(), true); self } diff --git a/beacon_node/http_api/src/publish_inclusion_lists.rs b/beacon_node/http_api/src/publish_inclusion_lists.rs index b1bf670551..e62df01e8e 100644 --- a/beacon_node/http_api/src/publish_inclusion_lists.rs +++ b/beacon_node/http_api/src/publish_inclusion_lists.rs @@ -206,7 +206,10 @@ fn verify_and_publish_inclusion_list( ); // Store verified IL in the IL cache - chain.on_verified_inclusion_list(verified_inclusion_list.signed_il); + chain.on_verified_inclusion_list( + verified_inclusion_list.signed_il, + verified_inclusion_list.is_timely, + ); Ok(()) } diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 984c5f1e13..616f300221 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -2643,11 +2643,15 @@ impl NetworkBeaconProcessor { ) { match GossipVerifiedInclusionList::verify(&il, &self.chain) { Ok(gossip_verified_il) => { - debug!("Successfully verified gossip inclusion list"); + debug!( + is_timely = gossip_verified_il.is_timely, + "Successfully verified gossip inclusion list" + ); // Store validated inclusion list in the IL cache. This also catches // equivocating IL's and handles them accordingly. + let is_timely = gossip_verified_il.is_timely; self.chain - .on_verified_inclusion_list(gossip_verified_il.signed_il); + .on_verified_inclusion_list(gossip_verified_il.signed_il, is_timely); } Err(err) => match err { GossipInclusionListError::InvalidSlot { .. } diff --git a/consensus/types/src/state/inclusion_list_cache.rs b/consensus/types/src/state/inclusion_list_cache.rs index ccc5eceda3..1bb3ffa5a6 100644 --- a/consensus/types/src/state/inclusion_list_cache.rs +++ b/consensus/types/src/state/inclusion_list_cache.rs @@ -15,7 +15,12 @@ struct Inner { pub inclusion_lists: HashSet>, pub inclusion_lists_seen: HashSet, pub inclusion_list_equivocators: HashSet, + pub inclusion_list_timeliness: HashMap, pub inclusion_list_transactions: HashSet>, + pub timely_transactions: HashSet>, + /// Track which transactions belong to which validator so we can remove them on equivocation. + pub validator_transactions: + HashMap>>, } impl InclusionListCache { @@ -34,75 +39,88 @@ impl InclusionListCache { .contains(&inclusion_list.message.validator_index) } - pub fn on_inclusion_list(&mut self, inclusion_list: SignedInclusionList) { + pub fn on_inclusion_list( + &mut self, + inclusion_list: SignedInclusionList, + is_timely: bool, + ) { let slot = inclusion_list.message.slot; + let validator_index = inclusion_list.message.validator_index; let inner = self.inner_map.entry(slot).or_default(); - if inner - .inclusion_list_equivocators - .contains(&inclusion_list.message.validator_index) - { + if inner.inclusion_list_equivocators.contains(&validator_index) { info!( ?slot, - inclusion_list.message.validator_index, + validator_index, "This validator was flagged for an equivocating inclusion list", ); return; } // Skip inserting into the cache if we've already seen an identical IL - if inner - .inclusion_lists_seen - .contains(&inclusion_list.message.validator_index) + if inner.inclusion_lists_seen.contains(&validator_index) && inner.inclusion_lists.contains(&inclusion_list) { info!("Already seen identical inclusion list from this validator"); return; } - if inner - .inclusion_lists_seen - .contains(&inclusion_list.message.validator_index) + if inner.inclusion_lists_seen.contains(&validator_index) && !inner.inclusion_lists.contains(&inclusion_list) { info!( ?slot, - inclusion_list.message.validator_index, "Equivocating inclusion list", + validator_index, "Equivocating inclusion list", ); - inner - .inclusion_list_equivocators - .insert(inclusion_list.message.validator_index); + inner.inclusion_list_equivocators.insert(validator_index); + + // Remove equivocator's transactions per spec + if let Some(txs) = inner.validator_transactions.remove(&validator_index) { + for tx in &txs { + inner.inclusion_list_transactions.remove(tx); + inner.timely_transactions.remove(tx); + } + } + inner.inclusion_list_timeliness.remove(&validator_index); return; } - for transaction in &inclusion_list.message.transactions { - inner - .inclusion_list_transactions - .insert(transaction.clone()); + let txs: Vec<_> = inclusion_list.message.transactions.iter().cloned().collect(); + for tx in &txs { + inner.inclusion_list_transactions.insert(tx.clone()); + if is_timely { + inner.timely_transactions.insert(tx.clone()); + } } - inner - .inclusion_lists_seen - .insert(inclusion_list.message.validator_index); + inner.validator_transactions.insert(validator_index, txs); + inner.inclusion_list_timeliness.insert(validator_index, is_timely); + inner.inclusion_lists_seen.insert(validator_index); inner.inclusion_lists.insert(inclusion_list); info!( ?slot, tx_count = inner.inclusion_list_transactions.len(), + timely_tx_count = inner.timely_transactions.len(), "Successfully added inclusion list transactions to the cache", ); } - pub fn get_inclusion_list_transactions(&self, slot: Slot) -> Option> { + pub fn get_inclusion_list_transactions( + &self, + slot: Slot, + only_timely: bool, + ) -> Option> { let Some(inner) = self.inner_map.get(&slot) else { return None; }; - let il = inner - .inclusion_list_transactions - .iter() - .cloned() - .collect::>(); - // TODO(heze) should return an error instead of None? + let txs = if only_timely { + &inner.timely_transactions + } else { + &inner.inclusion_list_transactions + }; + + let il: Vec<_> = txs.iter().cloned().collect(); il.try_into().ok() } }