@@ -5,6 +5,7 @@ mod error;
55mod tests;
66
77use std:: {
8+ hash:: Hash ,
89 panic:: AssertUnwindSafe ,
910 sync:: Arc ,
1011 task:: { Context , Poll } ,
@@ -29,7 +30,7 @@ use aggkit_prover_types::vkey_hash::{Sp1VKeyHash, VKeyHash};
2930use agglayer_interop:: types:: {
3031 bincode, GlobalIndexWithLeafHash , ImportedBridgeExitCommitmentValues ,
3132} ;
32- use agglayer_primitives:: { Address , Digest } ;
33+ use agglayer_primitives:: { Address , Digest , U256 } ;
3334use alloy:: eips:: BlockNumberOrTag ;
3435pub use error:: Error ;
3536use eyre:: Context as _;
@@ -121,6 +122,162 @@ pub struct AggchainProofBuilderResponse {
121122 pub public_values : AggchainProofPublicValues ,
122123}
123124
125+ /// Filters out values from a list based on a set of keys to remove, using a key
126+ /// extraction function.
127+ ///
128+ /// This function iterates over `values`, removing up to N occurrences of each
129+ /// value whose key, as determined by `key_fn`, matches a key in
130+ /// `keys_to_remove`, where N is the number of times the key appears in
131+ /// `keys_to_remove`. The removal is performed in order, and only the first N
132+ /// matching values are removed for each key. Remaining values are preserved in
133+ /// their original order.
134+ ///
135+ /// # Arguments
136+ ///
137+ /// * `keys_to_remove` - A slice of keys indicating which values to remove. Each
138+ /// occurrence of a key in this slice will remove one matching value from
139+ /// `values`.
140+ /// * `values` - The slice of values to filter.
141+ /// * `key_fn` - A function that extracts a key from a value for comparison.
142+ ///
143+ /// # Returns
144+ ///
145+ /// Returns a `Result` containing a `Vec<V>` of the filtered values, or an error
146+ /// if an overflow occurs while counting removals.
147+ ///
148+ /// # Example
149+ ///
150+ /// ```
151+ /// use aggchain_proof_builder::filter_values;
152+ ///
153+ /// let keys_to_remove = [1, 2, 2];
154+ /// let values = [1, 2, 2, 3, 4];
155+ /// let filtered = filter_values(&keys_to_remove, &values, |v| *v).unwrap();
156+ /// assert_eq!(filtered, vec![3, 4]);
157+ /// ```
158+ ///
159+ /// # Errors
160+ ///
161+ /// Returns `Error::FilteringValuesOverflow` if the removal count for any key
162+ /// would overflow `usize`.
163+ pub fn filter_values < K , V , KF > (
164+ keys_to_remove : & [ K ] ,
165+ values : & [ V ] ,
166+ mut key_fn : KF ,
167+ ) -> Result < Vec < V > , Error >
168+ where
169+ K : Eq + Hash + Copy ,
170+ V : Clone ,
171+ KF : FnMut ( & V ) -> K ,
172+ {
173+ use std:: collections:: HashMap ;
174+
175+ // Count how many times each key should be removed
176+ let mut removal_map: HashMap < K , usize > = HashMap :: new ( ) ;
177+ for & key in keys_to_remove {
178+ let count = removal_map. entry ( key) . or_insert ( 0 ) ;
179+ * count = count
180+ . checked_add ( 1 )
181+ . ok_or ( Error :: FilteringValuesOverflow ( * count) ) ?;
182+ }
183+
184+ // For each value, if its key is in removal_map and count > 0, skip it and
185+ // decrement count
186+ let mut result = Vec :: new ( ) ;
187+ for value in values {
188+ let key = key_fn ( value) ;
189+ if let Some ( count) = removal_map. get_mut ( & key) {
190+ if * count > 0 {
191+ * count -= 1 ;
192+ continue ;
193+ }
194+ }
195+ result. push ( value. clone ( ) ) ;
196+ }
197+
198+ Ok ( result)
199+ }
200+
201+ /// Filters, sorts, and maps items from an iterator based on a block number
202+ /// range.
203+ ///
204+ /// This function takes an iterator of items, filters them to include only those
205+ /// whose block number (as determined by `block_number_fn`) falls within the
206+ /// specified `range`, sorts the filtered items using their `Ord`
207+ /// implementation, and then maps each item to a new type using the provided
208+ /// `map_fn`.
209+ ///
210+ /// # Type Parameters
211+ /// - `T`: The type of the input items. Must implement `Ord`.
212+ /// - `F`: The mapping function type. Must be a function or closure that takes
213+ /// `T` and returns `U`.
214+ /// - `U`: The type of the output items.
215+ ///
216+ /// # Arguments
217+ /// - `items`: An iterator of items to process.
218+ /// - `range`: The inclusive range of block numbers to filter by.
219+ /// - `block_number_fn`: A function that extracts the block number from an item.
220+ /// - `map_fn`: A function that maps each filtered and sorted item to the
221+ /// desired output type.
222+ ///
223+ /// # Returns
224+ /// An iterator over the mapped items, filtered and sorted as described.
225+ ///
226+ /// # Example
227+ /// ```no_run
228+ /// # use aggchain_proof_builder::filter_sort_map;
229+ /// # struct Item { block_number: u64 }
230+ /// # impl Item {
231+ /// # fn to_output_type(self) -> u64 { self.block_number }
232+ /// # }
233+ /// # impl Ord for Item {
234+ /// # fn cmp(&self, other: &Self) -> std::cmp::Ordering {
235+ /// # self.block_number.cmp(&other.block_number)
236+ /// # }
237+ /// # }
238+ /// # impl PartialOrd for Item {
239+ /// # fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
240+ /// # Some(self.cmp(other))
241+ /// # }
242+ /// # }
243+ /// # impl Eq for Item {}
244+ /// # impl PartialEq for Item {
245+ /// # fn eq(&self, other: &Self) -> bool {
246+ /// # self.block_number == other.block_number
247+ /// # }
248+ /// # }
249+ /// let item1 = Item { block_number: 100 };
250+ /// let item2 = Item { block_number: 150 };
251+ /// let item3 = Item { block_number: 250 };
252+ /// let items = vec![item1, item2, item3];
253+ /// let range = 100..=200;
254+ /// let result: Vec<_> = filter_sort_map(
255+ /// items,
256+ /// &range,
257+ /// |item| item.block_number,
258+ /// |item| item.to_output_type(),
259+ /// )
260+ /// .collect();
261+ /// assert_eq!(result, vec![100, 150]);
262+ /// ```
263+ pub fn filter_sort_map < T , F , U > (
264+ items : impl IntoIterator < Item = T > ,
265+ range : & std:: ops:: RangeInclusive < u64 > ,
266+ block_number_fn : fn ( & T ) -> u64 ,
267+ map_fn : F ,
268+ ) -> impl Iterator < Item = U >
269+ where
270+ F : Fn ( T ) -> U ,
271+ T : Ord ,
272+ {
273+ let mut filtered_items: Vec < _ > = items
274+ . into_iter ( )
275+ . filter ( |item| range. contains ( & block_number_fn ( item) ) )
276+ . collect ( ) ;
277+ filtered_items. sort ( ) ;
278+ filtered_items. into_iter ( ) . map ( map_fn)
279+ }
280+
124281/// This service is responsible for building an Aggchain proof.
125282#[ derive( Clone ) ]
126283#[ allow( unused) ]
@@ -292,29 +449,60 @@ impl<ContractsClient> AggchainProofBuilder<ContractsClient> {
292449 . await
293450 . map_err ( Error :: UnableToFetchTrustedSequencerAddress ) ?;
294451
295- // From the request
296- let inserted_gers: Vec < InsertedGER > = request
452+ // Retrieve all the raw GERs from the aggsender input.
453+ // Removed GERs from this list have invalid merkle proofs.
454+ let raw_inserted_gers: Vec < InsertedGER > = request
297455 . aggchain_proof_inputs
298456 . sorted_inserted_gers ( & new_blocks_range) ;
299457
300- // NOTE: Corresponds to all of them because we do not have removed GERs yet.
301- let inserted_gers_hash_chain = inserted_gers
302- . iter ( )
303- . map ( |inserted_ger| inserted_ger. ger ( ) )
304- . collect ( ) ;
305-
306- // NOTE: Corresponds to all of them because we do not have unset claims yet.
307- let bridge_exits_claimed: Vec < GlobalIndexWithLeafHash > = request
308- . aggchain_proof_inputs
309- . imported_bridge_exits
310- . iter ( )
311- . filter ( |ib| new_blocks_range. contains ( & ib. block_number ) )
312- . map ( |ib| GlobalIndexWithLeafHash {
458+ // All the bridge exits in the new blocks range, also those that are unclaimed.
459+ let all_imported_bridge_exits: Vec < GlobalIndexWithLeafHash > = filter_sort_map (
460+ request. aggchain_proof_inputs . imported_bridge_exits ,
461+ & new_blocks_range,
462+ |ib| ib. block_number ,
463+ |ib| GlobalIndexWithLeafHash {
313464 global_index : ib. global_index . into ( ) ,
314465 bridge_exit_hash : ib. bridge_exit_hash . 0 ,
315- } )
466+ } ,
467+ )
468+ . collect ( ) ;
469+
470+ // Prepare removed GERS for the proof.
471+ let removed_gers: Vec < Digest > = filter_sort_map (
472+ request. aggchain_proof_inputs . removed_gers ,
473+ & new_blocks_range,
474+ |removed_ger| removed_ger. block_number ,
475+ |removed_ger| removed_ger. global_exit_root ,
476+ )
477+ . collect ( ) ;
478+
479+ // Prepare inserted GERS for the proof, filtering out the removed ones.
480+ let inserted_gers = filter_values ( & removed_gers, & raw_inserted_gers, |value| {
481+ value. l1_info_tree_leaf . inner . global_exit_root
482+ } ) ?;
483+
484+ // Prepare the hash chain of all the GERs (inserted and removed) for the
485+ // proof.
486+ let raw_inserted_gers = raw_inserted_gers
487+ . into_iter ( )
488+ . map ( |inserted_ger| inserted_ger. l1_info_tree_leaf . inner . global_exit_root )
316489 . collect ( ) ;
317490
491+ // Prepare unset claims input for the proof.
492+ let unset_claims: Vec < U256 > = filter_sort_map (
493+ request. aggchain_proof_inputs . unclaims ,
494+ & new_blocks_range,
495+ |unclaim| unclaim. block_number ,
496+ |unclaim| unclaim. global_index ,
497+ )
498+ . collect ( ) ;
499+
500+ // Filter out the unset claims from the all imported bridge exits list.
501+ let filtered_claimed_imported_bridge_exits =
502+ filter_values ( & unset_claims, & all_imported_bridge_exits, |value| {
503+ value. global_index
504+ } ) ?;
505+
318506 let l1_info_tree_leaf = request. aggchain_proof_inputs . l1_info_tree_leaf ;
319507 let mut fep_inputs = FepInputs {
320508 l1_head : l1_info_tree_leaf. inner . block_hash ,
@@ -373,15 +561,15 @@ impl<ContractsClient> AggchainProofBuilder<ContractsClient> {
373561 origin_network : network_id,
374562 fep : fep_inputs,
375563 commit_imported_bridge_exits : ImportedBridgeExitCommitmentValues {
376- claims : bridge_exits_claimed . clone ( ) ,
564+ claims : filtered_claimed_imported_bridge_exits ,
377565 }
378566 . commitment ( IMPORTED_BRIDGE_EXIT_COMMITMENT_VERSION ) ,
379567 bridge_witness : BridgeWitness {
380568 inserted_gers,
381- bridge_exits_claimed ,
382- global_indices_unset : vec ! [ ] , // NOTE: no unset yet.
383- raw_inserted_gers : inserted_gers_hash_chain ,
384- removed_gers : vec ! [ ] , // NOTE: no removed GERs yet.
569+ imported_bridge_exits : all_imported_bridge_exits ,
570+ removed_gers ,
571+ raw_inserted_gers,
572+ unset_claims ,
385573 prev_l2_block_sketch,
386574 new_l2_block_sketch,
387575 caller_address : static_call_caller_address,
0 commit comments