Skip to main content

ab_networking/utils/
piece_provider.rs

1//! Provides methods to retrieve pieces from DSN.
2
3use crate::constructor::DummyRecordStore;
4use crate::protocols::request_response::handlers::cached_piece_by_index::{
5    CachedPieceByIndexRequest, CachedPieceByIndexResponse, PieceResult,
6};
7use crate::protocols::request_response::handlers::piece_by_index::{
8    PieceByIndexRequest, PieceByIndexResponse,
9};
10use crate::utils::multihash::ToMultihash;
11use crate::{Multihash, Node};
12use ab_core_primitives::pieces::{Piece, PieceIndex};
13use async_lock::{Semaphore, SemaphoreGuard};
14use async_trait::async_trait;
15use futures::channel::mpsc;
16use futures::future::FusedFuture;
17use futures::stream::FuturesUnordered;
18use futures::task::noop_waker_ref;
19use futures::{FutureExt, Stream, StreamExt, stream};
20use libp2p::kad::{Behaviour as Kademlia, KBucketKey, RecordKey};
21use libp2p::swarm::NetworkBehaviour;
22use libp2p::{Multiaddr, PeerId};
23use rand::prelude::*;
24use std::any::type_name;
25use std::collections::{HashMap, HashSet};
26use std::fmt;
27use std::future::ready;
28use std::pin::Pin;
29use std::sync::Arc as StdArc;
30use std::task::{Context, Poll};
31use tokio_stream::StreamMap;
32use tracing::{Instrument, debug, trace, warn};
33use triomphe::Arc;
34
35/// Validates piece against using its root.
36#[async_trait]
37pub trait PieceValidator: Sync + Send {
38    /// Validates piece against using its root.
39    async fn validate_piece(
40        &self,
41        source_peer_id: PeerId,
42        piece_index: PieceIndex,
43        piece: Piece,
44    ) -> Option<Piece>;
45}
46
47/// Stub implementation for piece validation.
48#[derive(Debug, Clone, Copy)]
49pub struct NoPieceValidator;
50
51#[async_trait]
52impl PieceValidator for NoPieceValidator {
53    async fn validate_piece(&self, _: PeerId, _: PieceIndex, piece: Piece) -> Option<Piece> {
54        Some(piece)
55    }
56}
57
58/// Piece provider with cancellation and piece validator.
59/// Use `NoPieceValidator` to disable validation.
60#[derive(Clone)]
61pub struct PieceProvider<PV> {
62    node: Node,
63    piece_validator: PV,
64    piece_downloading_semaphore: Arc<Semaphore>,
65}
66
67impl<PV> fmt::Debug for PieceProvider<PV> {
68    #[inline]
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        f.debug_struct(&format!("PieceProvider<{}>", type_name::<PV>()))
71            .finish_non_exhaustive()
72    }
73}
74
75impl<PV> PieceProvider<PV>
76where
77    PV: PieceValidator,
78{
79    /// Creates new piece provider.
80    pub fn new(
81        node: Node,
82        piece_validator: PV,
83        piece_downloading_semaphore: Arc<Semaphore>,
84    ) -> Self {
85        Self {
86            node,
87            piece_validator,
88            piece_downloading_semaphore,
89        }
90    }
91
92    /// Get pieces with provided indices from cache.
93    ///
94    /// Number of elements in returned stream is the same as number of unique `piece_indices`.
95    pub fn get_from_cache<'a, PieceIndices>(
96        &'a self,
97        piece_indices: PieceIndices,
98    ) -> impl Future<Output = impl Stream<Item = (PieceIndex, Option<Piece>)> + Unpin + 'a>
99    where
100        PieceIndices: IntoIterator<Item = PieceIndex> + 'a,
101    {
102        let download_id = rand::random::<u64>();
103        let (tx, mut rx) = mpsc::unbounded();
104        let fut = async move {
105            let not_downloaded_pieces = download_cached_pieces(
106                piece_indices,
107                &self.node,
108                &self.piece_validator,
109                &tx,
110                &self.piece_downloading_semaphore,
111            )
112            .await;
113
114            if not_downloaded_pieces.is_empty() {
115                debug!("Done");
116                return;
117            }
118
119            for piece_index in not_downloaded_pieces {
120                tx.unbounded_send((piece_index, None))
121                    .expect("This future isn't polled after receiver is dropped; qed");
122            }
123
124            debug!("Done #2");
125        };
126
127        let mut fut = Box::pin(fut.instrument(tracing::info_span!("", %download_id)).fuse());
128
129        // Drive above future and stream back any pieces that were downloaded so far
130        ready(stream::poll_fn(move |cx| {
131            if !fut.is_terminated() {
132                // Result doesn't matter, we'll need to poll stream below anyway
133                let _: Poll<()> = fut.poll_unpin(cx);
134            }
135
136            if let Poll::Ready(maybe_result) = rx.poll_next_unpin(cx) {
137                return Poll::Ready(maybe_result);
138            }
139
140            // Exit will be done by the stream above
141            Poll::Pending
142        }))
143    }
144
145    /// Returns piece by its index from farmer's piece cache (L2)
146    pub async fn get_piece_from_cache(&self, piece_index: PieceIndex) -> Option<Piece> {
147        let key = RecordKey::from(piece_index.to_multihash());
148
149        let request_batch = self.node.get_requests_batch_handle().await;
150        let mut get_providers_stream = request_batch
151            .get_providers(key.clone())
152            .await
153            .inspect_err(|err| warn!(%piece_index,?key, ?err, "get_providers returned an error"))
154            .ok()?;
155
156        let key = hex::encode(&key);
157        while let Some(provider_id) = get_providers_stream.next().await {
158            trace!(%piece_index, key, %provider_id, "get_providers returned an item");
159
160            let Ok(PieceByIndexResponse {
161                piece,
162                cached_pieces: _,
163            }) = request_batch
164                .send_generic_request(
165                    provider_id,
166                    Vec::new(),
167                    PieceByIndexRequest {
168                        piece_index,
169                        cached_pieces: StdArc::default(),
170                    },
171                )
172                .await
173                .inspect_err(
174                    |error| debug!(%piece_index, key, %provider_id, ?error, "Piece request failed"),
175                )
176            else {
177                continue;
178            };
179
180            if let Some(piece) = piece {
181                trace!(%piece_index, key, %provider_id, "Piece request succeeded");
182
183                return self
184                    .piece_validator
185                    .validate_piece(provider_id, piece_index, piece)
186                    .await;
187            }
188
189            debug!(%piece_index, key, %provider_id, "Piece request returned empty piece");
190        }
191
192        None
193    }
194
195    /// Get piece from a particular peer.
196    pub async fn get_piece_from_peer(
197        &self,
198        peer_id: PeerId,
199        piece_index: PieceIndex,
200    ) -> Option<Piece> {
201        // TODO: Take advantage of `cached_pieces`
202        let PieceByIndexResponse {
203            piece,
204            cached_pieces: _,
205        } = self
206            .node
207            .send_generic_request(
208                peer_id,
209                Vec::new(),
210                PieceByIndexRequest {
211                    piece_index,
212                    cached_pieces: StdArc::default(),
213                },
214            )
215            .await
216            .inspect_err(|error| debug!(%peer_id, %piece_index, ?error, "Piece request failed"))
217            .ok()?;
218
219        if let Some(piece) = piece {
220            trace!(%peer_id, %piece_index, "Piece request succeeded");
221
222            return self
223                .piece_validator
224                .validate_piece(peer_id, piece_index, piece)
225                .await;
226        }
227
228        debug!(%peer_id, %piece_index, "Piece request returned empty piece");
229
230        None
231    }
232
233    /// Get piece from archival storage (L1). The algorithm tries to get a piece from currently
234    /// connected peers and falls back to random walking.
235    pub async fn get_piece_from_archival_storage(
236        &self,
237        piece_index: PieceIndex,
238        max_random_walking_rounds: usize,
239    ) -> Option<Piece> {
240        // TODO: consider using retry policy for L1 lookups as well.
241        trace!(%piece_index, "Getting piece from archival storage..");
242
243        let connected_servers = {
244            let connected_servers = match self.node.connected_servers().await {
245                Ok(connected_servers) => connected_servers,
246                Err(err) => {
247                    debug!(%piece_index, ?err, "Cannot get connected peers (DSN L1 lookup)");
248
249                    Vec::new()
250                }
251            };
252
253            HashSet::<PeerId>::from_iter(connected_servers)
254        };
255
256        if connected_servers.is_empty() {
257            debug!(%piece_index, "Cannot acquire piece from no connected peers (DSN L1 lookup)");
258        } else {
259            for peer_id in connected_servers {
260                let maybe_piece = self.get_piece_from_peer(peer_id, piece_index).await;
261
262                if maybe_piece.is_some() {
263                    trace!(%piece_index, %peer_id, "DSN L1 lookup from connected peers succeeded");
264
265                    return maybe_piece;
266                }
267            }
268        }
269
270        trace!(%piece_index, "Getting piece from DSN L1 using random walk.");
271        let random_walk_result = self
272            .get_piece_by_random_walking(piece_index, max_random_walking_rounds)
273            .await;
274
275        if random_walk_result.is_some() {
276            trace!(%piece_index, "DSN L1 lookup via random walk succeeded");
277
278            return random_walk_result;
279        }
280
281        debug!(
282            %piece_index,
283            %max_random_walking_rounds,
284            "Cannot acquire piece from DSN L1: random walk failed"
285        );
286
287        None
288    }
289
290    /// Get piece from L1 by random walking
291    async fn get_piece_by_random_walking(
292        &self,
293        piece_index: PieceIndex,
294        walking_rounds: usize,
295    ) -> Option<Piece> {
296        for round in 0..walking_rounds {
297            debug!(%piece_index, round, "Random walk round");
298
299            let result = self
300                .get_piece_by_random_walking_from_single_round(piece_index, round)
301                .await;
302
303            if result.is_some() {
304                return result;
305            }
306        }
307
308        debug!(%piece_index, "Random walking piece retrieval failed.");
309
310        None
311    }
312
313    /// Get piece from L1 by random walking (single round)
314    async fn get_piece_by_random_walking_from_single_round(
315        &self,
316        piece_index: PieceIndex,
317        round: usize,
318    ) -> Option<Piece> {
319        // TODO: Take advantage of `cached_pieces`
320        trace!(%piece_index, "get_piece_by_random_walking round");
321
322        // Random walk key
323        let key = PeerId::random();
324
325        let request_batch = self.node.get_requests_batch_handle().await;
326        let mut get_closest_peers_stream = request_batch
327            .get_closest_peers(key.into())
328            .await
329            .inspect_err(|err| warn!(%piece_index, ?key, ?err, %round, "get_closest_peers returned an error"))
330            .ok()?;
331
332        while let Some(peer_id) = get_closest_peers_stream.next().await {
333            trace!(%piece_index, %peer_id, %round, "get_closest_peers returned an item");
334
335            let Ok(PieceByIndexResponse {
336                piece,
337                cached_pieces: _,
338            }) = request_batch
339                .send_generic_request(
340                    peer_id,
341                    Vec::new(),
342                    PieceByIndexRequest {
343                        piece_index,
344                        cached_pieces: StdArc::default(),
345                    },
346                )
347                .await
348                .inspect_err(
349                    |error| debug!(%peer_id, %piece_index, ?key, %round, ?error, "Piece request failed."),
350                )
351            else {
352                continue;
353            };
354
355            if let Some(piece) = piece {
356                trace!(%peer_id, %piece_index, ?key, %round,  "Piece request succeeded.");
357
358                return self
359                    .piece_validator
360                    .validate_piece(peer_id, piece_index, piece)
361                    .await;
362            }
363
364            debug!(%peer_id, %piece_index, ?key, %round, "Piece request returned empty piece.");
365        }
366
367        None
368    }
369}
370
371/// Kademlia wrapper to take advantage of its internal logic of selecting closest peers
372struct KademliaWrapper {
373    local_peer_id: PeerId,
374    kademlia: Kademlia<DummyRecordStore>,
375}
376
377impl KademliaWrapper {
378    fn new(local_peer_id: PeerId) -> Self {
379        Self {
380            local_peer_id,
381            kademlia: Kademlia::new(local_peer_id, DummyRecordStore),
382        }
383    }
384
385    fn add_peer(&mut self, peer_id: &PeerId, addresses: Vec<Multiaddr>) {
386        for address in addresses {
387            self.kademlia.add_address(peer_id, address);
388        }
389        while self
390            .kademlia
391            .poll(&mut Context::from_waker(noop_waker_ref()))
392            .is_ready()
393        {
394            // Simply drain useless events generated by above calls
395        }
396    }
397
398    /// Returned peers are already sorted in ascending distance order
399    fn closest_peers(
400        &mut self,
401        key: &KBucketKey<Multihash>,
402    ) -> impl Iterator<Item = (PeerId, Vec<Multiaddr>)> + 'static {
403        let mut closest_peers = self
404            .kademlia
405            .find_closest_local_peers(key, &self.local_peer_id)
406            .map(|peer| {
407                (
408                    KBucketKey::from(peer.node_id).distance(key),
409                    peer.node_id,
410                    peer.multiaddrs,
411                )
412            })
413            .collect::<Vec<_>>();
414
415        closest_peers.sort_unstable_by_key(|a| a.0);
416        closest_peers
417            .into_iter()
418            .map(|(_distance, peer_id, addresses)| (peer_id, addresses))
419    }
420}
421
422/// Takes pieces to download as an input, sends results with pieces that were downloaded
423/// successfully and returns those that were not downloaded
424async fn download_cached_pieces<PV, PieceIndices>(
425    piece_indices: PieceIndices,
426    node: &Node,
427    piece_validator: &PV,
428    results: &mpsc::UnboundedSender<(PieceIndex, Option<Piece>)>,
429    semaphore: &Semaphore,
430) -> impl ExactSizeIterator<Item = PieceIndex>
431where
432    PV: PieceValidator,
433    PieceIndices: IntoIterator<Item = PieceIndex>,
434{
435    // Make sure every piece index has an entry since this will be the primary container for
436    // tracking pieces to download going forward.
437    //
438    // At the end pieces that were not downloaded will remain with a collection of known closest
439    // peers for them.
440    let mut pieces_to_download = piece_indices
441        .into_iter()
442        .map(|piece_index| async move {
443            let mut kademlia = KademliaWrapper::new(node.id());
444            let key = piece_index.to_multihash();
445
446            let local_closest_peers = node
447                .get_closest_local_peers(key, None)
448                .await
449                .unwrap_or_default();
450
451            // Seed with local closest peers
452            for (peer_id, addresses) in local_closest_peers {
453                kademlia.add_peer(&peer_id, addresses);
454            }
455
456            (piece_index, kademlia)
457        })
458        .collect::<FuturesUnordered<_>>()
459        .collect::<HashMap<_, _>>()
460        .await;
461
462    let num_pieces = pieces_to_download.len();
463    debug!(%num_pieces, "Starting");
464
465    let mut checked_peers = HashSet::new();
466
467    let Ok(connected_servers) = node.connected_servers().await else {
468        trace!("Connected servers error");
469        return pieces_to_download.into_keys();
470    };
471
472    let num_connected_servers = connected_servers.len();
473    debug!(
474        %num_connected_servers,
475        %num_pieces,
476        "Starting downloading"
477    );
478
479    // Dispatch initial set of requests to peers with checked pieces distributed uniformly
480    let mut downloading_stream = connected_servers
481        .into_iter()
482        .take(num_pieces)
483        .enumerate()
484        .map(|(peer_index, peer_id)| {
485            checked_peers.insert(peer_id);
486
487            // Inside to avoid division by zero in case there are no connected servers or pieces
488            let step = num_pieces / num_connected_servers.min(num_pieces);
489
490            // Take unique first piece index for each connected peer and the rest just to check
491            // cached pieces up to recommended limit
492            let mut check_cached_pieces = pieces_to_download
493                .keys()
494                .cycle()
495                .skip(step * peer_index)
496                // + 1 because one index below is removed below
497                .take(num_pieces.min(CachedPieceByIndexRequest::RECOMMENDED_LIMIT + 1))
498                .copied()
499                .collect::<Vec<_>>();
500            // Pick first piece index as the piece we want to download
501            let piece_index = check_cached_pieces.swap_remove(0);
502
503            trace!(%peer_id, %piece_index, "Downloading piece from initially connected peer");
504
505            let permit = semaphore.try_acquire();
506
507            let fut = async move {
508                let permit = match permit {
509                    Some(permit) => permit,
510                    None => semaphore.acquire().await,
511                };
512
513                download_cached_piece_from_peer(
514                    node,
515                    piece_validator,
516                    peer_id,
517                    Vec::new(),
518                    StdArc::new(check_cached_pieces),
519                    piece_index,
520                    HashSet::new(),
521                    HashSet::new(),
522                    permit,
523                )
524                .await
525            };
526
527            (piece_index, Box::pin(fut.into_stream()) as _)
528        })
529        .collect::<StreamMap<_, _>>();
530
531    loop {
532        // Process up to 50% of the pieces concurrently
533        let mut additional_pieces_to_download =
534            (num_pieces / 2).saturating_sub(downloading_stream.len());
535        if additional_pieces_to_download > 0 {
536            trace!(
537                %additional_pieces_to_download,
538                num_pieces,
539                currently_downloading = %downloading_stream.len(),
540                "Downloading additional pieces from closest peers"
541            );
542            // Pick up any newly connected peers (if any)
543            'outer: for peer_id in node
544                .connected_servers()
545                .await
546                .unwrap_or_default()
547                .into_iter()
548                .filter(|peer_id| checked_peers.insert(*peer_id))
549                .take(additional_pieces_to_download)
550            {
551                let permit = if downloading_stream.is_empty() {
552                    semaphore.acquire().await
553                } else if let Some(permit) = semaphore.try_acquire() {
554                    permit
555                } else {
556                    break;
557                };
558
559                for &piece_index in pieces_to_download.keys() {
560                    if downloading_stream.contains_key(&piece_index) {
561                        continue;
562                    }
563
564                    trace!(%peer_id, %piece_index, "Downloading piece from newly connected peer");
565
566                    let check_cached_pieces = sample_cached_piece_indices(
567                        pieces_to_download.keys(),
568                        &HashSet::new(),
569                        &HashSet::new(),
570                        piece_index,
571                    );
572                    let fut = download_cached_piece_from_peer(
573                        node,
574                        piece_validator,
575                        peer_id,
576                        Vec::new(),
577                        StdArc::new(check_cached_pieces),
578                        piece_index,
579                        HashSet::new(),
580                        HashSet::new(),
581                        permit,
582                    );
583
584                    downloading_stream.insert(piece_index, Box::pin(fut.into_stream()) as _);
585                    additional_pieces_to_download -= 1;
586
587                    continue 'outer;
588                }
589
590                break;
591            }
592
593            // Pick up more pieces to download from the closest peers
594            // Ideally we'd not allocate here, but it is hard to explain to the compiler that
595            // entries are not removed otherwise
596            let pieces_indices_to_download = pieces_to_download.keys().copied().collect::<Vec<_>>();
597            for piece_index in pieces_indices_to_download {
598                if additional_pieces_to_download == 0 {
599                    break;
600                }
601                if downloading_stream.contains_key(&piece_index) {
602                    continue;
603                }
604                let permit = if downloading_stream.is_empty() {
605                    semaphore.acquire().await
606                } else if let Some(permit) = semaphore.try_acquire() {
607                    permit
608                } else {
609                    break;
610                };
611
612                let kbucket_key = KBucketKey::from(piece_index.to_multihash());
613                let closest_peers_to_check = pieces_to_download
614                    .get_mut(&piece_index)
615                    .expect("Entries are not removed here; qed")
616                    .closest_peers(&kbucket_key);
617                for (peer_id, addresses) in closest_peers_to_check {
618                    if !checked_peers.insert(peer_id) {
619                        continue;
620                    }
621
622                    trace!(%peer_id, %piece_index, "Downloading piece from closest peer");
623
624                    let check_cached_pieces = sample_cached_piece_indices(
625                        pieces_to_download.keys(),
626                        &HashSet::new(),
627                        &HashSet::new(),
628                        piece_index,
629                    );
630                    let fut = download_cached_piece_from_peer(
631                        node,
632                        piece_validator,
633                        peer_id,
634                        addresses,
635                        StdArc::new(check_cached_pieces),
636                        piece_index,
637                        HashSet::new(),
638                        HashSet::new(),
639                        permit,
640                    );
641
642                    downloading_stream.insert(piece_index, Box::pin(fut.into_stream()) as _);
643                    additional_pieces_to_download -= 1;
644                    break;
645                }
646            }
647
648            trace!(
649                pieces_left = %additional_pieces_to_download,
650                "Initiated downloading additional pieces from closest peers"
651            );
652        }
653
654        let Some((piece_index, result)) = downloading_stream.next().await else {
655            if !pieces_to_download.is_empty() {
656                debug!(
657                    %num_pieces,
658                    to_download = %pieces_to_download.len(),
659                    "Finished downloading early"
660                );
661                // Nothing was downloaded, we're done here
662                break;
663            }
664            break;
665        };
666        process_downloading_result(
667            piece_index,
668            result,
669            &mut pieces_to_download,
670            &mut downloading_stream,
671            node,
672            piece_validator,
673            results,
674        );
675
676        if pieces_to_download.is_empty() {
677            break;
678        }
679    }
680
681    pieces_to_download.into_keys()
682}
683
684fn process_downloading_result<'a, 'b, PV>(
685    piece_index: PieceIndex,
686    result: DownloadedPieceFromPeer<'a>,
687    pieces_to_download: &'b mut HashMap<PieceIndex, KademliaWrapper>,
688    downloading_stream: &'b mut StreamMap<
689        PieceIndex,
690        Pin<Box<dyn Stream<Item = DownloadedPieceFromPeer<'a>> + Send + 'a>>,
691    >,
692    node: &'a Node,
693    piece_validator: &'a PV,
694    results: &'a mpsc::UnboundedSender<(PieceIndex, Option<Piece>)>,
695) where
696    PV: PieceValidator,
697{
698    let DownloadedPieceFromPeer {
699        peer_id,
700        result,
701        mut cached_pieces,
702        not_cached_pieces,
703        permit,
704    } = result;
705    trace!(%piece_index, %peer_id, result = %result.is_some(), "Piece response");
706
707    let Some(result) = result else {
708        // Downloading failed, ignore peer
709        return;
710    };
711
712    match result {
713        PieceResult::Piece(piece) => {
714            trace!(%piece_index, %peer_id, "Got piece");
715
716            // Downloaded successfully
717            pieces_to_download.remove(&piece_index);
718
719            results
720                .unbounded_send((piece_index, Some(piece)))
721                .expect("This future isn't polled after receiver is dropped; qed");
722
723            if pieces_to_download.is_empty() {
724                return;
725            }
726
727            cached_pieces.remove(&piece_index);
728        }
729        PieceResult::ClosestPeers(closest_peers) => {
730            trace!(%piece_index, %peer_id, "Got closest peers");
731
732            // Store closer peers in case piece index was not downloaded yet
733            if let Some(kademlia) = pieces_to_download.get_mut(&piece_index) {
734                for (peer_id, addresses) in Vec::from(closest_peers) {
735                    kademlia.add_peer(&peer_id, addresses);
736                }
737            }
738
739            // No need to ask this peer again if they claimed to have this piece index earlier
740            if cached_pieces.remove(&piece_index) {
741                return;
742            }
743        }
744    }
745
746    let mut maybe_piece_index_to_download_next = None;
747    // Clear useless entries in cached pieces and find something to download next
748    cached_pieces.retain(|piece_index| {
749        // Clear downloaded pieces
750        if !pieces_to_download.contains_key(piece_index) {
751            return false;
752        }
753
754        // Try to pick a piece to download that is not being downloaded already
755        if maybe_piece_index_to_download_next.is_none()
756            && !downloading_stream.contains_key(piece_index)
757        {
758            maybe_piece_index_to_download_next.replace(*piece_index);
759            // We'll check it later when receiving response
760            return true;
761        }
762
763        // Retain everything else
764        true
765    });
766
767    let piece_index_to_download_next = if let Some(piece_index) = maybe_piece_index_to_download_next
768    {
769        trace!(%piece_index, %peer_id, "Next piece to download from peer");
770        piece_index
771    } else {
772        trace!(%peer_id, "Peer doesn't have anything else");
773        // Nothing left to do with this peer
774        return;
775    };
776
777    let fut = download_cached_piece_from_peer(
778        node,
779        piece_validator,
780        peer_id,
781        Vec::new(),
782        // Sample more random cached piece indices for connected peer, algorithm can be
783        // improved, but has to be something simple and this should do it for now
784        StdArc::new(sample_cached_piece_indices(
785            pieces_to_download.keys(),
786            &cached_pieces,
787            &not_cached_pieces,
788            piece_index_to_download_next,
789        )),
790        piece_index_to_download_next,
791        cached_pieces,
792        not_cached_pieces,
793        permit,
794    );
795    downloading_stream.insert(piece_index_to_download_next, Box::pin(fut.into_stream()));
796}
797
798fn sample_cached_piece_indices<'a, I>(
799    pieces_to_download: I,
800    cached_pieces: &HashSet<PieceIndex>,
801    not_cached_pieces: &HashSet<PieceIndex>,
802    piece_index_to_download_next: PieceIndex,
803) -> Vec<PieceIndex>
804where
805    I: Iterator<Item = &'a PieceIndex>,
806{
807    pieces_to_download
808        // Do a bit of work to filter-out piece indices we already know remote peer
809        // has or doesn't to decrease burden on them
810        .filter_map(|piece_index| {
811            if piece_index == &piece_index_to_download_next
812                || cached_pieces.contains(piece_index)
813                || not_cached_pieces.contains(piece_index)
814            {
815                None
816            } else {
817                Some(*piece_index)
818            }
819        })
820        .sample(
821            &mut rand::rng(),
822            CachedPieceByIndexRequest::RECOMMENDED_LIMIT,
823        )
824}
825
826struct DownloadedPieceFromPeer<'a> {
827    peer_id: PeerId,
828    result: Option<PieceResult>,
829    cached_pieces: HashSet<PieceIndex>,
830    not_cached_pieces: HashSet<PieceIndex>,
831    permit: SemaphoreGuard<'a>,
832}
833
834/// `check_cached_pieces` contains a list of pieces for peer to filter-out according to locally
835/// caches pieces, `cached_pieces` and `not_cached_pieces` contain piece indices peer claims is
836/// known to have or not have already
837#[expect(clippy::too_many_arguments, reason = "Internal API")]
838async fn download_cached_piece_from_peer<'a, PV>(
839    node: &'a Node,
840    piece_validator: &'a PV,
841    peer_id: PeerId,
842    addresses: Vec<Multiaddr>,
843    check_cached_pieces: StdArc<Vec<PieceIndex>>,
844    piece_index: PieceIndex,
845    mut cached_pieces: HashSet<PieceIndex>,
846    mut not_cached_pieces: HashSet<PieceIndex>,
847    permit: SemaphoreGuard<'a>,
848) -> DownloadedPieceFromPeer<'a>
849where
850    PV: PieceValidator,
851{
852    let result = match node
853        .send_generic_request(
854            peer_id,
855            addresses,
856            CachedPieceByIndexRequest {
857                piece_index,
858                cached_pieces: StdArc::clone(&check_cached_pieces),
859            },
860        )
861        .await
862    {
863        Ok(response) => {
864            let CachedPieceByIndexResponse {
865                result,
866                cached_pieces,
867            } = response;
868
869            match result {
870                PieceResult::Piece(piece) => piece_validator
871                    .validate_piece(peer_id, piece_index, piece)
872                    .await
873                    .map(|piece| CachedPieceByIndexResponse {
874                        result: PieceResult::Piece(piece),
875                        cached_pieces,
876                    }),
877                PieceResult::ClosestPeers(closest_peers) => Some(CachedPieceByIndexResponse {
878                    result: PieceResult::ClosestPeers(closest_peers),
879                    cached_pieces,
880                }),
881            }
882        }
883        Err(error) => {
884            debug!(%error, %peer_id, %piece_index, "Failed to download cached piece from peer");
885
886            None
887        }
888    };
889
890    match result {
891        Some(result) => {
892            cached_pieces.extend(result.cached_pieces);
893            not_cached_pieces.extend(
894                check_cached_pieces
895                    .iter()
896                    .filter(|piece_index| !cached_pieces.contains(piece_index))
897                    .copied(),
898            );
899
900            DownloadedPieceFromPeer {
901                peer_id,
902                result: Some(result.result),
903                cached_pieces: { cached_pieces },
904                not_cached_pieces,
905                permit,
906            }
907        }
908        None => DownloadedPieceFromPeer {
909            peer_id,
910            result: None,
911            cached_pieces,
912            not_cached_pieces,
913            permit,
914        },
915    }
916}