Skip to content

Commit 1a581a7

Browse files
pablodeymoAliemeka
authored andcommitted
Merge branch 'main' into feat/accept-multiple-urls-for-checkpoint-sync-i
2 parents a58d4e3 + 7bb14c3 commit 1a581a7

1 file changed

Lines changed: 136 additions & 8 deletions

File tree

crates/storage/src/store.rs

Lines changed: 136 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,32 @@ impl PayloadBuffer {
261261
.collect()
262262
}
263263

264+
/// Prune payload entries whose attestation target slot is at or below `finalized_slot`.
265+
///
266+
/// Mirrors leanSpec's `prune_stale_attestation_data`: an entry is stale once its
267+
/// target checkpoint is finalized — it can no longer contribute to fork choice and
268+
/// keeping it around only pollutes `existing_proofs_for_data` lookups, occasionally
269+
/// forcing recursive aggregation when plain XMSS aggregation would suffice.
270+
///
271+
/// Returns the number of data_root entries removed.
272+
fn prune(&mut self, finalized_slot: u64) -> usize {
273+
let before = self.data.len();
274+
let total_proofs = &mut self.total_proofs;
275+
self.data.retain(|_root, entry| {
276+
if entry.data.target.slot > finalized_slot {
277+
true
278+
} else {
279+
*total_proofs -= entry.proofs.len();
280+
false
281+
}
282+
});
283+
let pruned = before - self.data.len();
284+
if pruned > 0 {
285+
self.order.retain(|r| self.data.contains_key(r));
286+
}
287+
pruned
288+
}
289+
264290
/// Extract per-validator latest attestations from proofs' participation bits.
265291
///
266292
/// Iterates entries in insertion order (via `self.order`) so that, when two
@@ -400,20 +426,20 @@ impl GossipSignatureBuffer {
400426
///
401427
/// Returns the number of data_root entries pruned.
402428
fn prune(&mut self, finalized_slot: u64) -> usize {
403-
let mut pruned_roots: HashSet<H256> = HashSet::new();
404-
self.data.retain(|root, entry| {
429+
let before = self.data.len();
430+
self.data.retain(|_root, entry| {
405431
if entry.data.slot > finalized_slot {
406432
true
407433
} else {
408434
self.total_signatures -= entry.signatures.len();
409-
pruned_roots.insert(*root);
410435
false
411436
}
412437
});
413-
if !pruned_roots.is_empty() {
414-
self.order.retain(|r| !pruned_roots.contains(r));
438+
let pruned = before - self.data.len();
439+
if pruned > 0 {
440+
self.order.retain(|r| self.data.contains_key(r));
415441
}
416-
pruned_roots.len()
442+
pruned
417443
}
418444

419445
/// Returns a snapshot of all gossip signatures grouped by attestation data.
@@ -727,11 +753,12 @@ impl Store {
727753
{
728754
let pruned_chain = self.prune_live_chain(finalized.slot);
729755
let pruned_sigs = self.prune_gossip_signatures(finalized.slot);
756+
let pruned_payloads = self.prune_stale_aggregated_payloads(finalized.slot);
730757

731-
if pruned_chain > 0 || pruned_sigs > 0 {
758+
if pruned_chain > 0 || pruned_sigs > 0 || pruned_payloads > 0 {
732759
info!(
733760
finalized_slot = finalized.slot,
734-
pruned_chain, pruned_sigs, "Pruned finalized data"
761+
pruned_chain, pruned_sigs, pruned_payloads, "Pruned finalized data"
735762
);
736763
}
737764
}
@@ -830,6 +857,18 @@ impl Store {
830857
gossip.prune(finalized_slot)
831858
}
832859

860+
/// Prune aggregated payload buffers (new + known) whose target slot is at or below
861+
/// `finalized_slot`.
862+
///
863+
/// Mirrors leanSpec's `prune_stale_attestation_data` for the two aggregated payload
864+
/// pools (gossip signatures are pruned separately by `prune_gossip_signatures`).
865+
/// Returns the total number of data_root entries removed across both buffers.
866+
pub fn prune_stale_aggregated_payloads(&mut self, finalized_slot: u64) -> usize {
867+
let pruned_new = self.new_payloads.lock().unwrap().prune(finalized_slot);
868+
let pruned_known = self.known_payloads.lock().unwrap().prune(finalized_slot);
869+
pruned_new + pruned_known
870+
}
871+
833872
/// Prune old states beyond the retention window.
834873
///
835874
/// Keeps the most recent `STATES_TO_KEEP` states (by slot), plus any
@@ -2075,6 +2114,95 @@ mod tests {
20752114
assert_eq!(buf.total_proofs, 2);
20762115
}
20772116

2117+
#[test]
2118+
fn payload_buffer_prune_drops_entries_with_finalized_target() {
2119+
let mut buf = PayloadBuffer::new(10);
2120+
let target_a = H256([0xaa; 32]);
2121+
let target_b = H256([0xbb; 32]);
2122+
let target_c = H256([0xcc; 32]);
2123+
2124+
// Three entries at different target slots: 3, 5, 7.
2125+
let data_3 = make_att_data_for_target(3, target_a);
2126+
let data_5 = make_att_data_for_target(5, target_b);
2127+
let data_7 = make_att_data_for_target(7, target_c);
2128+
let root_3 = data_3.hash_tree_root();
2129+
let root_5 = data_5.hash_tree_root();
2130+
let root_7 = data_7.hash_tree_root();
2131+
2132+
buf.push(
2133+
HashedAttestationData::new(data_3),
2134+
make_proof_for_validators(&[0]),
2135+
);
2136+
buf.push(
2137+
HashedAttestationData::new(data_5),
2138+
make_proof_for_validators(&[1, 2]),
2139+
);
2140+
buf.push(
2141+
HashedAttestationData::new(data_7),
2142+
make_proof_for_validators(&[3]),
2143+
);
2144+
assert_eq!(buf.total_proofs, 3);
2145+
2146+
// Finalized slot 5 prunes targets 3 and 5 (≤ 5), keeps target 7.
2147+
let pruned = buf.prune(5);
2148+
assert_eq!(pruned, 2);
2149+
assert!(!buf.data.contains_key(&root_3));
2150+
assert!(!buf.data.contains_key(&root_5));
2151+
assert!(buf.data.contains_key(&root_7));
2152+
assert_eq!(buf.total_proofs, 1);
2153+
assert_eq!(buf.order.len(), 1);
2154+
assert_eq!(buf.order.front(), Some(&root_7));
2155+
}
2156+
2157+
#[test]
2158+
fn payload_buffer_prune_noop_when_nothing_stale() {
2159+
let mut buf = PayloadBuffer::new(10);
2160+
let data = make_att_data_for_target(10, H256([0xaa; 32]));
2161+
buf.push(
2162+
HashedAttestationData::new(data),
2163+
make_proof_for_validators(&[0]),
2164+
);
2165+
2166+
let pruned = buf.prune(5);
2167+
assert_eq!(pruned, 0);
2168+
assert_eq!(buf.total_proofs, 1);
2169+
assert_eq!(buf.order.len(), 1);
2170+
}
2171+
2172+
#[test]
2173+
fn store_prune_stale_aggregated_payloads_clears_both_buffers() {
2174+
let mut store = Store::test_store();
2175+
2176+
let stale = make_att_data_for_target(2, H256([0xaa; 32]));
2177+
let fresh = make_att_data_for_target(10, H256([0xbb; 32]));
2178+
2179+
store.insert_new_aggregated_payload(
2180+
HashedAttestationData::new(stale.clone()),
2181+
make_proof_for_validators(&[0]),
2182+
);
2183+
store.insert_known_aggregated_payload(
2184+
HashedAttestationData::new(stale),
2185+
make_proof_for_validators(&[1]),
2186+
);
2187+
store.insert_new_aggregated_payload(
2188+
HashedAttestationData::new(fresh.clone()),
2189+
make_proof_for_validators(&[2]),
2190+
);
2191+
store.insert_known_aggregated_payload(
2192+
HashedAttestationData::new(fresh),
2193+
make_proof_for_validators(&[3]),
2194+
);
2195+
2196+
assert_eq!(store.new_aggregated_payloads_count(), 2);
2197+
assert_eq!(store.known_aggregated_payloads_count(), 2);
2198+
2199+
// Finalized slot 5: stale (target.slot == 2) is dropped from both buffers.
2200+
let pruned = store.prune_stale_aggregated_payloads(5);
2201+
assert_eq!(pruned, 2);
2202+
assert_eq!(store.new_aggregated_payloads_count(), 1);
2203+
assert_eq!(store.known_aggregated_payloads_count(), 1);
2204+
}
2205+
20782206
/// Build an attestation message at `slot` whose target points at `target_root`,
20792207
/// distinct from the default zero target so two such datas have different roots.
20802208
fn make_att_data_for_target(slot: u64, target_root: H256) -> AttestationData {

0 commit comments

Comments
 (0)