Repository: mintlayer/mintlayer-core
Pull request #831 (2023-04-13 23:03:34)

Disconnect peers that ignores blocks and headers requests
We had a similar logic in the past, but it was done for all requests. The current approach is more akin to what is done in the Bitcoin codebase, though in the latter an additional timeout for "downloading all the headers" is used, but I'm not sure that we need it.


Patch:
diff --git a/dns_server/src/main.rs b/dns_server/src/main.rs
index 068405f4c..26c6d94de 100644
--- a/dns_server/src/main.rs
+++ b/dns_server/src/main.rs
@@ -65,6 +65,7 @@ async fn run(config: Arc<DnsServerConfig>) -> Result<void::Void, error::DnsServe
         max_message_size: Default::default(),
         max_peer_tx_announcements: Default::default(),
         max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Default::default(),
     });
 
     let transport = p2p::make_p2p_transport();
diff --git a/node/src/config_files/p2p.rs b/node/src/config_files/p2p.rs
index 909b0fab5..92108319a 100644
--- a/node/src/config_files/p2p.rs
+++ b/node/src/config_files/p2p.rs
@@ -105,6 +105,7 @@ impl From<P2pConfigFile> for P2pConfig {
             max_message_size: Default::default(),
             max_peer_tx_announcements: Default::default(),
             max_unconnected_headers: Default::default(),
+            sync_stalling_timeout: Default::default(),
         }
     }
 }
diff --git a/p2p/src/config.rs b/p2p/src/config.rs
index 8f7f723f9..f8894443d 100644
--- a/p2p/src/config.rs
+++ b/p2p/src/config.rs
@@ -34,6 +34,7 @@ make_config_setting!(RequestedBlocksLimit, usize, 500);
 make_config_setting!(MaxMessageSize, usize, 10 * 1024 * 1024);
 make_config_setting!(MaxPeerTxAnnouncements, usize, 5000);
 make_config_setting!(MaxUnconnectedHeaders, usize, 10);
+make_config_setting!(SyncStallingTimeout, Duration, Duration::from_secs(5));
 
 /// A node type.
 #[derive(Debug, Copy, Clone)]
@@ -112,4 +113,6 @@ pub struct P2pConfig {
     /// A maximum number of unconnected headers (block announcements) that a peer can send before
     /// it will be considered malicious.
     pub max_unconnected_headers: MaxUnconnectedHeaders,
+    /// A timeout after which a peer is disconnected.
+    pub sync_stalling_timeout: SyncStallingTimeout,
 }
diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs
index e90a8da9c..3839d9f1d 100644
--- a/p2p/src/lib.rs
+++ b/p2p/src/lib.rs
@@ -118,7 +118,7 @@ where
             Arc::clone(&p2p_config),
             conn,
             rx_peer_manager,
