Repository: hyli-org/hyli
Pull request #1201: :sparkles: Mempool Sync submodule
Author: login: hhalex
Created: 2025-05-15T17:45:30Z | Merged: 2025-05-19T08:22:13Z



Diff:
diff --git a/src/mempool.rs b/src/mempool.rs
index 5480f17f4..7b936becb 100644
--- a/src/mempool.rs
+++ b/src/mempool.rs
@@ -19,12 +19,11 @@ use api::RestApiMessage;
 use block_construction::BlockUnderConstruction;
 use borsh::{BorshDeserialize, BorshSerialize};
 use client_sdk::tcp_client::TcpServerMessage;
-use futures::StreamExt;
 use hyle_contract_sdk::{ContractName, ProgramId, Verifier};
 use hyle_crypto::SharedBlstCrypto;
 use hyle_modules::{
     bus::SharedMessageBus, log_error, log_warn, module_bus_client, module_handle_messages,
-    modules::Module,
+    modules::Module, utils::static_type_map::Pick,
 };
 use hyle_net::ordered_join_set::OrderedJoinSet;
 use metrics::MempoolMetrics;
@@ -39,6 +38,7 @@ use std::{
     time::Duration,
 };
 use storage::{LaneEntryMetadata, Storage};
+use sync_request_reply::{MempoolSync, SyncRequest};
 use tokio::task::JoinSet;
 use verify_tx::DataProposalVerdict;
 // Pick one of the two implementations
@@ -54,6 +54,7 @@ pub mod own_lane;
 pub mod storage;
 pub mod storage_fjall;
 pub mod storage_memory;
+pub mod sync_request_reply;
 pub mod verifiers;
 pub mod verify_tx;
 
