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 =
77        spawn_blocking(move || reconstructor.reconstruct_segment(segment_pieces.as_slice()))
78            .await
79            .expect("Panic if blocking task panicked")?;
80
81    Ok(segment)
82}
83
84/// Downloads pieces of a segment so that segment can be reconstructed afterward.
85/// Repeatedly attempts to download pieces until the required number of pieces is reached.
86///
87/// Prefers source pieces if available, on error returns the number of available pieces.
88pub async fn download_segment_pieces<PG>(
89    segment_index: SegmentIndex,
90    piece_getter: &PG,
91    retries: usize,
92    retry_delay: Option<Duration>,
93) -> Result<Box<[Option<Piece>; ArchivedHistorySegment::NUM_PIECES]>, SegmentDownloadingError>
94where
95    PG: PieceGetter,
96{
97    let mut existing_pieces = Box::new([const { None }; ArchivedHistorySegment::NUM_PIECES]);
98
99    for retry in 0..=retries {
100        match download_missing_segment_pieces(segment_index, piece_getter, existing_pieces).await {
101            Ok(segment_pieces) => return Ok(segment_pieces),
102            Err((error, incomplete_segment_pieces)) => {
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/// Pieces that are already available are passed in `segment_pieces`, the rest are `None`.
139///
140/// Prefers source pieces if available, on error returns the incomplete piece download (including
141/// pieces that were already available).
142async fn download_missing_segment_pieces<PG>(
143    segment_index: SegmentIndex,
144    piece_getter: &PG,
145    mut segment_pieces: Box<[Option<Piece>; ArchivedHistorySegment::NUM_PIECES]>,
146) -> Result<
147    Box<[Option<Piece>; ArchivedHistorySegment::NUM_PIECES]>,
148    (
149        SegmentDownloadingError,
150        Box<[Option<Piece>; ArchivedHistorySegment::NUM_PIECES]>,
151    ),
152>
153where
154    PG: PieceGetter,
155{
156    let required_pieces_number = RecordedHistorySegment::NUM_RAW_RECORDS;
157    let mut downloaded_pieces = segment_pieces
158        .iter()
159        .filter(|piece| piece.is_some())
160        .count();
161
162    // Debugging failure patterns in piece downloads
163    let mut first_success = None;
164    let mut last_success = None;
165    let mut first_failure = None;
166    let mut last_failure = None;
167
168    let mut pieces_iter = segment_index.segment_piece_indexes().into_iter();
169
170    // Download in batches until we get enough or exhaust available pieces
171    while !pieces_iter.is_empty() && downloaded_pieces != required_pieces_number {
172        let piece_indices = pieces_iter
173            .by_ref()
174            .filter(|piece_index| segment_pieces[usize::from(piece_index.position())].is_none())
175            .take(required_pieces_number - downloaded_pieces)
176            .collect();
177
178        let mut received_segment_pieces = match piece_getter.get_pieces(piece_indices).await {
179            Ok(pieces) => pieces,
180            Err(error) => return Err((error.into(), segment_pieces)),
181        };
182
183        while let Some((piece_index, result)) = received_segment_pieces.next().await {
184            match result {
185                Ok(Some(piece)) => {
186                    downloaded_pieces += 1;
187                    segment_pieces
188                        .get_mut(usize::from(piece_index.position()))
189                        .expect("Piece position is by definition within segment; qed")
190                        .replace(piece);
191
192                    if first_success.is_none() {
193                        first_success = Some(piece_index.position());
194                    }
195                    last_success = Some(piece_index.position());
196                }
197                // We often see an error where 127 pieces are downloaded successfully, but the
198                // other 129 fail. It seems like 1 request in a 128 piece batch fails, then 128
199                // single piece requests are made, and also fail.
200                // Delaying requests after a failure gives the node a chance to find other peers.
201                Ok(None) => {
202                    debug!(%piece_index, "Piece was not found");
203                    if first_failure.is_none() {
204                        first_failure = Some(piece_index.position());
205                    }
206                    last_failure = Some(piece_index.position());
207                }
208                Err(error) => {
209                    debug!(%error, %piece_index, "Failed to get piece");
210                    if first_failure.is_none() {
211                        first_failure = Some(piece_index.position());
212                    }
213                    last_failure = Some(piece_index.position());
214                }
215            }
216        }
217    }
218
219    if downloaded_pieces < required_pieces_number {
220        debug!(
221            %segment_index,
222            %downloaded_pieces,
223            %required_pieces_number,
224            // Piece positions that succeeded/failed
225            ?first_success,
226            ?last_success,
227            ?first_failure,
228            ?last_failure,
229            "Failed to retrieve pieces for segment"
230        );
231
232        return Err((
233            SegmentDownloadingError::NotEnoughPieces {
234                segment_index,
235                downloaded_pieces: segment_pieces
236                    .iter()
237                    .filter(|piece| piece.is_some())
238                    .count(),
239            },
240            segment_pieces,
241        ));
242    }
243
244    Ok(segment_pieces)
245}