subspace_data_retrieval/
segment_downloading.rs1use 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
17pub const SEGMENT_DOWNLOAD_RETRIES: usize = 3;
20
21pub const SEGMENT_DOWNLOAD_RETRY_DELAY: Duration = Duration::from_secs(10);
24
25#[derive(Debug, thiserror::Error)]
27pub enum SegmentDownloadingError {
28 #[error(
30 "Not enough ({downloaded_pieces}/{}) pieces for segment {segment_index}",
31 RecordedHistorySegment::NUM_RAW_RECORDS
32 )]
33 NotEnoughPieces {
34 segment_index: SegmentIndex,
36 downloaded_pieces: usize,
38 },
39
40 #[error("Piece getter error: {source}")]
42 PieceGetterError {
43 #[from]
44 source: anyhow::Error,
45 },
46
47 #[error("Segment reconstruction error: {source}")]
49 SegmentReconstruction {
50 #[from]
51 source: ReconstructorError,
52 },
53
54 #[error("Segment data decoding error: {source}")]
56 SegmentDecoding {
57 #[from]
58 source: parity_scale_codec::Error,
59 },
60}
61
62pub 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
85pub 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 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#[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 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 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 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 ?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}