-            time_getter,
+            time_getter.clone(),
             peerdb_storage,
         )?;
         tokio::spawn(async move {
@@ -143,6 +143,7 @@ where
                     chainstate_handle,
                     mempool_handle_,
                     tx_peer_manager,
+                    time_getter,
                 )
                 .run()
                 .await
diff --git a/p2p/src/sync/mod.rs b/p2p/src/sync/mod.rs
index d72d6d918..0fe867561 100644
--- a/p2p/src/sync/mod.rs
+++ b/p2p/src/sync/mod.rs
@@ -33,6 +33,7 @@ use chainstate::chainstate_interface::ChainstateInterface;
 use common::{
     chain::{block::Block, config::ChainConfig},
     primitives::Id,
+    time_getter::TimeGetter,
 };
 use logging::log;
 use mempool::MempoolHandle;
@@ -72,6 +73,8 @@ pub struct BlockSyncManager<T: NetworkingService> {
 
     /// A mapping from a peer identifier to the channel.
     peers: HashMap<PeerId, UnboundedSender<SyncMessage>>,
+
+    time_getter: TimeGetter,
 }
 
 /// Syncing manager
@@ -82,6 +85,7 @@ where
     T::SyncingEventReceiver: SyncingEventReceiver,
 {
     /// Creates a new sync manager instance.
+    #[allow(clippy::too_many_arguments)]
     pub fn new(
         chain_config: Arc<ChainConfig>,
         p2p_config: Arc<P2pConfig>,
@@ -90,6 +94,7 @@ where
         chainstate_handle: subsystem::Handle<Box<dyn ChainstateInterface>>,
         mempool_handle: MempoolHandle,
         peer_manager_sender: UnboundedSender<PeerManagerEvent<T>>,
+        time_getter: TimeGetter,
     ) -> Self {
         Self {
             _chain_config: chain_config,
@@ -101,6 +106,7 @@ where
             mempool_handle,
             is_initial_block_download: Arc::new(true.into()),
             peers: Default::default(),
+            time_getter,
         }
     }
 
@@ -167,6 +173,7 @@ where
         let mempool_handle = self.mempool_handle.clone();
         let p2p_config = Arc::clone(&self.p2p_config);
         let is_initial_block_download = Arc::clone(&self.is_initial_block_download);
+        let time_getter = self.time_getter.clone();
         tokio::spawn(async move {
             let mut peer = Peer::<T>::new(
                 peer,
@@ -177,6 +184,7 @@ where
                 messaging_handle,
                 receiver,
                 is_initial_block_download,
+                time_getter,
             );
             if let Err(e) = peer.run().await {
                 log::error!("Sync manager peer ({}) error: {e:?}", peer.id());
diff --git a/p2p/src/sync/peer.rs b/p2p/src/sync/peer.rs
index ba83ec25f..d4f45ea2a 100644
--- a/p2p/src/sync/peer.rs
+++ b/p2p/src/sync/peer.rs
@@ -20,10 +20,14 @@ use std::{
         atomic::{AtomicBool, Ordering},
         Arc,
     },
+    time::Duration,
 };
 
 use itertools::Itertools;
-use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
+use tokio::{
+    sync::mpsc::{UnboundedReceiver, UnboundedSender},
+    time::MissedTickBehavior,
+};
 use void::Void;
 
 use chainstate::chainstate_interface::ChainstateInterface;
@@ -31,6 +35,7 @@ use chainstate::{ban_score::BanScore, BlockError, BlockSource, ChainstateError,
 use common::{
     chain::{block::BlockHeader, Block, Transaction},
     primitives::{BlockHeight, Id, Idable},
+    time_getter::TimeGetter,
 };
 use logging::log;
 use mempool::{
@@ -55,9 +60,7 @@ use crate::{
     MessagingService, PeerManagerEvent, Result,
 };
 
-// TODO: Investigate if we need some kind of "timeouts" (waiting for blocks or headers).
-// TODO: Track the block availability for a peer.
-// TODO: Track the best known block for a peer and take into account the chain work when syncing.
+// TODO: Take into account the chain work when syncing.
 /// A peer context.
 ///
 /// Syncing logic runs in a separate task for each peer.
@@ -87,6 +90,10 @@ pub struct Peer<T: NetworkingService> {
     /// A number of consecutive unconnected headers received from a peer. This counter is reset
     /// after receiving a valid header.
     unconnected_headers: usize,
+    /// A time when the last message from the peer is received. This field is equal to `None` if
+    /// we aren't waiting for specific messages.
+    last_activity: Option<Duration>,
+    time_getter: TimeGetter,
 }
 
 impl<T> Peer<T>
@@ -104,6 +111,7 @@ where
         messaging_handle: T::MessagingHandle,
         message_receiver: UnboundedReceiver<SyncMessage>,
         is_initial_block_download: Arc<AtomicBool>,
+        time_getter: TimeGetter,
     ) -> Self {
         let services = (*p2p_config.node_type).into();
 
@@ -123,6 +131,8 @@ where
             best_known_block: None,
             announced_transactions: BTreeSet::new(),
             unconnected_headers: 0,
+            last_activity: None,
+            time_getter,
         }
     }
 
@@ -132,7 +142,11 @@ where
     }
 
     pub async fn run(&mut self) -> Result<Void> {
+        let mut stalling_interval = tokio::time::interval(*self.p2p_config.sync_stalling_timeout);
+        stalling_interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
+
         self.request_headers().await?;
+        self.last_activity = Some(self.time_getter.get_time());
 
         loop {
             tokio::select! {
@@ -144,6 +158,10 @@ where
                 block_to_send_to_peer = async { self.blocks_queue.pop_front().expect("The block queue is empty") }, if !self.blocks_queue.is_empty() => {
                     self.send_block(block_to_send_to_peer).await?;
                 }
+
+                _ = stalling_interval.tick(), if self.last_activity.is_some() => {
+                    self.handle_stalling_interval().await?;
+                }
             }
         }
     }
@@ -256,6 +274,7 @@ where
 
     async fn handle_header_list(&mut self, headers: Vec<BlockHeader>) -> Result<()> {
         log::debug!("Headers list from peer {}", self.id());
+        self.last_activity = Some(self.time_getter.get_time());
 
         if !self.known_headers.is_empty() {
             // The headers list contains exactly one header when a new block is announced.
@@ -282,6 +301,11 @@ where
 
         // The empty headers list means that the peer doesn't have more blocks.
         if headers.is_empty() {
+            // We don't need anything from this peer unless we are still receiving blocks.
+            if self.requested_blocks.is_empty() {
+                self.last_activity = None;
+            }
+
             return Ok(());
         }
 
@@ -349,6 +373,7 @@ where
 
     async fn handle_block_response(&mut self, block: Block) -> Result<()> {
         log::debug!("Block ({}) from peer {}", block.get_id(), self.id());
+        self.last_activity = Some(self.time_getter.get_time());
 
         if self.requested_blocks.take(&block.get_id()).is_none() {
             return Err(P2pError::ProtocolError(ProtocolError::UnexpectedMessage(
@@ -422,6 +447,8 @@ where
         Ok(())
     }
 
+    // TODO: This can be optimized, see https://github.com/mintlayer/mintlayer-core/issues/829
+    // for details.
     async fn handle_transaction_announcement(&mut self, tx: Id<Transaction>) -> Result<()> {
         log::debug!("Transaction announcement from {} peer: {tx}", self.id());
 
@@ -577,4 +604,25 @@ where
             SyncMessage::BlockResponse(BlockResponse::new(block)),
         )
     }
+
+    async fn handle_stalling_interval(&mut self) -> Result<()> {
+        // Expect is OK here, because this function should only be called when the `last_activity`
+        // is set to something.
+        if self.time_getter.get_time()
+            < self.last_activity.expect("Last activity time is missing")
+                + *self.p2p_config.sync_stalling_timeout
+        {
+            return Ok(());
+        }
+
+        // Nodes can disconnect each other if all of them are in the initial block download state,
+        // but this should never occur in a normal network and can be worked around in the tests.
+        let (sender, receiver) = oneshot_nofail::channel();
+        log::warn!("Disconnecting peer for ignoring requests");
+        self.peer_manager_sender.send(PeerManagerEvent::Disconnect(self.id(), sender))?;
+        receiver.await?.or_else(|e| match e {
+            P2pError::PeerError(PeerError::PeerDoesntExist) => Ok(()),
+            e => Err(e),
+        })
+    }
 }


Test patch:
diff --git a/p2p/backend-test-suite/src/ban.rs b/p2p/backend-test-suite/src/ban.rs
index edacc3ede..69e6a4019 100644
--- a/p2p/backend-test-suite/src/ban.rs
+++ b/p2p/backend-test-suite/src/ban.rs
@@ -21,6 +21,7 @@ use chainstate::{ban_score::BanScore, BlockError, ChainstateError, CheckBlockErr
 use common::{
     chain::block::{timestamp::BlockTimestamp, Block, BlockReward, ConsensusData},
     primitives::Idable,
+    time_getter::TimeGetter,
 };
 
 use p2p::{
@@ -70,6 +71,7 @@ where
         chainstate,
         mempool,
         tx_peer_manager,
+        TimeGetter::default(),
     );
 
     let (mut conn2, mut messaging_handle_2, mut sync2) = N::start(
diff --git a/p2p/backend-test-suite/src/block_announcement.rs b/p2p/backend-test-suite/src/block_announcement.rs
index 054757cd2..6fea35c49 100644
--- a/p2p/backend-test-suite/src/block_announcement.rs
+++ b/p2p/backend-test-suite/src/block_announcement.rs
@@ -157,6 +157,7 @@ where
         max_message_size: Default::default(),
         max_peer_tx_announcements: Default::default(),
         max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Default::default(),
     });
     let (mut conn1, mut messaging_handle1, _sync1) = N::start(
         T::make_transport(),
diff --git a/p2p/src/peer_manager/peerdb/tests.rs b/p2p/src/peer_manager/peerdb/tests.rs
index 0d9549b7d..5befedac3 100644
--- a/p2p/src/peer_manager/peerdb/tests.rs
+++ b/p2p/src/peer_manager/peerdb/tests.rs
@@ -56,6 +56,7 @@ fn unban_peer() {
             max_message_size: Default::default(),
             max_peer_tx_announcements: Default::default(),
             max_unconnected_headers: Default::default(),
+            sync_stalling_timeout: Default::default(),
         }),
         time_getter.get_time_getter(),
         db_store,
diff --git a/p2p/src/peer_manager/tests/connections.rs b/p2p/src/peer_manager/tests/connections.rs
index 34ba98fc3..72e7bdfe2 100644
--- a/p2p/src/peer_manager/tests/connections.rs
+++ b/p2p/src/peer_manager/tests/connections.rs
@@ -683,6 +683,7 @@ where
         max_message_size: Default::default(),
         max_peer_tx_announcements: Default::default(),
         max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Default::default(),
     });
     let tx1 = run_peer_manager::<T>(
         A::make_transport(),
@@ -721,6 +722,7 @@ where
         max_message_size: Default::default(),
         max_peer_tx_announcements: Default::default(),
         max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Default::default(),
     });
     let tx1 = run_peer_manager::<T>(
         A::make_transport(),
@@ -802,6 +804,7 @@ where
         max_message_size: Default::default(),
         max_peer_tx_announcements: Default::default(),
         max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Default::default(),
     });
     let tx1 = run_peer_manager::<T>(
         A::make_transport(),
@@ -841,6 +844,7 @@ where
         max_message_size: Default::default(),
         max_peer_tx_announcements: Default::default(),
         max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Default::default(),
     });
     let tx2 = run_peer_manager::<T>(
         A::make_transport(),
@@ -873,6 +877,7 @@ where
         max_message_size: Default::default(),
         max_peer_tx_announcements: Default::default(),
         max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Default::default(),
     });
     let tx3 = run_peer_manager::<T>(
         A::make_transport(),
diff --git a/p2p/src/peer_manager/tests/ping.rs b/p2p/src/peer_manager/tests/ping.rs
index 6dd482670..4ad9a30f4 100644
--- a/p2p/src/peer_manager/tests/ping.rs
+++ b/p2p/src/peer_manager/tests/ping.rs
@@ -62,6 +62,7 @@ async fn ping_timeout() {
         max_message_size: Default::default(),
         max_peer_tx_announcements: Default::default(),
         max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Default::default(),
     });
     let ping_check_period = *p2p_config.ping_check_period;
     let ping_timeout = *p2p_config.ping_timeout;
diff --git a/p2p/src/sync/tests/block_announcement.rs b/p2p/src/sync/tests/block_announcement.rs
index 5cb952b3e..65f780a90 100644
--- a/p2p/src/sync/tests/block_announcement.rs
+++ b/p2p/src/sync/tests/block_announcement.rs
@@ -226,6 +226,7 @@ async fn unconnected_headers(#[case] seed: Seed) {
         max_message_size: Default::default(),
         max_peer_tx_announcements: Default::default(),
         max_unconnected_headers: 1.into(),
+        sync_stalling_timeout: Default::default(),
     });
     let mut handle = SyncManagerHandle::builder()
         .with_chain_config(Arc::clone(&chain_config))
