Skip to main content

subspace_data_retrieval/
segment_downloading.rs

1//! Fetching segments of the archived history of Subspace Network.
2
3use crate::piece_getter::PieceGetter;
4use futures::StreamExt;
5use std::time::Duration;
6use subspace_archiving::archiver::Segment;
7use subspace_archiving::reconstructor::{Reconstructor, ReconstructorError};
8use subspace_core_primitives::pieces::Piece;
9use subspace_core_primitives::segments::{
10    ArchivedHistorySegment, RecordedHistorySegment, SegmentIndex,
11};
12use subspace_erasure_coding::ErasureCoding;
13use tokio::task::spawn_blocking;
14use tokio::time::sleep;
15use tracing::debug;
16
17/// The number of times we try to download a segment before giving up.
18/// This is a suggested default, callers can supply their own value if needed.
19pub const SEGMENT_DOWNLOAD_RETRIES: usize = 3;
20
21/// The amount of time we wait between segment download retries.
22/// This is a suggested default, callers can supply their own value if needed.
23pub const SEGMENT_DOWNLOAD_RETRY_DELAY: Duration = Duration::from_secs(10);
24
25/// Segment getter errors.
26#[derive(Debug, thiserror::Error)]
27pub enum SegmentDownloadingError {
28    /// Not enough pieces
29    #[error(
30        "Not enough ({downloaded_pieces}/{}) pieces for segment {segment_index}",
31        RecordedHistorySegment::NUM_RAW_RECORDS
32    )]
33    NotEnoughPieces {
34        /// The segment we were trying to download
35        segment_index: SegmentIndex,
36        /// Number of pieces that were downloaded
37        downloaded_pieces: usize,
38    },
39
40    /// Piece getter error
41    #[error("Piece getter error: {source}")]
42    PieceGetterError {
43        #[from]
44        source: anyhow::Error,
45    },
46
47    /// Segment reconstruction error
48    #[error("Segment reconstruction error: {source}")]
49    SegmentReconstruction {
50        #[from]
51        source: ReconstructorError,
52    },
53
54    /// Segment decoding error
55    #[error("Segment data decoding error: {source}")]
56    SegmentDecoding {
57        #[from]
58        source: parity_scale_codec::Error,
59    },
60}
61
62/// Concurrently downloads the pieces for `segment_index`, and reconstructs the segment.
63pub async fn download_segment<PG>(
64    segment_index: SegmentIndex,
65    piece_getter: &PG,
66    erasure_coding: ErasureCoding,
67    retries: usize,
68    retry_delay: Option<Duration>,
69) -> Result<Segment, SegmentDownloadingError>
70where
71    PG: PieceGetter,
72{
73    let reconstructor = Reconstructor::new(erasure_coding);
74
75    let segment_pieces =
76        download_segment_pieces(segment_index, piece_getter, retries, retry_delay).await?;
77
78    let segment = spawn_blocking(move || reconstructor.reconstruct_segment(&segment_pieces))
79        .await
80        .expect("Panic if blocking task panicked")?;
81
82    Ok(segment)
83}
84
85/// Downloads pieces of a segment so that segment can be reconstructed afterward.
86/// Repeatedly attempts to download pieces until the required number of pieces is reached.
87///
88/// Prefers source pieces if available, on error returns the number of available pieces.
89pub async fn download_segment_pieces<PG>(
90    segment_index: SegmentIndex,
91    piece_getter: &PG,
92    retries: usize,
93    retry_delay: Option<Duration>,
94) -> Result<Vec<Option<Piece>>, SegmentDownloadingError>
95where
96    PG: PieceGetter,
97{
98    let mut existing_pieces = [const { None }; ArchivedHistorySegment::NUM_PIECES];
99
100    for retry in 0..=retries {
101        match download_missing_segment_pieces(segment_index, piece_getter, existing_pieces).await {
102            Ok(segment_pieces) => return Ok(segment_pieces),
103            Err((error, incomplete_segment_pieces)) => {
104                existing_pieces = incomplete_segment_pieces;
105
106                if retry < retries {
107                    debug!(
108                        %segment_index,
109                        %retry,
110                        ?retry_delay,
111                        ?error,
112                        "Failed to download segment pieces once, retrying"
113                    );
114                    if let Some(retry_delay) = retry_delay {
115                        // Wait before retrying to give the node a chance to find other peers
116                        sleep(retry_delay).await;
117                    }
118                }
119            }
120        }
121    }
122
123    debug!(
124        %segment_index,
125        %retries,
126        "Failed to download segment pieces"
127    );
128
129    Err(SegmentDownloadingError::NotEnoughPieces {
130        segment_index,
131        downloaded_pieces: existing_pieces
132            .iter()
133            .filter(|piece| piece.is_some())
134            .count(),
135    })
136}
137
138/// Tries to download pieces of a segment once, so that segment can be reconstructed afterward.
139/// Pass existing pieces in `existing_pieces`, or use
140/// `[const { None }; ArchivedHistorySegment::NUM_PIECES]` if no pieces are available.
141///
142/// Prefers source pieces if available, on error returns the incomplete piece download (including
143/// existing pieces).
144#[expect(
145    clippy::result_large_err,
146    reason = "Same as the input, returned back to the caller to retry with"
147)]
148async fn download_missing_segment_pieces<PG>(
149    segment_index: SegmentIndex,
150    piece_getter: &PG,
151    existing_pieces: [Option<Piece>; ArchivedHistorySegment::NUM_PIECES],
152) -> Result<
153    Vec<Option<Piece>>,
154    (
155        SegmentDownloadingError,
156        [Option<Piece>; ArchivedHistorySegment::NUM_PIECES],
157    ),
158>
159where
160    PG: PieceGetter,
161{
162    let required_pieces_number = RecordedHistorySegment::NUM_RAW_RECORDS;
163    let mut downloaded_pieces = existing_pieces
164        .iter()
165        .filter(|piece| piece.is_some())
166        .count();
167
168    // Debugging failure patterns in piece downloads
169    let mut first_success = None;
170    let mut last_success = None;
171    let mut first_failure = None;
172    let mut last_failure = None;
173
174    let mut segment_pieces = existing_pieces;
175
176    let mut pieces_iter = segment_index
177        .segment_piece_indexes_source_first()
178        .into_iter();
179
180    // Download in batches until we get enough or exhaust available pieces
181    while !pieces_iter.is_empty() && downloaded_pieces != required_pieces_number {
182        let piece_indices = pieces_iter
183            .by_ref()
184            .filter(|piece_index| segment_pieces[piece_index.position() as usize].is_none())
185            .take(required_pieces_number - downloaded_pieces)
186            .collect();
187
188        let mut received_segment_pieces = match piece_getter.get_pieces(piece_indices).await {
189            Ok(pieces) => pieces,
190            Err(error) => return Err((error.into(), segment_pieces)),
191        };
192
193        while let Some((piece_index, result)) = received_segment_pieces.next().await {
194            match result {
195                Ok(Some(piece)) => {
196                    downloaded_pieces += 1;
197                    segment_pieces
198                        .get_mut(piece_index.position() as usize)
199                        .expect("Piece position is by definition within segment; qed")
200                        .replace(piece);
201
202                    if first_success.is_none() {
203                        first_success = Some(piece_index.position());
204                    }
205                    last_success = Some(piece_index.position());
206                }
207                // We often see an error where 127 pieces are downloaded successfully, but the
208                // other 129 fail. It seems like 1 request in a 128 piece batch fails, then 128
209                // single piece requests are made, and also fail.
210                // Delaying requests after a failure gives the node a chance to find other peers.
211                Ok(None) => {
212                    debug!(%piece_index, "Piece was not found");
213                    if first_failure.is_none() {
214                        first_failure = Some(piece_index.position());
215                    }
216                    last_failure = Some(piece_index.position());
217                }
218                Err(error) => {
219                    debug!(%error, %piece_index, "Failed to get piece");
220                    if first_failure.is_none() {
221                        first_failure = Some(piece_index.position());
222                    }
223                    last_failure = Some(piece_index.position());
224                }
225            }
226        }
227    }
228
229    if downloaded_pieces < required_pieces_number {
230        debug!(
231            %segment_index,
232            %downloaded_pieces,
233            %required_pieces_number,
234            // Piece positions that succeeded/failed
235            ?first_success,
236            ?last_success,
237            ?first_failure,
238            ?last_failure,
239            "Failed to retrieve pieces for segment"
240        );
241
242        return Err((
243            SegmentDownloadingError::NotEnoughPieces {
244                segment_index,
245                downloaded_pieces: segment_pieces
246                    .iter()
247                    .filter(|piece| piece.is_some())
248                    .count(),
249            },
250            segment_pieces,
251        ));
252    }
253
254    Ok(segment_pieces.to_vec())
255}