Skip to main content

ab_data_retrieval/
segment_downloading.rs

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