Skip to main content

forest/libp2p_bitswap/
request_manager.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4//! Request manager implementation that is optimized for `filecoin` network
5//! usage
6
7use std::time::{Duration, Instant};
8
9use crate::cid_collections::CidHashMap;
10use crate::prelude::*;
11use ahash::HashSet;
12use flume::TryRecvError;
13use futures::StreamExt;
14use libp2p::PeerId;
15use parking_lot::RwLock;
16
17use crate::libp2p_bitswap::{event_handlers::*, *};
18
19const BITSWAP_BLOCK_REQUEST_INTERVAL: Duration = Duration::from_millis(500);
20
21pub type ValidatePeerCallback = dyn Fn(PeerId) -> bool + Send + Sync;
22
23#[derive(Debug, Clone)]
24struct ResponseChannels {
25    block_have: flume::Sender<PeerId>,
26    block_received: flume::Sender<Option<Vec<u8>>>,
27}
28
29/// Request manager implementation that is optimized for Filecoin network
30/// usage
31pub struct BitswapRequestManager {
32    // channel for outbound `have` requests
33    outbound_have_request_tx: flume::Sender<(PeerId, Cid)>,
34    outbound_have_request_rx: flume::Receiver<(PeerId, Cid)>,
35    // channel for outbound `cancel` requests
36    outbound_cancel_request_tx: flume::Sender<(PeerId, Cid)>,
37    outbound_cancel_request_rx: flume::Receiver<(PeerId, Cid)>,
38    // channel for outbound `block` requests
39    outbound_block_request_tx: flume::Sender<(PeerId, Cid)>,
40    outbound_block_request_rx: flume::Receiver<(PeerId, Cid)>,
41    peers: RwLock<HashSet<PeerId>>,
42    response_channels: RwLock<CidHashMap<ResponseChannels>>,
43}
44
45impl BitswapRequestManager {
46    /// A receiver channel of the outbound `bitswap` network requests that the
47    /// [`BitswapRequestManager`] emits. The messages from this channel need
48    /// to be sent with [`BitswapBehaviour::send_request`] to make
49    /// [`BitswapRequestManager::get_block`] work.
50    pub fn outbound_request_stream(
51        &self,
52    ) -> impl futures::stream::Stream<Item = (PeerId, BitswapRequest)> + '_ {
53        type MapperType = fn((libp2p::PeerId, Cid)) -> (libp2p::PeerId, BitswapRequest);
54
55        fn new_block((peer, cid): (PeerId, Cid)) -> (PeerId, BitswapRequest) {
56            (peer, BitswapRequest::new_block(cid).send_dont_have(false))
57        }
58
59        fn new_have((peer, cid): (PeerId, Cid)) -> (PeerId, BitswapRequest) {
60            (peer, BitswapRequest::new_have(cid).send_dont_have(false))
61        }
62
63        fn new_cancel((peer, cid): (PeerId, Cid)) -> (PeerId, BitswapRequest) {
64            (peer, BitswapRequest::new_cancel(cid).send_dont_have(false))
65        }
66
67        // Use separate channels here to not block `block` requests when too many other type of requests are queued.
68        let streams = vec![
69            self.outbound_block_request_rx
70                .stream()
71                .map(new_block as MapperType),
72            self.outbound_have_request_rx
73                .stream()
74                .map(new_have as MapperType),
75            self.outbound_cancel_request_rx
76                .stream()
77                .map(new_cancel as MapperType),
78        ];
79        futures::stream::select_all(streams)
80    }
81}
82
83impl Default for BitswapRequestManager {
84    fn default() -> Self {
85        let (outbound_have_request_tx, outbound_have_request_rx) = flume::unbounded();
86        let (outbound_cancel_request_tx, outbound_cancel_request_rx) = flume::unbounded();
87        let (outbound_block_request_tx, outbound_block_request_rx) = flume::unbounded();
88        Self {
89            outbound_have_request_tx,
90            outbound_have_request_rx,
91            outbound_cancel_request_tx,
92            outbound_cancel_request_rx,
93            outbound_block_request_tx,
94            outbound_block_request_rx,
95            peers: RwLock::new(HashSet::new()),
96            response_channels: RwLock::new(CidHashMap::new()),
97        }
98    }
99}
100
101impl BitswapRequestManager {
102    /// Hook the `bitswap` network event into the [`BitswapRequestManager`]
103    pub fn handle_event<S: BitswapStoreRead>(
104        self: &Arc<Self>,
105        bitswap: &mut BitswapBehaviour,
106        store: &S,
107        event: BitswapBehaviourEvent,
108    ) -> anyhow::Result<()> {
109        handle_event_impl(self, bitswap, store, event)
110    }
111
112    /// Gets a block, writing it to the given block store that implements
113    /// [`BitswapStoreReadWrite`] and respond to the channel. Note: this
114    /// method is a non-blocking, it is intended to return immediately.
115    #[cfg(not(target_arch = "wasm32"))]
116    pub fn get_block(
117        self: Arc<Self>,
118        store: impl BitswapStoreReadWrite + ShallowClone,
119        cid: Cid,
120        timeout: Duration,
121        responder: Option<flume::Sender<bool>>,
122        validate_peer: Option<Arc<ValidatePeerCallback>>,
123    ) {
124        let start = Instant::now();
125        task::spawn(async move {
126            let mut success = store.contains(&cid).unwrap_or_default();
127            if !success {
128                let deadline = start.checked_add(timeout).expect("Infallible");
129                success = task::spawn_blocking({
130                    let store = store.shallow_clone();
131                    move || self.get_block_sync(&store, cid, deadline, validate_peer)
132                })
133                .await
134                .unwrap_or_default();
135                // Spin check db when `get_block_sync` fails fast,
136                // which means there is other task actually processing the same `cid`
137                while !success && Instant::now() < deadline {
138                    task::sleep(BITSWAP_BLOCK_REQUEST_INTERVAL).await;
139                    success = store.contains(&cid).unwrap_or_default();
140                }
141            }
142
143            if success {
144                metrics::message_counter_get_block_success().inc();
145            } else {
146                metrics::message_counter_get_block_failure().inc();
147            }
148
149            if let Some(responder) = responder
150                && let Err(e) = responder.send_async(success).await
151            {
152                debug!("{e}");
153            }
154
155            metrics::GET_BLOCK_TIME.observe((Instant::now() - start).as_secs_f64());
156        });
157    }
158
159    fn get_block_sync(
160        &self,
161        store: &impl BitswapStoreReadWrite,
162        cid: Cid,
163        deadline: Instant,
164        validate_peer: Option<Arc<ValidatePeerCallback>>,
165    ) -> bool {
166        // Fail fast here when the given `cid` is being processed by other tasks
167        if self.response_channels.read().contains_key(&cid) {
168            return false;
169        }
170
171        let (block_have_tx, block_have_rx) = flume::unbounded();
172        let (block_saved_tx, block_saved_rx) = flume::unbounded();
173        let channels = ResponseChannels {
174            block_have: block_have_tx,
175            block_received: block_saved_tx,
176        };
177        {
178            self.response_channels.write().insert(cid, channels);
179        }
180
181        let peers: Vec<_> = self.peers.read().iter().cloned().collect();
182        let validated_peers: Vec<_> = peers
183            .iter()
184            .filter(|&&p| validate_peer.as_ref().map(|f| f(p)).unwrap_or(true))
185            .cloned()
186            .collect();
187
188        debug!("Found {} valid peers for {cid}", validated_peers.len());
189        let selected_peers = if validated_peers.is_empty() {
190            // Fallback to all peers
191            peers
192        } else {
193            validated_peers
194        };
195
196        for peer in selected_peers {
197            if let Err(e) = self.outbound_have_request_tx.send((peer, cid)) {
198                debug!("{e}");
199            }
200        }
201
202        let mut success = false;
203        let mut block_data = None;
204        while !success && Instant::now() < deadline {
205            match block_have_rx.try_recv() {
206                Ok(peer) => {
207                    _ = self.outbound_block_request_tx.send((peer, cid));
208                }
209                Err(TryRecvError::Empty) => {}
210                Err(TryRecvError::Disconnected) => {
211                    break;
212                }
213            }
214
215            if let Ok(data) = block_saved_rx.recv_timeout(BITSWAP_BLOCK_REQUEST_INTERVAL) {
216                success = true;
217                block_data = data;
218            }
219        }
220
221        if !success && let Ok(data) = block_saved_rx.recv_deadline(deadline) {
222            success = true;
223            block_data = data;
224        }
225
226        if let Some(data) = block_data {
227            success = match Block::new(cid, data) {
228                Ok(block) => match store.insert(&block) {
229                    Ok(()) => {
230                        metrics::message_counter_inbound_response_block_update_db().inc();
231                        true
232                    }
233                    Err(e) => {
234                        metrics::message_counter_inbound_response_block_update_db_failure().inc();
235                        warn!(
236                            "Failed to update db, cid: {cid}, data: {:?}, error: {e:#}",
237                            block.data()
238                        );
239                        false
240                    }
241                },
242                Err(e) => {
243                    warn!("Failed to construct block, cid: {cid}, error: {e:#}");
244                    false
245                }
246            };
247        }
248
249        // Cleanup
250        {
251            let mut response_channels = self.response_channels.write();
252            if response_channels.remove(&cid).is_some() {
253                response_channels.shrink_to_fit();
254                metrics::response_channel_container_capacity()
255                    .set(response_channels.total_capacity() as _);
256            }
257        }
258
259        success
260    }
261
262    pub(in crate::libp2p_bitswap) fn on_inbound_response_event<S: BitswapStoreRead>(
263        &self,
264        store: &S,
265        response: BitswapInboundResponseEvent,
266    ) {
267        use BitswapInboundResponseEvent::*;
268
269        match response {
270            HaveBlock(peer, cid) => {
271                if let Some(chans) = self.response_channels.read().get(&cid) {
272                    _ = chans.block_have.send(peer);
273                }
274            }
275            DataBlock(_peer, cid, data) => {
276                if let Some(chans) = self.response_channels.read().get(&cid) {
277                    if let Ok(true) = store.contains(&cid) {
278                        // Avoid duplicate writes, still notify the receiver
279                        metrics::message_counter_inbound_response_block_already_exists_in_db()
280                            .inc();
281                        _ = chans.block_received.send(None);
282                    } else {
283                        _ = chans.block_received.send(Some(data));
284                    }
285
286                    // <https://github.com/ipfs/go-libipfs/tree/main/bitswap#background>
287                    // When a node receives blocks that it asked for, the node should send out a
288                    // notification called a 'Cancel' to tell its peers that the
289                    // node no longer wants those blocks.
290                    for &peer in self.peers.read().iter() {
291                        if let Err(e) = self.outbound_cancel_request_tx.send((peer, cid)) {
292                            debug!("{e}");
293                        }
294                    }
295                } else {
296                    metrics::message_counter_inbound_response_block_not_requested().inc();
297                }
298            }
299        }
300    }
301
302    pub(in crate::libp2p_bitswap) fn on_peer_connected(&self, peer: PeerId) -> bool {
303        let mut peers = self.peers.write();
304        let success = peers.insert(peer);
305        if success {
306            metrics::peer_container_capacity().set(peers.capacity() as _);
307        }
308        success
309    }
310
311    pub(in crate::libp2p_bitswap) fn on_peer_disconnected(&self, peer: &PeerId) -> bool {
312        let mut peers = self.peers.write();
313        let success = peers.remove(peer);
314        if success {
315            peers.shrink_to_fit();
316            metrics::peer_container_capacity().set(peers.capacity() as _);
317        }
318        success
319    }
320}