diff --git a/p2p/src/sync/tests/block_response.rs b/p2p/src/sync/tests/block_response.rs
index a4933060a..b8f44870f 100644
--- a/p2p/src/sync/tests/block_response.rs
+++ b/p2p/src/sync/tests/block_response.rs
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use std::sync::Arc;
+use std::{sync::Arc, time::Duration};
 
 use chainstate::ban_score::BanScore;
 use chainstate_test_framework::TestFramework;
@@ -23,12 +23,13 @@ use p2p_test_utils::{create_n_blocks, start_subsystems_with_chainstate};
 use test_utils::random::Seed;
 
 use crate::{
+    config::NodeType,
     error::ProtocolError,
     message::{BlockListRequest, BlockResponse, HeaderList, SyncMessage},
     net::types::SyncingEvent,
     sync::tests::helpers::SyncManagerHandle,
     types::peer_id::PeerId,
-    P2pError,
+    P2pConfig, P2pError,
 };
 
 #[rstest::rstest]
@@ -174,3 +175,67 @@ async fn valid_response(#[case] seed: Seed) {
 
     handle.assert_no_error().await;
 }
+
+#[rstest::rstest]
+#[trace]
+#[case(Seed::from_entropy())]
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn disconnect(#[case] seed: Seed) {
+    let mut rng = test_utils::random::make_seedable_rng(seed);
+
+    let chain_config = Arc::new(create_unit_test_config());
+    let mut tf = TestFramework::builder(&mut rng)
+        .with_chain_config(chain_config.as_ref().clone())
+        .build();
+    let block = tf.make_block_builder().build();
+    let (chainstate, mempool) =
+        start_subsystems_with_chainstate(tf.into_chainstate(), Arc::clone(&chain_config)).await;
+
+    let p2p_config = Arc::new(P2pConfig {
+        bind_addresses: Default::default(),
+        socks5_proxy: Default::default(),
+        disable_noise: Default::default(),
+        boot_nodes: Default::default(),
+        reserved_nodes: Default::default(),
+        max_inbound_connections: Default::default(),
+        ban_threshold: Default::default(),
+        ban_duration: Default::default(),
+        outbound_connection_timeout: Default::default(),
+        ping_check_period: Default::default(),
+        ping_timeout: Default::default(),
+        node_type: NodeType::Full.into(),
+        allow_discover_private_ips: Default::default(),
+        msg_header_count_limit: Default::default(),
+        msg_max_locator_count: Default::default(),
+        max_request_blocks_count: Default::default(),
+        user_agent: "test".try_into().unwrap(),
+        max_message_size: Default::default(),
+        max_peer_tx_announcements: Default::default(),
+        max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Duration::from_millis(100).into(),
+    });
+    let mut handle = SyncManagerHandle::builder()
+        .with_chain_config(chain_config)
+        .with_p2p_config(Arc::clone(&p2p_config))
+        .with_subsystems(chainstate, mempool)
+        .build()
+        .await;
+
+    let peer = PeerId::new();
+    handle.connect_peer(peer).await;
+
+    handle.send_message(
+        peer,
+        SyncMessage::HeaderList(HeaderList::new(vec![block.header().clone()])),
+    );
+
+    let (sent_to, message) = handle.message().await;
+    assert_eq!(peer, sent_to);
+    assert_eq!(
+        message,
+        SyncMessage::BlockListRequest(BlockListRequest::new(vec![block.get_id()]))
+    );
+
+    tokio::time::sleep(Duration::from_millis(300)).await;
+    handle.assert_disconnect_peer_event(peer).await;
+}
diff --git a/p2p/src/sync/tests/header_list_response.rs b/p2p/src/sync/tests/header_list_response.rs
index 3c09c68ac..22dfc47c2 100644
--- a/p2p/src/sync/tests/header_list_response.rs
+++ b/p2p/src/sync/tests/header_list_response.rs
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use std::{iter, sync::Arc};
+use std::{iter, sync::Arc, time::Duration};
 
 use chainstate::ban_score::BanScore;
 use chainstate_test_framework::TestFramework;
