ab_data_retrieval/
segment_downloading.rs1use 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
15pub const SEGMENT_DOWNLOAD_RETRIES: usize = 3;
18
19pub const SEGMENT_DOWNLOAD_RETRY_DELAY: Duration = Duration::from_secs(10);
22
23#[derive(Debug, thiserror::Error)]
25pub enum SegmentDownloadingError {
26 #[error(
28 "Not enough ({downloaded_pieces}/{}) pieces for segment {segment_index}",
29 RecordedHistorySegment::NUM_RAW_RECORDS
30 )]
31 NotEnoughPieces {
32 segment_index: SegmentIndex,
34 downloaded_pieces: usize,
36 },
37
38 #[error("Piece getter error: {source}")]
40 PieceGetterError {
41 #[from]
42 source: anyhow::Error,
43 },
44
45 #[error("Segment reconstruction error: {source}")]
47 SegmentReconstruction {
48 #[from]
49 source: ReconstructorError,
50 },
51
52 #[error("Segment data decoding error: {source}")]
54 SegmentDecoding {
55 #[from]
56 source: parity_scale_codec::Error,
57 },
58}
59
60pub 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
84pub 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 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
137async 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 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 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 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 ?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}