diff --git a/crates/blockchain/src/aggregation.rs b/crates/blockchain/src/aggregation.rs index 8fe3e2d2..b9f35dd8 100644 --- a/crates/blockchain/src/aggregation.rs +++ b/crates/blockchain/src/aggregation.rs @@ -321,12 +321,18 @@ pub fn finalize_aggregation_session(store: &Store) { metrics::update_gossip_signatures(store.gossip_signatures_count()); } +/// Maximum number of existing proofs reused as children in a single +/// aggregation job. Recursive aggregation is costly, so we limit the +/// number of children to avoid unbounded aggregation times. +const MAX_AGGREGATION_CHILDREN: usize = 2; + /// Greedy set-cover selection of proofs to maximize validator coverage. /// /// Processes proof sets in priority order (new before known). Within each set, -/// repeatedly picks the proof covering the most uncovered validators until -/// no proof adds new coverage. This keeps the number of children minimal -/// while maximizing the validators we can skip re-aggregating from scratch. +/// repeatedly picks the proof covering the most uncovered validators until no +/// proof adds new coverage. +/// +/// Caps the number of proofs selected at [`MAX_AGGREGATION_CHILDREN`]. fn select_proofs_greedily( new_proofs: &[TypeOneMultiSignature], known_proofs: &[TypeOneMultiSignature], @@ -337,7 +343,7 @@ fn select_proofs_greedily( for proof_set in [new_proofs, known_proofs] { let mut remaining: Vec<&TypeOneMultiSignature> = proof_set.iter().collect(); - while !remaining.is_empty() { + while selected.len() < MAX_AGGREGATION_CHILDREN && !remaining.is_empty() { let best_idx = remaining .iter() .enumerate() @@ -361,6 +367,10 @@ fn select_proofs_greedily( selected.push(remaining.swap_remove(best_idx).clone()); covered.extend(new_coverage); } + + if selected.len() >= MAX_AGGREGATION_CHILDREN { + break; + } } (selected, covered)