@@ -22,12 +22,13 @@ use p2p_test_utils::{create_n_blocks, start_subsystems_with_chainstate};
 use test_utils::random::Seed;
 
 use crate::{
+    config::NodeType,
     error::ProtocolError,
     message::{BlockListRequest, HeaderList, SyncMessage},
     sync::tests::helpers::SyncManagerHandle,
     testing_utils::test_p2p_config,
     types::peer_id::PeerId,
-    P2pError,
+    P2pConfig, P2pError,
 };
 
 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -200,3 +201,40 @@ async fn valid_headers(#[case] seed: Seed) {
 
     handle.assert_no_error().await;
 }
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn disconnect() {
+    let p2p_config = Arc::new(P2pConfig {
+        bind_addresses: Default::default(),
+        socks5_proxy: Default::default(),
+        disable_noise: Default::default(),
+        boot_nodes: Default::default(),
+        reserved_nodes: Default::default(),
+        max_inbound_connections: Default::default(),
+        ban_threshold: Default::default(),
+        ban_duration: Default::default(),
+        outbound_connection_timeout: Default::default(),
+        ping_check_period: Default::default(),
+        ping_timeout: Default::default(),
+        node_type: NodeType::Full.into(),
+        allow_discover_private_ips: Default::default(),
+        msg_header_count_limit: Default::default(),
+        msg_max_locator_count: Default::default(),
+        max_request_blocks_count: Default::default(),
+        user_agent: "test".try_into().unwrap(),
+        max_message_size: Default::default(),
+        max_peer_tx_announcements: Default::default(),
+        max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Duration::from_millis(100).into(),
+    });
+    let mut handle = SyncManagerHandle::builder()
+        .with_p2p_config(Arc::clone(&p2p_config))
+        .build()
+        .await;
+
+    let peer = PeerId::new();
+    handle.connect_peer(peer).await;
+
+    tokio::time::sleep(Duration::from_millis(300)).await;
+    handle.assert_disconnect_peer_event(peer).await;
+}
diff --git a/p2p/src/sync/tests/helpers.rs b/p2p/src/sync/tests/helpers.rs
index 7e05f7279..e7fa970a9 100644
--- a/p2p/src/sync/tests/helpers.rs
+++ b/p2p/src/sync/tests/helpers.rs
@@ -28,7 +28,10 @@ use tokio::{
 };
 
 use chainstate::chainstate_interface::ChainstateInterface;