@@ -337,6 +338,8 @@ impl Mempool {
         new_dp_timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
         disseminate_timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
 
+        let sync_request_sender = self.start_mempool_sync();
+
         // TODO: Recompute optimistic node_state for contract registrations.
         module_handle_messages! {
             on_bus self.bus,
@@ -345,7 +348,7 @@ impl Mempool {
                 self.processing_dps.is_empty() && self.processing_txs.is_empty() && self.own_data_proposal_in_preparation.is_empty()
             },
             listen<MsgWithHeader<MempoolNetMessage>> cmd => {
-                let _ = log_error!(self.handle_net_message(cmd).await, "Handling MempoolNetMessage in Mempool");
+                let _ = log_error!(self.handle_net_message(cmd, &sync_request_sender).await, "Handling MempoolNetMessage in Mempool");
             }
             listen<RestApiMessage> cmd => {
                 let _ = log_error!(self.handle_api_message(cmd), "Handling API Message in Mempool");
@@ -422,6 +425,26 @@ impl Mempool {
         Ok(())
     }
 
+    pub fn start_mempool_sync(&self) -> tokio::sync::mpsc::Sender<SyncRequest> {
+        let (sync_request_sender, sync_request_receiver) =
+            tokio::sync::mpsc::channel::<SyncRequest>(30);
+        let net_sender =
+            Pick::<tokio::sync::broadcast::Sender<OutboundMessage>>::get(&self.bus).clone();
+
+        let mut mempool_sync = MempoolSync::create(
+            self.own_lane_id().clone(),
+            self.lanes.new_handle(),
+            self.crypto.clone(),
+            self.metrics.clone(),
+            net_sender,
+            sync_request_receiver,
+        );
+
+        tokio::spawn(async move { mempool_sync.start().await });
+
+        sync_request_sender
+    }
+
     fn handle_contract_registration(&mut self, effect: RegisterContractEffect) {
         #[allow(clippy::expect_used, reason = "not held across await")]
         let mut known_contracts = self.known_contracts.write().expect("logic issue");
@@ -515,7 +538,11 @@ impl Mempool {
         }
     }
 
-    async fn handle_net_message(&mut self, msg: MsgWithHeader<MempoolNetMessage>) -> Result<()> {
+    async fn handle_net_message(
+        &mut self,
+        msg: MsgWithHeader<MempoolNetMessage>,
+        sync_request_sender: &tokio::sync::mpsc::Sender<SyncRequest>,
+    ) -> Result<()> {
         let validator = &msg.header.signature.validator;
         // TODO: adapt can_rejoin test to emit a stake tx before turning on the joining node
         // if !self.validators.contains(validator) {
@@ -539,12 +566,32 @@ impl Mempool {
                 self.on_poda_update(&lane_id, &data_proposal_hash, signatures)?
             }
             MempoolNetMessage::SyncRequest(from_data_proposal_hash, to_data_proposal_hash) => {
-                self.on_sync_request(
-                    validator,
-                    from_data_proposal_hash.as_ref(),
-                    to_data_proposal_hash.as_ref(),
-                )
-                .await?;
+                info!(
+                    "{} SyncRequest received from validator {validator} for last_data_proposal_hash {:?}",
+                    &self.own_lane_id(),
+                    to_data_proposal_hash
+                );
+
+                // Redirect to chan
+
+                let Some(to) = to_data_proposal_hash.or(self
+                    .lanes
+                    .lanes_tip
+                    .get(&self.own_lane_id())
+                    .map(|lane_id| lane_id.0.clone()))
+                else {
+                    info!("Nothing to do for this SyncRequest");
+                    return Ok(());
+                };
+
+                sync_request_sender
+                    .send(SyncRequest {
+                        from: from_data_proposal_hash,
+                        to,
+                        validator: validator.clone(),
+                    })
+                    .await
+                    .context("Sending SyncRequest to Mempool submodule")?;
             }
             MempoolNetMessage::SyncReply(metadata, data_proposal) => {
                 self.on_sync_reply(validator, metadata, data_proposal)
@@ -562,6 +609,12 @@ impl Mempool {
     ) -> Result<()> {
         trace!("SyncReply from validator {sender_validator}");
 
+        // TODO: Introduce lane ids in sync reply
+        self.metrics.sync_reply_receive(
+            &LaneId(sender_validator.clone()),
+            self.crypto.validator_pubkey(),
+        );
+
         // TODO: this isn't necessarily the case - another validator could have sent us data for this lane.
         let lane_id = &LaneId(sender_validator.clone());
         let lane_operator = self.get_lane_operator(lane_id);
@@ -614,46 +667,6 @@ impl Mempool {
         Ok(())
     }
 
-    async fn on_sync_request(
-        &mut self,
-        validator: &ValidatorPublicKey,
-        from_data_proposal_hash: Option<&DataProposalHash>,
-        to_data_proposal_hash: Option<&DataProposalHash>,
-    ) -> anyhow::Result<()> {
-        info!(
-            "{} SyncRequest received from validator {validator} for last_data_proposal_hash {:?}",
-            &self.own_lane_id(),
-            to_data_proposal_hash
-        );
-
-        let own_id = self.own_lane_id();
-        let lanes_clone = self.lanes.clone();
-
-        let mut missing_lane_entries = Box::pin(lanes_clone.get_entries_between_hashes(
-            &own_id,
-            from_data_proposal_hash.cloned(),
-            to_data_proposal_hash.cloned(),
-        ));
-
-        while let Some(res) = missing_lane_entries.next().await {
-            match res {
-                Err(e) => info!(
-                    "Can't send sync reply as there are no missing data proposals found between {:?} and {:?} for {}: {}",
-                    to_data_proposal_hash, from_data_proposal_hash, self.own_lane_id(), e
-                ),
-                Ok((metadata, data_proposal)) => {
-                    debug!(
-                        "One missing data proposal on {} is {:?}",
-                        validator, metadata
-                    );
-                    self.send_sync_reply(validator, metadata, data_proposal)?;
-                }
-            }
-        }
-
-        Ok(())
-    }
-
     fn on_poda_update(
         &mut self,
         lane_id: &LaneId,
@@ -715,7 +728,7 @@ impl Mempool {
             validator, from_data_proposal_hash, to_data_proposal_hash
         );
         self.metrics
-            .add_sync_request(self.crypto.validator_pubkey(), validator);
+            .sync_request_send(lane_id, self.crypto.validator_pubkey());
         self.send_net_message(
             validator.clone(),
             MempoolNetMessage::SyncRequest(
@@ -726,22 +739,6 @@ impl Mempool {
         Ok(())
     }
 
-    fn send_sync_reply(
-        &mut self,
-        validator: &ValidatorPublicKey,
-        metadata: LaneEntryMetadata,
-        data_proposal: DataProposal,
-    ) -> Result<()> {
-        // cleanup previously tracked sent sync request
-        self.metrics
-            .add_sync_reply(self.crypto.validator_pubkey(), validator, 1);
-        self.send_net_message(
-            validator.clone(),
-            MempoolNetMessage::SyncReply(metadata, data_proposal),
-        )?;
-        Ok(())
-    }
-
     #[inline(always)]
     fn broadcast_net_message(&mut self, net_message: MempoolNetMessage) -> Result<()> {
         let enum_variant_name: &'static str = (&net_message).into();
@@ -823,6 +820,7 @@ pub mod test {
     pub struct MempoolTestCtx {
         pub name: String,
         pub out_receiver: Receiver<OutboundMessage>,
+        pub mempool_sync_request_sender: tokio::sync::mpsc::Sender<SyncRequest>,
         pub mempool_event_receiver: Receiver<MempoolBlockEvent>,
         pub mempool_status_event_receiver: Receiver<MempoolStatusEvent>,
         pub mempool: Mempool,
@@ -854,12 +852,13 @@ pub mod test {
             let mempool_event_receiver = get_receiver::<MempoolBlockEvent>(&shared_bus).await;
             let mempool_status_event_receiver =
                 get_receiver::<MempoolStatusEvent>(&shared_bus).await;
-
             let mempool = Self::build_mempool(&shared_bus, crypto).await;
+            let mempool_sync_request_sender = mempool.start_mempool_sync();
 
             MempoolTestCtx {
                 name: name.to_string(),
                 out_receiver,
+                mempool_sync_request_sender,
                 mempool_event_receiver,
                 mempool_status_event_receiver,
                 mempool,
@@ -931,9 +930,13 @@ pub mod test {
                 || self.mempool.disseminate_data_proposals(None).await?)
         }
 
-        pub async fn handle_poda_update(&mut self, net_message: MsgWithHeader<MempoolNetMessage>) {
+        pub async fn handle_poda_update(
+            &mut self,
+            net_message: MsgWithHeader<MempoolNetMessage>,
+            sync_request_sender: &tokio::sync::mpsc::Sender<SyncRequest>,
+        ) {
             self.mempool
-                .handle_net_message(net_message)
+                .handle_net_message(net_message, sync_request_sender)
                 .await
                 .expect("fail to handle net message");
         }
@@ -953,38 +956,6 @@ pub mod test {
                 .expect("fail to handle event");
         }
 
-        pub fn assert_broadcast(
-            &mut self,
-            description: &str,
-        ) -> Pin<Box<dyn Future<Output = MsgWithHeader<MempoolNetMessage>>>> {
-            #[allow(clippy::expect_fun_call)]
-            let rec = self
-                .out_receiver
-                .try_recv()
-                .expect(format!("{description}: No message broadcasted").as_str());
-
-            match rec {
-                OutboundMessage::BroadcastMessage(net_msg) => {
-                    if let NetMessage::MempoolMessage(msg) = net_msg {
-                        Box::pin(async move { msg })
-                    } else {
-                        println!(
-                            "{description}: Mempool OutboundMessage message is missing, found {}",
-                            net_msg
-                        );
-                        self.assert_broadcast(description)
-                    }
-                }
-                _ => {
-                    println!(
-                        "{description}: Broadcast OutboundMessage message is missing, found {:?}",
-                        rec
-                    );
-                    self.assert_broadcast(description)
-                }
-            }
-        }
-
         #[track_caller]
         pub fn assert_broadcast_only_for(
             &mut self,
@@ -1017,61 +988,104 @@ pub mod test {
                 }
             }
         }
-
         pub fn assert_send(
             &mut self,
             to: &ValidatorPublicKey,
             description: &str,
-        ) -> Pin<Box<dyn Future<Output = MsgWithHeader<MempoolNetMessage>>>> {
-            #[allow(clippy::expect_fun_call)]
-            let rec = self
-                .out_receiver
-                .try_recv()
-                .expect(format!("{description}: No message broadcasted").as_str());
-
-            match rec {
-                OutboundMessage::SendMessage { validator_id, msg } => {
-                    if let NetMessage::MempoolMessage(msg) = msg {
-                        if &validator_id != to {
-                            panic!(
+        ) -> Pin<Box<dyn Future<Output = MsgWithHeader<MempoolNetMessage>> + '_>> {
+            let to = to.clone();
+            let description = description.to_string().clone();
+            Box::pin(async move {
+                #[allow(clippy::expect_fun_call)]
+                let rec =
+                    tokio::time::timeout(Duration::from_millis(1000), self.out_receiver.recv())
+                        .await
+                        .expect(format!("{description}: No message broadcasted").as_str())
+                        .expect(format!("{description}: No message broadcasted").as_str());
+
+                match rec {
+                    OutboundMessage::SendMessage { validator_id, msg } => {
+                        if let NetMessage::MempoolMessage(msg) = msg {
+                            if validator_id != to {
+                                panic!(
                                 "{description}: Send message was sent to {validator_id} instead of {}",
                                 to
                             );
-                        }
+                            }
 
-                        Box::pin(async move { msg })
-                    } else {
-                        tracing::warn!("{description}: skipping {:?}", msg);
-                        self.assert_send(to, description)
+                            msg
+                        } else {
+                            tracing::warn!("{description}: skipping {:?}", msg);
+                            self.assert_send(&to, description.as_str()).await
+                        }
                     }
-                }
-                OutboundMessage::BroadcastMessage(NetMessage::ConsensusMessage(e)) => {
-                    tracing::warn!("{description}: skipping broadcast message {:?}", e);
-                    self.assert_send(to, description)
-                }
-                OutboundMessage::BroadcastMessage(els) => {
-                    panic!(
-                        "{description}: received broadcast message instead of send {:?}",
+                    OutboundMessage::BroadcastMessage(NetMessage::ConsensusMessage(e)) => {
+                        tracing::warn!("{description}: skipping broadcast message {:?}", e);
+                        self.assert_send(&to, description.as_str()).await
+                    }
+                    OutboundMessage::BroadcastMessage(els) => {
+                        panic!(
+                            "{description}: received broadcast message instead of send {:?}",
+                            els
+                        );
+                    }
+                    OutboundMessage::BroadcastMessageOnlyFor(
+                        _,
+                        NetMessage::ConsensusMessage(e),
+                    ) => {
+                        tracing::warn!("{description}: skipping broadcast message {:?}", e);
+                        self.assert_send(&to, description.as_str()).await
+                    }
+                    OutboundMessage::BroadcastMessageOnlyFor(_, els) => {
+                        panic!(
+                        "{description}: received broadcast only for message instead of send {:?}",
                         els
                     );
+                    }
                 }
-                OutboundMessage::BroadcastMessageOnlyFor(_, NetMessage::ConsensusMessage(e)) => {
-                    tracing::warn!("{description}: skipping broadcast message {:?}", e);
-                    self.assert_send(to, description)
-                }
-                OutboundMessage::BroadcastMessageOnlyFor(_, els) => {
-                    panic!(
-                        "{description}: received broadcast only for message instead of send {:?}",
-                        els
+            })
+        }
+
+        pub fn assert_broadcast(
+            &mut self,
+            description: &str,
+        ) -> Pin<Box<dyn Future<Output = MsgWithHeader<MempoolNetMessage>> + '_>> {
+            let description = description.to_string().clone();
+            Box::pin(async move {
+                #[allow(clippy::expect_fun_call)]
+                let rec =
+                    tokio::time::timeout(Duration::from_millis(1000), self.out_receiver.recv())
+                        .await
+                        .expect(format!("{description}: No message broadcasted").as_str())
+                        .expect(format!("{description}: No message broadcasted").as_str());
+
+                match rec {
+                    OutboundMessage::BroadcastMessage(net_msg) => {
+                        if let NetMessage::MempoolMessage(msg) = net_msg {
+                            msg
+                        } else {
+                            println!(
+                            "{description}: Mempool OutboundMessage message is missing, found {}",
+                            net_msg
+                        );
+                            self.assert_broadcast(description.as_str()).await
+                        }
+                    }
+                    _ => {
+                        println!(
+                        "{description}: Broadcast OutboundMessage message is missing, found {:?}",
+                        rec
                     );
+                        self.assert_broadcast(description.as_str()).await
+                    }
                 }
-            }
+            })
         }
 
         pub async fn handle_msg(&mut self, msg: &MsgWithHeader<MempoolNetMessage>, _err: &str) {
             debug!("📥 {} Handling message: {:?}", self.name, msg);
             self.mempool
-                .handle_net_message(msg.clone())
+                .handle_net_message(msg.clone(), &self.mempool_sync_request_sender)
                 .await
                 .expect("should handle net msg");
         }
@@ -1290,15 +1304,31 @@ pub mod test {
         let crypto2 = BlstCrypto::new("2").unwrap();
         ctx.add_trusted_validator(crypto2.validator_pubkey());
 
-        let signed_msg = crypto2.sign_msg_with_header(MempoolNetMessage::SyncRequest(
-            None,
-            Some(data_proposal.hashed()),
-        ))?;
+        for _ in 1..5 {
+            let signed_msg = crypto2.sign_msg_with_header(MempoolNetMessage::SyncRequest(
+                None,
+                Some(data_proposal.hashed()),
+            ))?;
 
-        ctx.mempool
-            .handle_net_message(signed_msg)
-            .await
-            .expect("should handle net message");
+            ctx.mempool
+                .handle_net_message(signed_msg, &ctx.mempool_sync_request_sender)
+                .await
+                .expect("should handle net message");
+        }
+
+        tokio::time::sleep(Duration::from_millis(500)).await;
+
+        for _ in 1..5 {
+            let signed_msg = crypto2.sign_msg_with_header(MempoolNetMessage::SyncRequest(
+                None,
+                Some(data_proposal.hashed()),
+            ))?;
+
+            ctx.mempool
+                .handle_net_message(signed_msg, &ctx.mempool_sync_request_sender)
+                .await
+                .expect("should handle net message");
+        }
 
         // Assert that we send a SyncReply
         match ctx
@@ -1311,6 +1341,96 @@ pub mod test {
             }
             _ => panic!("Expected SyncReply message"),
         };
+
+        assert!(ctx.out_receiver.is_empty());
+        Ok(())
+    }
+
+    #[test_log::test(tokio::test)]
+    async fn test_receiving_sync_requests_multiple_dps() -> Result<()> {
+        let mut ctx = MempoolTestCtx::new("mempool").await;
+
+        // Store the DP locally.
+        let register_tx = make_register_contract_tx(ContractName::new("test1"));
+        let data_proposal = ctx.create_data_proposal(None, &[register_tx.clone()]);
+        ctx.process_new_data_proposal(data_proposal.clone())?;
+
+        let data_proposal2 =
+            ctx.create_data_proposal(Some(data_proposal.hashed()), &[register_tx.clone()]);
+        ctx.process_new_data_proposal(data_proposal2.clone())?;
+
+        // Since mempool is alone, no broadcast
+        let (..) = ctx.last_lane_entry(&LaneId(ctx.validator_pubkey().clone()));
+
+        // Add new validator
+        let crypto2 = BlstCrypto::new("2").unwrap();
+        ctx.add_trusted_validator(crypto2.validator_pubkey());
+
+        // Sync request for the interval up to data proposal
+        for _ in 1..3 {
+            let signed_msg = crypto2.sign_msg_with_header(MempoolNetMessage::SyncRequest(
+                None,
+                Some(data_proposal.hashed()),
+            ))?;
+
+            ctx.mempool
+                .handle_net_message(signed_msg, &ctx.mempool_sync_request_sender)
+                .await
+                .expect("should handle net message");
+        }
+
+        tokio::time::sleep(Duration::from_millis(500)).await;
+
+        // Sync request for the whole interval
+        for _ in 1..3 {
+            let signed_msg = crypto2.sign_msg_with_header(MempoolNetMessage::SyncRequest(
+                None,
+                Some(data_proposal2.hashed()),
+            ))?;
+
+            ctx.mempool
+                .handle_net_message(signed_msg, &ctx.mempool_sync_request_sender)
+                .await
+                .expect("should handle net message");
+        }
+
+        tokio::time::sleep(Duration::from_millis(500)).await;
+
+        // Sync request for the whole interval
+        for _ in 1..3 {
+            let signed_msg =
+                crypto2.sign_msg_with_header(MempoolNetMessage::SyncRequest(None, None))?;
+
+            ctx.mempool
+                .handle_net_message(signed_msg, &ctx.mempool_sync_request_sender)
+                .await
+                .expect("should handle net message");
+        }
+
+        // Assert that we send a SyncReply #1
+        match ctx
+            .assert_send(crypto2.validator_pubkey(), "SyncReply")
+            .await
+            .msg
+        {
+            MempoolNetMessage::SyncReply(_metadata, data_proposal_r) => {
+                assert_eq!(data_proposal_r, data_proposal);
+            }
+            _ => panic!("Expected SyncReply message"),
+        };
+        // Assert that we send a SyncReply #2
+        match ctx
+            .assert_send(crypto2.validator_pubkey(), "SyncReply")
+            .await
+            .msg
+        {
+            MempoolNetMessage::SyncReply(_metadata, data_proposal_r) => {
+                assert_eq!(data_proposal_r, data_proposal2);
+            }
+            _ => panic!("Expected SyncReply message"),
+        };
+
+        assert!(ctx.out_receiver.is_empty());
         Ok(())
     }
 
@@ -1341,7 +1461,10 @@ pub mod test {
             data_proposal.clone(),
         ))?;
 
-        let handle = ctx.mempool.handle_net_message(signed_msg.clone()).await;
+        let handle = ctx
+            .mempool
+            .handle_net_message(signed_msg.clone(), &ctx.mempool_sync_request_sender)
+            .await;
         assert_eq!(
             handle.expect_err("should fail").to_string(),
             format!(
@@ -1363,7 +1486,10 @@ pub mod test {
         ))?;
 
         // This actually fails - we don't know how to handle it
-        let handle = ctx.mempool.handle_net_message(signed_msg.clone()).await;
+        let handle = ctx
+            .mempool
+            .handle_net_message(signed_msg.clone(), &ctx.mempool_sync_request_sender)
+            .await;
         assert_eq!(
             handle.expect_err("should fail").to_string(),
             format!(
@@ -1385,7 +1511,10 @@ pub mod test {
         ))?;
 
         // This actually fails - we don't know how to handle it
-        let handle = ctx.mempool.handle_net_message(signed_msg.clone()).await;
+        let handle = ctx
+            .mempool
+            .handle_net_message(signed_msg.clone(), &ctx.mempool_sync_request_sender)
+            .await;
         assert_eq!(
             handle.expect_err("should fail").to_string(),
             format!(
@@ -1407,7 +1536,10 @@ pub mod test {
         ))?;
 
         // This actually fails - we don't know how to handle it
-        let handle = ctx.mempool.handle_net_message(signed_msg.clone()).await;
+        let handle = ctx
+            .mempool
+            .handle_net_message(signed_msg.clone(), &ctx.mempool_sync_request_sender)
+            .await;
         assert_eq!(
             handle.expect_err("should fail").to_string(),
             format!(
@@ -1428,7 +1560,10 @@ pub mod test {
             data_proposal.clone(),
         ))?;
 
-        let handle = ctx.mempool.handle_net_message(signed_msg.clone()).await;
+        let handle = ctx
+            .mempool
+            .handle_net_message(signed_msg.clone(), &ctx.mempool_sync_request_sender)
+            .await;
         assert_ok!(handle, "Should handle net message");
 
         // Assert that the lane entry was added
diff --git a/src/mempool/metrics.rs b/src/mempool/metrics.rs
index 5bb3d9492..b25068840 100644
--- a/src/mempool/metrics.rs
+++ b/src/mempool/metrics.rs
@@ -8,11 +8,13 @@ use crate::model::ValidatorPublicKey;
 
 use super::QueryNewCut;
 
+#[derive(Clone)]
 pub struct MempoolMetrics {
     api_tx: Counter<u64>,
     dp_vote: Counter<u64>,
     sync_request: Counter<u64>,
     sync_reply: Counter<u64>,
+    mempool_sync: Counter<u64>,
     tx_waiting_dissemination: Gauge<u64>,
     new_cut: Counter<u64>,
 
@@ -42,6 +44,7 @@ impl MempoolMetrics {
             sync_reply: my_meter
                 .u64_counter(format!("{}_sync_reply", mempool))
                 .build(),
+            mempool_sync: my_meter.u64_counter(format!("{}_sync", mempool)).build(),
             tx_waiting_dissemination: my_meter
                 .u64_gauge(format!("{}_tx_waiting_dissemination", mempool))
                 .build(),
@@ -115,28 +118,60 @@ impl MempoolMetrics {
             .add(1, &[KeyValue::new("lane_id", format!("{}", lane_id))])
     }
 
-    pub fn add_sync_request(&self, sender: &ValidatorPublicKey, dest: &ValidatorPublicKey) {
+    /// *emitted* a sync request
+    pub fn sync_request_send(&self, lane: &LaneId, requester: &ValidatorPublicKey) {
         self.sync_request.add(
             1,
             &[
-                KeyValue::new("sender", format!("{}", sender)),
-                KeyValue::new("dest", format!("{}", dest)),
+                KeyValue::new("lane", format!("{}", lane)),
+                KeyValue::new("requester", format!("{}", requester)),
             ],
         );
     }
-    pub fn add_sync_reply(
-        &self,
-        sender: &ValidatorPublicKey,
-        dest: &ValidatorPublicKey,
-        lane_entries: usize,
-    ) {
+    /// *received* a sync reply
+    pub fn sync_reply_receive(&self, lane: &LaneId, requester: &ValidatorPublicKey) {
         self.sync_reply.add(
             1,
             &[
-                KeyValue::new("nb_lane_entries", lane_entries.to_string()),
-                KeyValue::new("sender", format!("{}", sender)),
-                KeyValue::new("dest", format!("{}", dest)),
+                KeyValue::new("lane", format!("{}", lane)),
+                KeyValue::new("requester", format!("{}", requester)),
             ],
         )
     }
+
+    /// MempoolSync: Prepare a sync reply to *send*
+    pub fn mempool_sync_processed(&self, lane: &LaneId, requester: &ValidatorPublicKey) {
+        self.mempool_sync.add(
+            1,
+            &[
+                KeyValue::new("lane", format!("{}", lane)),
+                KeyValue::new("requester", format!("{}", requester)),
+                KeyValue::new("status", "processed"),
+            ],
+        );
+    }
+
+    /// MempoolSync: Prepare a sync reply to *send*
+    pub fn mempool_sync_failure(&self, lane: &LaneId, requester: &ValidatorPublicKey) {
+        self.mempool_sync.add(
+            1,
+            &[
+                KeyValue::new("lane", format!("{}", lane)),
+                KeyValue::new("requester", format!("{}", requester)),
+                KeyValue::new("status", "failure"),
+            ],
+        );
+    }
+
+    /// MempoolSync: Throttle a sync reply instead of preparing it
+    pub fn mempool_sync_throttled(&self, lane: &LaneId, requester: &ValidatorPublicKey) {
+        self.mempool_sync.add(
+            1,
+            &[
+                KeyValue::new("lane", format!("{}", lane)),
+                KeyValue::new("requester", format!("{}", requester)),
+                KeyValue::new("status", "throttled"),
+            ],
+        );
+    }
 }
diff --git a/src/mempool/own_lane.rs b/src/mempool/own_lane.rs
index eea392301..f27fd4f4d 100644
--- a/src/mempool/own_lane.rs
+++ b/src/mempool/own_lane.rs
@@ -543,10 +543,16 @@ pub mod test {
         let signed_msg2 = create_data_vote(&crypto2, data_proposal.hashed(), size)?;
         let signed_msg3 = create_data_vote(&crypto3, data_proposal.hashed(), size)?;
         ctx.mempool
-            .handle_net_message(crypto2.sign_msg_with_header(signed_msg2)?)
+            .handle_net_message(
+                crypto2.sign_msg_with_header(signed_msg2)?,
+                &ctx.mempool_sync_request_sender,
+            )
             .await?;
         ctx.mempool
-            .handle_net_message(crypto3.sign_msg_with_header(signed_msg3)?)
+            .handle_net_message(
+                crypto3.sign_msg_with_header(signed_msg3)?,
+                &ctx.mempool_sync_request_sender,
+            )
             .await?;
 
         // Assert that PoDAUpdate message is broadcasted
@@ -575,7 +581,10 @@ pub mod test {
         let signed_msg = create_data_vote(&temp_crypto, data_proposal.hashed(), size)?;
         assert!(ctx
             .mempool
-            .handle_net_message(temp_crypto.sign_msg_with_header(signed_msg)?)
+            .handle_net_message(
+                temp_crypto.sign_msg_with_header(signed_msg)?,
+                &ctx.mempool_sync_request_sender,
+            )
             .await
             .is_err());
 
@@ -602,7 +611,10 @@ pub mod test {
         let signed_msg = create_data_vote(&crypto2, data_proposal_hash, size)?;
 
         ctx.mempool
-            .handle_net_message(crypto2.sign_msg_with_header(signed_msg)?)
+            .handle_net_message(
+                crypto2.sign_msg_with_header(signed_msg)?,
+                &ctx.mempool_sync_request_sender,
+            )
             .await
             .expect("should handle net message");
 
@@ -630,7 +642,10 @@ pub mod test {
 
         assert!(ctx
             .mempool
-            .handle_net_message(crypto2.sign_msg_with_header(signed_msg)?)
+            .handle_net_message(
+                crypto2.sign_msg_with_header(signed_msg)?,
+                &ctx.mempool_sync_request_sender,
+            )
             .await
             .is_err());
         Ok(())
diff --git a/src/mempool/storage_fjall.rs b/src/mempool/storage_fjall.rs
index 84b0d1fe9..4b6b50e05 100644
--- a/src/mempool/storage_fjall.rs
+++ b/src/mempool/storage_fjall.rs
@@ -28,6 +28,16 @@ pub struct LanesStorage {
 }
 
 impl LanesStorage {
+    /// Create another set of handles, without the data stored in lanes_tip, to have access and methods to access mempool storage
+    pub fn new_handle(&self) -> LanesStorage {
+        LanesStorage {
+            lanes_tip: Default::default(),
+            db: self.db.clone(),
+            by_hash_metadata: self.by_hash_metadata.clone(),
+            by_hash_data: self.by_hash_data.clone(),
+        }
+    }
+
     pub fn new(
         path: &Path,
         lanes_tip: BTreeMap<LaneId, (DataProposalHash, LaneBytesSize)>,
diff --git a/src/mempool/storage_memory.rs b/src/mempool/storage_memory.rs
index e02d821b5..b997aabca 100644
--- a/src/mempool/storage_memory.rs
+++ b/src/mempool/storage_memory.rs
@@ -15,6 +15,7 @@ use super::{
 };
 use crate::model::{DataProposal, DataProposalHash, Hashed};
 
+#[derive(Default)]
 pub struct LanesStorage {
     pub lanes_tip: BTreeMap<LaneId, (DataProposalHash, LaneBytesSize)>,
     // NB: do not iterate on these as they're unordered
@@ -22,6 +23,9 @@ pub struct LanesStorage {
 }
 
 impl LanesStorage {
+    pub fn new_handle(&self) -> LanesStorage {
+        LanesStorage::default()
+    }
     pub fn new(
         _path: &Path,
         lanes_tip: BTreeMap<LaneId, (DataProposalHash, LaneBytesSize)>,
diff --git a/src/mempool/sync_request_reply.rs b/src/mempool/sync_request_reply.rs
new file mode 100644
index 000000000..42a328efc
--- /dev/null
+++ b/src/mempool/sync_request_reply.rs
@@ -0,0 +1,215 @@
+use std::{
+    collections::{HashMap, HashSet},
+    time::Duration,
+};
+
+use futures::StreamExt;
+use hyle_crypto::SharedBlstCrypto;
+use hyle_model::{utils::TimestampMs, DataProposalHash, LaneId, ValidatorPublicKey};
+use hyle_modules::{log_error, log_warn};
+use hyle_net::clock::TimestampMsClock;
+use tokio::pin;
+use tracing::{debug, info, warn};
+
+use crate::p2p::network::{HeaderSigner, OutboundMessage};
+
+use super::{
+    metrics::MempoolMetrics,
+    storage::{LaneEntryMetadata, Storage},
+    storage_fjall::LanesStorage,
+    MempoolNetMessage,
+};
+
+pub struct SyncRequest {
+    pub from: Option<DataProposalHash>,
+    pub to: DataProposalHash,
+    pub validator: ValidatorPublicKey,
+}
+
+/// Submodule of Mempool dedicated to SyncRequest/SyncReply handling
+pub struct MempoolSync {
+    // TODO: Remove after putting lane id in sync request/sync reply
+    lane_id: LaneId,
+    /// Storage handle
+    lanes: LanesStorage,
+    /// Crypto handle
+    crypto: SharedBlstCrypto,
+    /// Metrics handle
+    metrics: MempoolMetrics,
+    /// Keeping track of last time we sent a reply to the validator and the data proposal hash
+    by_pubkey_by_dp_hash: HashMap<ValidatorPublicKey, HashMap<DataProposalHash, TimestampMs>>,
+    /// Map containing per data proposal, which validators are interested in a sync reply
+    todo: HashMap<DataProposalHash, (LaneEntryMetadata, HashSet<ValidatorPublicKey>)>,
+    /// Network message channel
+    net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+    /// Chan where Mempool puts received Sync Requests to handle
+    sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
+}
+
+impl MempoolSync {
+    pub fn create(
+        lane_id: LaneId,
+        lanes: LanesStorage,
+        crypto: SharedBlstCrypto,
+        metrics: MempoolMetrics,
+        net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+        sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
+    ) -> MempoolSync {
+        MempoolSync {
+            lane_id,
+            lanes,
+            crypto,
+            metrics,
+            net_sender,
+            sync_request_receiver,
+            by_pubkey_by_dp_hash: Default::default(),
+            todo: Default::default(),
+        }
+    }
+    pub async fn start(&mut self) -> anyhow::Result<()> {
+        info!("Starting MempoolSync");
+
+        let mut batched_replies_interval = tokio::time::interval(Duration::from_millis(200));
+        loop {
+            tokio::select! {
+                Some(sync_request) = self.sync_request_receiver.recv() => {
+                    _ = log_error!(
+                        self.unfold_sync_request_interval(sync_request).await,
+                        "Unfolding SyncRequest interval"
+                    );
+                }
+                _ = batched_replies_interval.tick() => {
+                    self.send_replies().await;
+                }
+            }
+        }
+    }
+
+    /// Reply can be emitted because
+    /// - it has never been emitted before
+    /// - it was emitted a long time ago
+    fn should_throttle(
+        &self,
+        validator: &ValidatorPublicKey,
+        data_proposal_hash: &DataProposalHash,
+    ) -> bool {
+        let now = TimestampMsClock::now();
+
+        let Some(data_proposal_record) = self
+            .by_pubkey_by_dp_hash
+            .get(validator)
+            .and_then(|validator_records| validator_records.get(data_proposal_hash))
+        else {
+            return false;
+        };
+
+        if now - data_proposal_record.clone() > Duration::from_secs(10) {
+            return false;
+        }
+
+        true
+    }
+
+    /// Fetches metadata from storage for the given interval, and populate the todo hashmap with it. Called everytime we get a new SyncRequest
+    async fn unfold_sync_request_interval(
+        &mut self,
+        SyncRequest {
+            from,
+            to,
+            validator,
+        }: SyncRequest,
+    ) -> anyhow::Result<()> {
+        if from.as_ref() == Some(&to) {
+            return Ok(());
+        }
+
+        pin! {
+            let stream = self.lanes.get_entries_metadata_between_hashes(&self.lane_id, from, Some(to));
+        };
+
+        while let Some(entry) = stream.next().await {
+            if let Ok((metadata, dp_hash)) =
+                log_warn!(entry, "Getting entry metada to prepare sync replies")
+            {
+                self.todo
+                    .entry(dp_hash)
+                    .or_insert((metadata, Default::default()))
+                    .1
+                    .insert(validator.clone());
+            }
+        }
+        Ok(())
+    }
+
+    /// Try to send replies based on what is stored in the todo hashmap. Every time a reply is sent, it stored a timestamp to throttle upcoming SyncRequests, and remove it from the todo hashmap
+    async fn send_replies(&mut self) {
+        if self.todo.is_empty() {
+            return;
+        }
+
+        let mut todo = HashMap::new();
+
+        std::mem::swap(&mut self.todo, &mut todo);
+
+        for (dp_hash, (metadata, validators)) in todo.into_iter() {
+            for validator in validators.into_iter() {
+                if self.should_throttle(&validator, &dp_hash) {
+                    debug!(
+                        "Throttling reply for DP Hash: {} to: {}",
+                        &dp_hash, &validator
+                    );
+                    self.metrics
+                        .mempool_sync_throttled(&self.lane_id, &validator);
+                } else {
+                    self.metrics
+                        .mempool_sync_processed(&self.lane_id, &validator);
+
+                    // Update last dissemination time
+                    let now = TimestampMsClock::now();
+                    self.by_pubkey_by_dp_hash
+                        .entry(validator.clone())
+                        .or_default()
+                        .insert(dp_hash.clone(), now);
+
+                    if let Ok(Some(data_proposal)) = log_error!(
+                        self.lanes.get_dp_by_hash(&self.lane_id, &dp_hash),
+                        "Getting data proposal for to prepare a SyncReply"
+                    ) {
+                        let signed_reply =
+                            self.crypto
+                                .sign_msg_with_header(MempoolNetMessage::SyncReply(
+                                    metadata.clone(),
+                                    data_proposal,
+                                ));
+
+                        if let Ok(signed_reply) = signed_reply {
+                            if log_error!(
+                                self.net_sender
+                                    .send(OutboundMessage::send(validator.clone(), signed_reply)),
+                                "Sending MempoolNetMessage::SyncReply msg on the bus"
+                            )
+                            .is_ok()
+                            {
+                                debug!("Sent reply for DP Hash: {} to: {}", &dp_hash, &validator);
+                                // In case of success, we don't put back this reply in the todo map
+                                continue;
+                            }
+                        }
+                    }
+
+                    warn!(
+                        "Could not send reply for DP Hash: {} to: {}, retrying later.",
+                        &dp_hash, &validator
+                    );
+                    self.metrics.mempool_sync_failure(&self.lane_id, &validator);
+
+                    self.todo
+                        .entry(dp_hash.clone())
+                        .or_insert((metadata.clone(), Default::default()))
+                        .1
+                        .insert(validator);
+                }
+            }
+        }
+    }
+}
diff --git a/src/mempool/verify_tx.rs b/src/mempool/verify_tx.rs
index 76b03b214..f742d981e 100644
--- a/src/mempool/verify_tx.rs
+++ b/src/mempool/verify_tx.rs
@@ -571,7 +571,7 @@ pub mod test {
                 ))?;
 
         ctx.mempool
-            .handle_net_message(signed_msg)
+            .handle_net_message(signed_msg, &ctx.mempool_sync_request_sender)
             .await
             .expect("should handle net message");
 
diff --git a/src/tests/autobahn_testing.rs b/src/tests/autobahn_testing.rs
index 871bd0f0e..1288d12ee 100644
--- a/src/tests/autobahn_testing.rs
+++ b/src/tests/autobahn_testing.rs
@@ -309,6 +309,8 @@ impl AutobahnTestCtx {
         let consensus = ConsensusTestCtx::build_consensus(&shared_bus, crypto.clone()).await;
         let mempool = MempoolTestCtx::build_mempool(&shared_bus, crypto).await;
 
+        let mempool_sync_request_sender = mempool.start_mempool_sync();
+
         AutobahnTestCtx {
             shared_bus,
             consensus_ctx: ConsensusTestCtx {
@@ -324,6 +326,7 @@ impl AutobahnTestCtx {
                 mempool_event_receiver,
                 mempool_status_event_receiver,
                 mempool,
+                mempool_sync_request_sender,
             },
         }
     }


Reviews:
user: login: wraitii
body: Ça me parait beaucoup plus clean comme ça
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T08:10:32Z
user: login: hhalex
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T09:11:44Z
user: login: hhalex
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T09:13:47Z
user: login: wraitii
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T09:17:07Z
user: login: hhalex
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T09:17:45Z
user: login: wraitii
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T09:17:51Z
user: login: hhalex
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T09:21:35Z
user: login: hhalex
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T09:26:28Z
user: login: wraitii
body: 🚀 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-19T08:00:19Z

Review comments:
diff_hunk: @@ -0,0 +1,201 @@
+use std::{
+    collections::{HashMap, HashSet},
+    time::Duration,
+};
+
+use futures::StreamExt;
+use hyle_crypto::SharedBlstCrypto;
+use hyle_model::{utils::TimestampMs, DataProposalHash, LaneId, ValidatorPublicKey};
+use hyle_modules::{log_error, log_warn};
+use hyle_net::clock::TimestampMsClock;
+use tokio::pin;
+use tracing::info;
+
+use crate::p2p::network::{HeaderSigner, OutboundMessage};
+
+use super::{
+    metrics::MempoolMetrics,
+    storage::{LaneEntryMetadata, Storage},
+    storage_fjall::LanesStorage,
+    MempoolNetMessage,
+};
+
+pub struct SyncRequest {
+    pub from: Option<DataProposalHash>,
+    pub to: DataProposalHash,
+    pub validator: ValidatorPublicKey,
+}
+
+/// Submodule of Mempool dedicated to SyncRequest/SyncReply handling
+pub struct MempoolSync {
+    lane_id: LaneId,
+    /// Storage handle
+    lanes: LanesStorage,
+    /// Crypto handle
+    crypto: SharedBlstCrypto,
+    /// Metrics handle
+    _metrics: MempoolMetrics,
+    /// Keeping track of last time we sent a reply to the validator and the data proposal hash
+    by_pubkey_by_dp_hash: HashMap<ValidatorPublicKey, HashMap<DataProposalHash, TimestampMs>>,
+    /// Map containing per data proposal, which validators are interested in a sync reply
+    todo: HashMap<DataProposalHash, (LaneEntryMetadata, HashSet<ValidatorPublicKey>)>,
+    /// Network message channel
+    net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+    /// Chan where Mempool puts received Sync Requests to handle
+    sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
path: src/mempool/sync_request_reply.rs
user: login: wraitii
body: Pourquoi tu listen pas directement au bus ? 
created_at: 2025-05-16T08:08:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,201 @@
+use std::{
+    collections::{HashMap, HashSet},
+    time::Duration,
+};
+
+use futures::StreamExt;
+use hyle_crypto::SharedBlstCrypto;
+use hyle_model::{utils::TimestampMs, DataProposalHash, LaneId, ValidatorPublicKey};
+use hyle_modules::{log_error, log_warn};
+use hyle_net::clock::TimestampMsClock;
+use tokio::pin;
+use tracing::info;
+
+use crate::p2p::network::{HeaderSigner, OutboundMessage};
+
+use super::{
+    metrics::MempoolMetrics,
+    storage::{LaneEntryMetadata, Storage},
+    storage_fjall::LanesStorage,
+    MempoolNetMessage,
+};
+
+pub struct SyncRequest {
+    pub from: Option<DataProposalHash>,
+    pub to: DataProposalHash,
+    pub validator: ValidatorPublicKey,
+}
+
+/// Submodule of Mempool dedicated to SyncRequest/SyncReply handling
+pub struct MempoolSync {
+    lane_id: LaneId,
+    /// Storage handle
+    lanes: LanesStorage,
+    /// Crypto handle
+    crypto: SharedBlstCrypto,
+    /// Metrics handle
+    _metrics: MempoolMetrics,
+    /// Keeping track of last time we sent a reply to the validator and the data proposal hash
+    by_pubkey_by_dp_hash: HashMap<ValidatorPublicKey, HashMap<DataProposalHash, TimestampMs>>,
+    /// Map containing per data proposal, which validators are interested in a sync reply
+    todo: HashMap<DataProposalHash, (LaneEntryMetadata, HashSet<ValidatorPublicKey>)>,
+    /// Network message channel
+    net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+    /// Chan where Mempool puts received Sync Requests to handle
+    sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
+}
+
+impl MempoolSync {
+    pub fn create(
+        lane_id: LaneId,
+        lanes: LanesStorage,
+        crypto: SharedBlstCrypto,
+        metrics: MempoolMetrics,
+        net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+        sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
+    ) -> MempoolSync {
+        MempoolSync {
+            lane_id,
+            lanes,
+            crypto,
+            _metrics: metrics,
+            net_sender,
+            sync_request_receiver,
+            by_pubkey_by_dp_hash: Default::default(),
+            todo: Default::default(),
+        }
+    }
+    pub async fn start(&mut self) -> anyhow::Result<()> {
+        info!("Starting MempoolSync");
+
+        let mut batched_replies_interval = tokio::time::interval(Duration::from_secs(1));
+        loop {
+            tokio::select! {
+                Some(sync_request) = self.sync_request_receiver.recv() => {
+
+                    tracing::warn!("SyncRequest received");
+                    _ = log_error!(
+                        self.unfold_sync_request_interval(sync_request).await,
+                        "Unfolding SyncRequest interval"
+                    );
+                }
+                _ = batched_replies_interval.tick() => {
+                    tracing::info!("Sending replies");
+                    self.send_replies().await;
+                }
+            }
+        }
+    }
+
+    /// Update the timestamp in the map, so we don't emit replies before some time
+    fn start_throttling_for(
path: src/mempool/sync_request_reply.rs
user: login: wraitii
body: J'aime pas trop le nom de cette fonction, je préférerais quelque chose comme "update_last_dissemination_time"
created_at: 2025-05-16T08:10:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,201 @@
+use std::{
+    collections::{HashMap, HashSet},
+    time::Duration,
+};
+
+use futures::StreamExt;
+use hyle_crypto::SharedBlstCrypto;
+use hyle_model::{utils::TimestampMs, DataProposalHash, LaneId, ValidatorPublicKey};
+use hyle_modules::{log_error, log_warn};
+use hyle_net::clock::TimestampMsClock;
+use tokio::pin;
+use tracing::info;
+
+use crate::p2p::network::{HeaderSigner, OutboundMessage};
+
+use super::{
+    metrics::MempoolMetrics,
+    storage::{LaneEntryMetadata, Storage},
+    storage_fjall::LanesStorage,
+    MempoolNetMessage,
+};
+
+pub struct SyncRequest {
+    pub from: Option<DataProposalHash>,
+    pub to: DataProposalHash,
+    pub validator: ValidatorPublicKey,
+}
+
+/// Submodule of Mempool dedicated to SyncRequest/SyncReply handling
+pub struct MempoolSync {
+    lane_id: LaneId,
+    /// Storage handle
+    lanes: LanesStorage,
+    /// Crypto handle
+    crypto: SharedBlstCrypto,
+    /// Metrics handle
+    _metrics: MempoolMetrics,
+    /// Keeping track of last time we sent a reply to the validator and the data proposal hash
+    by_pubkey_by_dp_hash: HashMap<ValidatorPublicKey, HashMap<DataProposalHash, TimestampMs>>,
+    /// Map containing per data proposal, which validators are interested in a sync reply
+    todo: HashMap<DataProposalHash, (LaneEntryMetadata, HashSet<ValidatorPublicKey>)>,
+    /// Network message channel
+    net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+    /// Chan where Mempool puts received Sync Requests to handle
+    sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
+}
+
+impl MempoolSync {
+    pub fn create(
+        lane_id: LaneId,
+        lanes: LanesStorage,
+        crypto: SharedBlstCrypto,
+        metrics: MempoolMetrics,
+        net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+        sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
+    ) -> MempoolSync {
+        MempoolSync {
+            lane_id,
+            lanes,
+            crypto,
+            _metrics: metrics,
+            net_sender,
+            sync_request_receiver,
+            by_pubkey_by_dp_hash: Default::default(),
+            todo: Default::default(),
+        }
+    }
+    pub async fn start(&mut self) -> anyhow::Result<()> {
+        info!("Starting MempoolSync");
+
+        let mut batched_replies_interval = tokio::time::interval(Duration::from_secs(1));
+        loop {
+            tokio::select! {
+                Some(sync_request) = self.sync_request_receiver.recv() => {
+
+                    tracing::warn!("SyncRequest received");
+                    _ = log_error!(
+                        self.unfold_sync_request_interval(sync_request).await,
+                        "Unfolding SyncRequest interval"
+                    );
+                }
+                _ = batched_replies_interval.tick() => {
+                    tracing::info!("Sending replies");
+                    self.send_replies().await;
+                }
+            }
+        }
+    }
+
+    /// Update the timestamp in the map, so we don't emit replies before some time
+    fn start_throttling_for(
+        &mut self,
+        validator: ValidatorPublicKey,
+        data_proposal_hash: DataProposalHash,
+    ) {
+        let now = TimestampMsClock::now();
+        self.by_pubkey_by_dp_hash
+            .entry(validator)
+            .or_default()
+            .insert(data_proposal_hash, now);
+    }
+
+    /// Reply can be emitted because
+    /// - it has never been emitted before
+    /// - it was emitted a long time ago
+    fn should_throttle(
+        &self,
+        validator: &ValidatorPublicKey,
+        data_proposal_hash: &DataProposalHash,
+    ) -> bool {
+        let now = TimestampMsClock::now();
+
+        let Some(data_proposal_record) = self
+            .by_pubkey_by_dp_hash
+            .get(validator)
+            .and_then(|validator_records| validator_records.get(data_proposal_hash))
+        else {
+            return false;
+        };
+
+        if now - data_proposal_record.clone() > Duration::from_secs(10) {
+            return false;
+        }
+
+        true
+    }
+
+    /// Fetches metadata from storage for the given interval, and populate the todo hashmap with it. Called everytime we get a new SyncRequest
+    async fn unfold_sync_request_interval(
+        &mut self,
+        SyncRequest {
+            from,
+            to,
+            validator,
+        }: SyncRequest,
+    ) -> anyhow::Result<()> {
+        if from.as_ref() == Some(&to) {
+            return Ok(());
+        }
+
+        pin! {
+            let stream = self.lanes.get_entries_metadata_between_hashes(&self.lane_id, from, Some(to));
+        };
+
+        while let Some(entry) = stream.next().await {
+            if let Ok((metadata, dp_hash)) =
+                log_warn!(entry, "Getting entry metada to prepare sync replies")
+            {
+                self.todo
+                    .entry(dp_hash)
+                    .or_insert((metadata, Default::default()))
+                    .1
+                    .insert(validator.clone());
+            }
+        }
+        Ok(())
+    }
+
+    /// Try to send replies based on what is stored in the todo hashmap. Every time a reply is sent, it stored a timestamp to throttle upcoming SyncRequests, and remove it from the todo hashmap
+    async fn send_replies(&mut self) {
+        for (dp_hash, (md, validators)) in self.todo.clone().into_iter() {
+            for validator in validators.into_iter() {
path: src/mempool/sync_request_reply.rs
user: login: wraitii
body: IMO t'as des clone / into_iter à cleaner, mais ça me semble good overall
created_at: 2025-05-16T08:10:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,201 @@
+use std::{
+    collections::{HashMap, HashSet},
+    time::Duration,
+};
+
+use futures::StreamExt;
+use hyle_crypto::SharedBlstCrypto;
+use hyle_model::{utils::TimestampMs, DataProposalHash, LaneId, ValidatorPublicKey};
+use hyle_modules::{log_error, log_warn};
+use hyle_net::clock::TimestampMsClock;
+use tokio::pin;
+use tracing::info;
+
+use crate::p2p::network::{HeaderSigner, OutboundMessage};
+
+use super::{
+    metrics::MempoolMetrics,
+    storage::{LaneEntryMetadata, Storage},
+    storage_fjall::LanesStorage,
+    MempoolNetMessage,
+};
+
+pub struct SyncRequest {
+    pub from: Option<DataProposalHash>,
+    pub to: DataProposalHash,
+    pub validator: ValidatorPublicKey,
+}
+
+/// Submodule of Mempool dedicated to SyncRequest/SyncReply handling
+pub struct MempoolSync {
+    lane_id: LaneId,
+    /// Storage handle
+    lanes: LanesStorage,
+    /// Crypto handle
+    crypto: SharedBlstCrypto,
+    /// Metrics handle
+    _metrics: MempoolMetrics,
+    /// Keeping track of last time we sent a reply to the validator and the data proposal hash
+    by_pubkey_by_dp_hash: HashMap<ValidatorPublicKey, HashMap<DataProposalHash, TimestampMs>>,
+    /// Map containing per data proposal, which validators are interested in a sync reply
+    todo: HashMap<DataProposalHash, (LaneEntryMetadata, HashSet<ValidatorPublicKey>)>,
+    /// Network message channel
+    net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+    /// Chan where Mempool puts received Sync Requests to handle
+    sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
path: src/mempool/sync_request_reply.rs
user: login: hhalex
body: ~~Je peux, mais ça demande de clone le bus client, ça crée des receivers qu'on ne consommera pas, et donc du lag sur des topics.
Je peux en revanche faire un bus client plus statique... mais voilà~~
created_at: 2025-05-16T09:11:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,201 @@
+use std::{
+    collections::{HashMap, HashSet},
+    time::Duration,
+};
+
+use futures::StreamExt;
+use hyle_crypto::SharedBlstCrypto;
+use hyle_model::{utils::TimestampMs, DataProposalHash, LaneId, ValidatorPublicKey};
+use hyle_modules::{log_error, log_warn};
+use hyle_net::clock::TimestampMsClock;
+use tokio::pin;
+use tracing::info;
+
+use crate::p2p::network::{HeaderSigner, OutboundMessage};
+
+use super::{
+    metrics::MempoolMetrics,
+    storage::{LaneEntryMetadata, Storage},
+    storage_fjall::LanesStorage,
+    MempoolNetMessage,
+};
+
+pub struct SyncRequest {
+    pub from: Option<DataProposalHash>,
+    pub to: DataProposalHash,
+    pub validator: ValidatorPublicKey,
+}
+
+/// Submodule of Mempool dedicated to SyncRequest/SyncReply handling
+pub struct MempoolSync {
+    lane_id: LaneId,
+    /// Storage handle
+    lanes: LanesStorage,
+    /// Crypto handle
+    crypto: SharedBlstCrypto,
+    /// Metrics handle
+    _metrics: MempoolMetrics,
+    /// Keeping track of last time we sent a reply to the validator and the data proposal hash
+    by_pubkey_by_dp_hash: HashMap<ValidatorPublicKey, HashMap<DataProposalHash, TimestampMs>>,
+    /// Map containing per data proposal, which validators are interested in a sync reply
+    todo: HashMap<DataProposalHash, (LaneEntryMetadata, HashSet<ValidatorPublicKey>)>,
+    /// Network message channel
+    net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+    /// Chan where Mempool puts received Sync Requests to handle
+    sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
+}
+
+impl MempoolSync {
+    pub fn create(
+        lane_id: LaneId,
+        lanes: LanesStorage,
+        crypto: SharedBlstCrypto,
+        metrics: MempoolMetrics,
+        net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+        sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
+    ) -> MempoolSync {
+        MempoolSync {
+            lane_id,
+            lanes,
+            crypto,
+            _metrics: metrics,
+            net_sender,
+            sync_request_receiver,
+            by_pubkey_by_dp_hash: Default::default(),
+            todo: Default::default(),
+        }
+    }
+    pub async fn start(&mut self) -> anyhow::Result<()> {
+        info!("Starting MempoolSync");
+
+        let mut batched_replies_interval = tokio::time::interval(Duration::from_secs(1));
+        loop {
+            tokio::select! {
+                Some(sync_request) = self.sync_request_receiver.recv() => {
+
+                    tracing::warn!("SyncRequest received");
+                    _ = log_error!(
+                        self.unfold_sync_request_interval(sync_request).await,
+                        "Unfolding SyncRequest interval"
+                    );
+                }
+                _ = batched_replies_interval.tick() => {
+                    tracing::info!("Sending replies");
+                    self.send_replies().await;
+                }
+            }
+        }
+    }
+
+    /// Update the timestamp in the map, so we don't emit replies before some time
+    fn start_throttling_for(
+        &mut self,
+        validator: ValidatorPublicKey,
+        data_proposal_hash: DataProposalHash,
+    ) {
+        let now = TimestampMsClock::now();
+        self.by_pubkey_by_dp_hash
+            .entry(validator)
+            .or_default()
+            .insert(data_proposal_hash, now);
+    }
+
+    /// Reply can be emitted because
+    /// - it has never been emitted before
+    /// - it was emitted a long time ago
+    fn should_throttle(
+        &self,
+        validator: &ValidatorPublicKey,
+        data_proposal_hash: &DataProposalHash,
+    ) -> bool {
+        let now = TimestampMsClock::now();
+
+        let Some(data_proposal_record) = self
+            .by_pubkey_by_dp_hash
+            .get(validator)
+            .and_then(|validator_records| validator_records.get(data_proposal_hash))
+        else {
+            return false;
+        };
+
+        if now - data_proposal_record.clone() > Duration::from_secs(10) {
+            return false;
+        }
+
+        true
+    }
+
+    /// Fetches metadata from storage for the given interval, and populate the todo hashmap with it. Called everytime we get a new SyncRequest
+    async fn unfold_sync_request_interval(
+        &mut self,
+        SyncRequest {
+            from,
+            to,
+            validator,
+        }: SyncRequest,
+    ) -> anyhow::Result<()> {
+        if from.as_ref() == Some(&to) {
+            return Ok(());
+        }
+
+        pin! {
+            let stream = self.lanes.get_entries_metadata_between_hashes(&self.lane_id, from, Some(to));
+        };
+
+        while let Some(entry) = stream.next().await {
+            if let Ok((metadata, dp_hash)) =
+                log_warn!(entry, "Getting entry metada to prepare sync replies")
+            {
+                self.todo
+                    .entry(dp_hash)
+                    .or_insert((metadata, Default::default()))
+                    .1
+                    .insert(validator.clone());
+            }
+        }
+        Ok(())
+    }
+
+    /// Try to send replies based on what is stored in the todo hashmap. Every time a reply is sent, it stored a timestamp to throttle upcoming SyncRequests, and remove it from the todo hashmap
+    async fn send_replies(&mut self) {
+        for (dp_hash, (md, validators)) in self.todo.clone().into_iter() {
+            for validator in validators.into_iter() {
path: src/mempool/sync_request_reply.rs
user: login: hhalex
body: J'ai rajouté du cleaning des métriques qui étaient un peu broken.

Il reste à fix untruc cela dit, les lane ids ne sont pas transmis dans les sync requests/sync replies, ce qui fait qu'on se base systématiquement sur la validateur pubkey... Next PR, j'ai mis des TODO
created_at: 2025-05-16T09:13:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,201 @@
+use std::{
+    collections::{HashMap, HashSet},
+    time::Duration,
+};
+
+use futures::StreamExt;
+use hyle_crypto::SharedBlstCrypto;
+use hyle_model::{utils::TimestampMs, DataProposalHash, LaneId, ValidatorPublicKey};
+use hyle_modules::{log_error, log_warn};
+use hyle_net::clock::TimestampMsClock;
+use tokio::pin;
+use tracing::info;
+
+use crate::p2p::network::{HeaderSigner, OutboundMessage};
+
+use super::{
+    metrics::MempoolMetrics,
+    storage::{LaneEntryMetadata, Storage},
+    storage_fjall::LanesStorage,
+    MempoolNetMessage,
+};
+
+pub struct SyncRequest {
+    pub from: Option<DataProposalHash>,
+    pub to: DataProposalHash,
+    pub validator: ValidatorPublicKey,
+}
+
+/// Submodule of Mempool dedicated to SyncRequest/SyncReply handling
+pub struct MempoolSync {
+    lane_id: LaneId,
+    /// Storage handle
+    lanes: LanesStorage,
+    /// Crypto handle
+    crypto: SharedBlstCrypto,
+    /// Metrics handle
+    _metrics: MempoolMetrics,
+    /// Keeping track of last time we sent a reply to the validator and the data proposal hash
+    by_pubkey_by_dp_hash: HashMap<ValidatorPublicKey, HashMap<DataProposalHash, TimestampMs>>,
+    /// Map containing per data proposal, which validators are interested in a sync reply
+    todo: HashMap<DataProposalHash, (LaneEntryMetadata, HashSet<ValidatorPublicKey>)>,
+    /// Network message channel
+    net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+    /// Chan where Mempool puts received Sync Requests to handle
+    sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
path: src/mempool/sync_request_reply.rs
user: login: wraitii
body: mh ui ok
created_at: 2025-05-16T09:17:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,201 @@
+use std::{
+    collections::{HashMap, HashSet},
+    time::Duration,
+};
+
+use futures::StreamExt;
+use hyle_crypto::SharedBlstCrypto;
+use hyle_model::{utils::TimestampMs, DataProposalHash, LaneId, ValidatorPublicKey};
+use hyle_modules::{log_error, log_warn};
+use hyle_net::clock::TimestampMsClock;
+use tokio::pin;
+use tracing::info;
+
+use crate::p2p::network::{HeaderSigner, OutboundMessage};
+
+use super::{
+    metrics::MempoolMetrics,
+    storage::{LaneEntryMetadata, Storage},
+    storage_fjall::LanesStorage,
+    MempoolNetMessage,
+};
+
+pub struct SyncRequest {
+    pub from: Option<DataProposalHash>,
+    pub to: DataProposalHash,
+    pub validator: ValidatorPublicKey,
+}
+
+/// Submodule of Mempool dedicated to SyncRequest/SyncReply handling
+pub struct MempoolSync {
+    lane_id: LaneId,
+    /// Storage handle
+    lanes: LanesStorage,
+    /// Crypto handle
+    crypto: SharedBlstCrypto,
+    /// Metrics handle
+    _metrics: MempoolMetrics,
+    /// Keeping track of last time we sent a reply to the validator and the data proposal hash
+    by_pubkey_by_dp_hash: HashMap<ValidatorPublicKey, HashMap<DataProposalHash, TimestampMs>>,
+    /// Map containing per data proposal, which validators are interested in a sync reply
+    todo: HashMap<DataProposalHash, (LaneEntryMetadata, HashSet<ValidatorPublicKey>)>,
+    /// Network message channel
+    net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+    /// Chan where Mempool puts received Sync Requests to handle
+    sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
path: src/mempool/sync_request_reply.rs
user: login: hhalex
body: Ah mince pour les MempoolNetMessage ?
created_at: 2025-05-16T09:17:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,201 @@
+use std::{
+    collections::{HashMap, HashSet},
+    time::Duration,
+};
+
+use futures::StreamExt;
+use hyle_crypto::SharedBlstCrypto;
+use hyle_model::{utils::TimestampMs, DataProposalHash, LaneId, ValidatorPublicKey};
+use hyle_modules::{log_error, log_warn};
+use hyle_net::clock::TimestampMsClock;
+use tokio::pin;
+use tracing::info;
+
+use crate::p2p::network::{HeaderSigner, OutboundMessage};
+
+use super::{
+    metrics::MempoolMetrics,
+    storage::{LaneEntryMetadata, Storage},
+    storage_fjall::LanesStorage,
+    MempoolNetMessage,
+};
+
+pub struct SyncRequest {
+    pub from: Option<DataProposalHash>,
+    pub to: DataProposalHash,
+    pub validator: ValidatorPublicKey,
+}
+
+/// Submodule of Mempool dedicated to SyncRequest/SyncReply handling
+pub struct MempoolSync {
+    lane_id: LaneId,
+    /// Storage handle
+    lanes: LanesStorage,
+    /// Crypto handle
+    crypto: SharedBlstCrypto,
+    /// Metrics handle
+    _metrics: MempoolMetrics,
+    /// Keeping track of last time we sent a reply to the validator and the data proposal hash
+    by_pubkey_by_dp_hash: HashMap<ValidatorPublicKey, HashMap<DataProposalHash, TimestampMs>>,
+    /// Map containing per data proposal, which validators are interested in a sync reply
+    todo: HashMap<DataProposalHash, (LaneEntryMetadata, HashSet<ValidatorPublicKey>)>,
+    /// Network message channel
+    net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+    /// Chan where Mempool puts received Sync Requests to handle
+    sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
+}
+
+impl MempoolSync {
+    pub fn create(
+        lane_id: LaneId,
+        lanes: LanesStorage,
+        crypto: SharedBlstCrypto,
+        metrics: MempoolMetrics,
+        net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+        sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
+    ) -> MempoolSync {
+        MempoolSync {
+            lane_id,
+            lanes,
+            crypto,
+            _metrics: metrics,
+            net_sender,
+            sync_request_receiver,
+            by_pubkey_by_dp_hash: Default::default(),
+            todo: Default::default(),
+        }
+    }
+    pub async fn start(&mut self) -> anyhow::Result<()> {
+        info!("Starting MempoolSync");
+
+        let mut batched_replies_interval = tokio::time::interval(Duration::from_secs(1));
+        loop {
+            tokio::select! {
+                Some(sync_request) = self.sync_request_receiver.recv() => {
+
+                    tracing::warn!("SyncRequest received");
+                    _ = log_error!(
+                        self.unfold_sync_request_interval(sync_request).await,
+                        "Unfolding SyncRequest interval"
+                    );
+                }
+                _ = batched_replies_interval.tick() => {
+                    tracing::info!("Sending replies");
+                    self.send_replies().await;
+                }
+            }
+        }
+    }
+
+    /// Update the timestamp in the map, so we don't emit replies before some time
+    fn start_throttling_for(
+        &mut self,
+        validator: ValidatorPublicKey,
+        data_proposal_hash: DataProposalHash,
+    ) {
+        let now = TimestampMsClock::now();
+        self.by_pubkey_by_dp_hash
+            .entry(validator)
+            .or_default()
+            .insert(data_proposal_hash, now);
+    }
+
+    /// Reply can be emitted because
+    /// - it has never been emitted before
+    /// - it was emitted a long time ago
+    fn should_throttle(
+        &self,
+        validator: &ValidatorPublicKey,
+        data_proposal_hash: &DataProposalHash,
+    ) -> bool {
+        let now = TimestampMsClock::now();
+
+        let Some(data_proposal_record) = self
+            .by_pubkey_by_dp_hash
+            .get(validator)
+            .and_then(|validator_records| validator_records.get(data_proposal_hash))
+        else {
+            return false;
+        };
+
+        if now - data_proposal_record.clone() > Duration::from_secs(10) {
+            return false;
+        }
+
+        true
+    }
+
+    /// Fetches metadata from storage for the given interval, and populate the todo hashmap with it. Called everytime we get a new SyncRequest
+    async fn unfold_sync_request_interval(
+        &mut self,
+        SyncRequest {
+            from,
+            to,
+            validator,
+        }: SyncRequest,
+    ) -> anyhow::Result<()> {
+        if from.as_ref() == Some(&to) {
+            return Ok(());
+        }
+
+        pin! {
+            let stream = self.lanes.get_entries_metadata_between_hashes(&self.lane_id, from, Some(to));
+        };
+
+        while let Some(entry) = stream.next().await {
+            if let Ok((metadata, dp_hash)) =
+                log_warn!(entry, "Getting entry metada to prepare sync replies")
+            {
+                self.todo
+                    .entry(dp_hash)
+                    .or_insert((metadata, Default::default()))
+                    .1
+                    .insert(validator.clone());
+            }
+        }
+        Ok(())
+    }
+
+    /// Try to send replies based on what is stored in the todo hashmap. Every time a reply is sent, it stored a timestamp to throttle upcoming SyncRequests, and remove it from the todo hashmap
+    async fn send_replies(&mut self) {
+        for (dp_hash, (md, validators)) in self.todo.clone().into_iter() {
+            for validator in validators.into_iter() {
path: src/mempool/sync_request_reply.rs
user: login: wraitii
body: C'est "normal" on ne demande qu'au validateur en charge de la lane actuellement, mais oui
created_at: 2025-05-16T09:17:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,201 @@
+use std::{
+    collections::{HashMap, HashSet},
+    time::Duration,
+};
+
+use futures::StreamExt;
+use hyle_crypto::SharedBlstCrypto;
+use hyle_model::{utils::TimestampMs, DataProposalHash, LaneId, ValidatorPublicKey};
+use hyle_modules::{log_error, log_warn};
+use hyle_net::clock::TimestampMsClock;
+use tokio::pin;
+use tracing::info;
+
+use crate::p2p::network::{HeaderSigner, OutboundMessage};
+
+use super::{
+    metrics::MempoolMetrics,
+    storage::{LaneEntryMetadata, Storage},
+    storage_fjall::LanesStorage,
+    MempoolNetMessage,
+};
+
+pub struct SyncRequest {
+    pub from: Option<DataProposalHash>,
+    pub to: DataProposalHash,
+    pub validator: ValidatorPublicKey,
+}
+
+/// Submodule of Mempool dedicated to SyncRequest/SyncReply handling
+pub struct MempoolSync {
+    lane_id: LaneId,
+    /// Storage handle
+    lanes: LanesStorage,
+    /// Crypto handle
+    crypto: SharedBlstCrypto,
+    /// Metrics handle
+    _metrics: MempoolMetrics,
+    /// Keeping track of last time we sent a reply to the validator and the data proposal hash
+    by_pubkey_by_dp_hash: HashMap<ValidatorPublicKey, HashMap<DataProposalHash, TimestampMs>>,
+    /// Map containing per data proposal, which validators are interested in a sync reply
+    todo: HashMap<DataProposalHash, (LaneEntryMetadata, HashSet<ValidatorPublicKey>)>,
+    /// Network message channel
+    net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+    /// Chan where Mempool puts received Sync Requests to handle
+    sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
path: src/mempool/sync_request_reply.rs
user: login: hhalex
body: ça m'a paru lourd de filter 90% des messages
created_at: 2025-05-16T09:21:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,201 @@
+use std::{
+    collections::{HashMap, HashSet},
+    time::Duration,
+};
+
+use futures::StreamExt;
+use hyle_crypto::SharedBlstCrypto;
+use hyle_model::{utils::TimestampMs, DataProposalHash, LaneId, ValidatorPublicKey};
+use hyle_modules::{log_error, log_warn};
+use hyle_net::clock::TimestampMsClock;
+use tokio::pin;
+use tracing::info;
+
+use crate::p2p::network::{HeaderSigner, OutboundMessage};
+
+use super::{
+    metrics::MempoolMetrics,
+    storage::{LaneEntryMetadata, Storage},
+    storage_fjall::LanesStorage,
+    MempoolNetMessage,
+};
+
+pub struct SyncRequest {
+    pub from: Option<DataProposalHash>,
+    pub to: DataProposalHash,
+    pub validator: ValidatorPublicKey,
+}
+
+/// Submodule of Mempool dedicated to SyncRequest/SyncReply handling
+pub struct MempoolSync {
+    lane_id: LaneId,
+    /// Storage handle
+    lanes: LanesStorage,
+    /// Crypto handle
+    crypto: SharedBlstCrypto,
+    /// Metrics handle
+    _metrics: MempoolMetrics,
+    /// Keeping track of last time we sent a reply to the validator and the data proposal hash
+    by_pubkey_by_dp_hash: HashMap<ValidatorPublicKey, HashMap<DataProposalHash, TimestampMs>>,
+    /// Map containing per data proposal, which validators are interested in a sync reply
+    todo: HashMap<DataProposalHash, (LaneEntryMetadata, HashSet<ValidatorPublicKey>)>,
+    /// Network message channel
+    net_sender: tokio::sync::broadcast::Sender<OutboundMessage>,
+    /// Chan where Mempool puts received Sync Requests to handle
+    sync_request_receiver: tokio::sync::mpsc::Receiver<SyncRequest>,
path: src/mempool/sync_request_reply.rs
user: login: hhalex
body: et en vrai en re regardant, il y a de la logique côté mempool qui override la borne sup de l'intervalle avec le tip s'il est à None, donc c'est plus simple comme ça
created_at: 2025-05-16T09:26:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-15T17:56:11Z
body: ## [Codecov](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org) Report
Attention: Patch coverage is `79.72973%` with `60 lines` in your changes missing coverage. Please review.

:white_check_mark: All tests successful. No failed tests found.

| [Files with missing lines](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org) | Patch % | Lines |
|---|---|---|
| [src/mempool.rs](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&filepath=src%2Fmempool.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org#diff-c3JjL21lbXBvb2wucnM=) | 77.04% | [28 Missing :warning: ](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org) |
| [src/mempool/sync\_request\_reply.rs](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&filepath=src%2Fmempool%2Fsync_request_reply.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org#diff-c3JjL21lbXBvb2wvc3luY19yZXF1ZXN0X3JlcGx5LnJz) | 84.92% | [19 Missing :warning: ](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org) |
| [src/mempool/metrics.rs](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&filepath=src%2Fmempool%2Fmetrics.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org#diff-c3JjL21lbXBvb2wvbWV0cmljcy5ycw==) | 72.97% | [10 Missing :warning: ](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org) |
| [src/mempool/storage\_memory.rs](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&filepath=src%2Fmempool%2Fstorage_memory.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org#diff-c3JjL21lbXBvb2wvc3RvcmFnZV9tZW1vcnkucnM=) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org) |

| [Files with missing lines](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org) | Coverage Δ | |
|---|---|---|
| [src/mempool/own\_lane.rs](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&filepath=src%2Fmempool%2Fown_lane.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org#diff-c3JjL21lbXBvb2wvb3duX2xhbmUucnM=) | `76.81% <ø> (ø)` | |
| [src/mempool/storage\_fjall.rs](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&filepath=src%2Fmempool%2Fstorage_fjall.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org#diff-c3JjL21lbXBvb2wvc3RvcmFnZV9mamFsbC5ycw==) | `80.45% <100.00%> (+0.61%)` | :arrow_up: |
| [src/mempool/verify\_tx.rs](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&filepath=src%2Fmempool%2Fverify_tx.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org#diff-c3JjL21lbXBvb2wvdmVyaWZ5X3R4LnJz) | `53.72% <ø> (ø)` | |
| [src/mempool/storage\_memory.rs](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&filepath=src%2Fmempool%2Fstorage_memory.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org#diff-c3JjL21lbXBvb2wvc3RvcmFnZV9tZW1vcnkucnM=) | `69.46% <0.00%> (-1.28%)` | :arrow_down: |
| [src/mempool/metrics.rs](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&filepath=src%2Fmempool%2Fmetrics.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org#diff-c3JjL21lbXBvb2wvbWV0cmljcy5ycw==) | `92.24% <72.97%> (-7.76%)` | :arrow_down: |
| [src/mempool/sync\_request\_reply.rs](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&filepath=src%2Fmempool%2Fsync_request_reply.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org#diff-c3JjL21lbXBvb2wvc3luY19yZXF1ZXN0X3JlcGx5LnJz) | `84.92% <84.92%> (ø)` | |
| [src/mempool.rs](https://app.codecov.io/gh/hyli-org/hyli/pull/1201?src=pr&el=tree&filepath=src%2Fmempool.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hyli-org#diff-c3JjL21lbXBvb2wucnM=) | `83.39% <77.04%> (-0.05%)` | :arrow_down: |

author_association: NONE
user: login: hhalex
created_at: 2025-05-15T19:39:06Z
body: Missing tests, but surprisingly ready to review...
author_association: CONTRIBUTOR