-use common::chain::{config::create_mainnet, ChainConfig};
+use common::{
+    chain::{config::create_mainnet, ChainConfig},
+    time_getter::TimeGetter,
+};
 use mempool::MempoolHandle;
 use p2p_test_utils::start_subsystems;
 
@@ -94,6 +97,7 @@ impl SyncManagerHandle {
             chainstate,
             mempool,
             peer_manager_sender,
+            TimeGetter::default(),
         );
 
         let (error_sender, error_receiver) = mpsc::unbounded_channel();
@@ -176,6 +180,16 @@ impl SyncManagerHandle {
         }
     }
 
+    pub async fn assert_disconnect_peer_event(&mut self, id: PeerId) {
+        match self.peer_manager_receiver.recv().await.unwrap() {
+            PeerManagerEvent::Disconnect(peer_id, sender) => {
+                assert_eq!(id, peer_id);
+                sender.send(Ok(()));
+            }
+            e => panic!("Expected PeerManagerEvent::Disconnect, received: {e:?}"),
+        }
+    }
+
     /// Panics if there is an event from the peer manager.
     pub async fn assert_no_peer_manager_event(&mut self) {
         time::timeout(SHORT_TIMEOUT, self.peer_manager_receiver.recv())
diff --git a/p2p/src/sync/tests/tx_announcement.rs b/p2p/src/sync/tests/tx_announcement.rs
index 155dd65ff..6cdaa24a1 100644
--- a/p2p/src/sync/tests/tx_announcement.rs
+++ b/p2p/src/sync/tests/tx_announcement.rs
@@ -163,6 +163,7 @@ async fn no_transaction_service(#[case] seed: Seed) {
         max_message_size: Default::default(),
         max_peer_tx_announcements: Default::default(),
         max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Default::default(),
     });
     let mut handle = SyncManagerHandle::builder()
         .with_chain_config(Arc::clone(&chain_config))
@@ -223,6 +224,7 @@ async fn too_many_announcements(#[case] seed: Seed) {
         max_message_size: Default::default(),
         max_peer_tx_announcements: 0.into(),
         max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Default::default(),
     });
     let mut handle = SyncManagerHandle::builder()
         .with_chain_config(Arc::clone(&chain_config))
diff --git a/p2p/src/testing_utils.rs b/p2p/src/testing_utils.rs
index 39cecfe4b..34afe1a1a 100644
--- a/p2p/src/testing_utils.rs
+++ b/p2p/src/testing_utils.rs
@@ -330,6 +330,7 @@ pub fn test_p2p_config() -> P2pConfig {
         max_message_size: Default::default(),
         max_peer_tx_announcements: Default::default(),
         max_unconnected_headers: Default::default(),
+        sync_stalling_timeout: Default::default(),
     }
 }