1use crate::sector::{
8 RecordMetadata, SectorContentsMap, SectorContentsMapFromBytesError, SectorMetadataChecksummed,
9 sector_record_chunks_size,
10};
11use crate::{ReadAt, ReadAtAsync, ReadAtSync};
12use ab_core_primitives::hashes::Blake3Hash;
13use ab_core_primitives::pieces::{Piece, PieceOffset, Record, RecordChunk};
14use ab_core_primitives::sectors::{SBucket, SectorId};
15use ab_erasure_coding::{ErasureCoding, ErasureCodingError, RecoveryShardState};
16use ab_proof_of_space::{PosProofs, Table, TableGenerator};
17use futures::StreamExt;
18use futures::stream::FuturesUnordered;
19use parity_scale_codec::Decode;
20use rayon::prelude::*;
21use std::io;
22use std::simd::Simd;
23use thiserror::Error;
24use tracing::debug;
25
26#[derive(Debug, Error)]
28pub enum ReadingError {
29 #[error("Failed to read chunk at location {chunk_location}: {error}")]
34 FailedToReadChunk {
35 chunk_location: u64,
37 error: io::Error,
39 },
40 #[error("Missing PoS proof for s-bucket {s_bucket}")]
45 MissingPosProof {
46 s_bucket: SBucket,
48 },
49 #[error("Failed to erasure-decode record at offset {piece_offset}: {error}")]
51 FailedToErasureDecodeRecord {
52 piece_offset: PieceOffset,
54 error: ErasureCodingError,
56 },
57 #[error("Wrong record size after decoding: expected {expected}, actual {actual}")]
59 WrongRecordSizeAfterDecoding {
60 expected: usize,
62 actual: usize,
64 },
65 #[error("Failed to decode sector contents map: {0}")]
67 FailedToDecodeSectorContentsMap(#[from] SectorContentsMapFromBytesError),
68 #[error("Reading I/O error: {0}")]
70 Io(#[from] io::Error),
71 #[error("Checksum mismatch")]
73 ChecksumMismatch,
74}
75
76impl ReadingError {
77 pub fn is_fatal(&self) -> bool {
79 match self {
80 ReadingError::FailedToReadChunk { .. } => false,
81 ReadingError::MissingPosProof { .. } => false,
82 ReadingError::FailedToErasureDecodeRecord { .. } => false,
83 ReadingError::WrongRecordSizeAfterDecoding { .. } => false,
84 ReadingError::FailedToDecodeSectorContentsMap(_) => false,
85 ReadingError::Io(_) => true,
86 ReadingError::ChecksumMismatch => false,
87 }
88 }
89}
90
91pub async fn read_sector_record_chunks<S, A>(
96 piece_offset: PieceOffset,
97 pieces_in_sector: u16,
98 s_bucket_offsets: &[u32; Record::NUM_S_BUCKETS],
99 sector_contents_map: &SectorContentsMap,
100 pos_proofs: &PosProofs,
101 sector: &ReadAt<S, A>,
102) -> Result<Box<[Option<RecordChunk>; Record::NUM_S_BUCKETS]>, ReadingError>
103where
104 S: ReadAtSync,
105 A: ReadAtAsync,
106{
107 let mut record_chunks = Box::<[Option<RecordChunk>; Record::NUM_S_BUCKETS]>::try_from(
108 vec![None::<RecordChunk>; Record::NUM_S_BUCKETS].into_boxed_slice(),
109 )
110 .expect("Correct size; qed");
111
112 let read_chunks_inputs = record_chunks
113 .par_iter_mut()
114 .zip(sector_contents_map.par_iter_record_chunk_to_plot(piece_offset))
115 .zip(s_bucket_offsets.par_iter())
116 .map(
117 |((maybe_record_chunk, maybe_chunk_offset), &s_bucket_offset)| {
118 let chunk_offset = maybe_chunk_offset?;
119
120 let chunk_location = chunk_offset as u64 + u64::from(s_bucket_offset);
121
122 Some((maybe_record_chunk, chunk_location))
123 },
124 )
125 .flatten()
126 .collect::<Vec<_>>();
127
128 let sector_contents_map_size = SectorContentsMap::encoded_size(pieces_in_sector) as u64;
129 match sector {
130 ReadAt::Sync(sector) => {
131 read_chunks_inputs
132 .into_par_iter()
133 .zip(&pos_proofs.proofs)
134 .try_for_each(|((maybe_record_chunk, chunk_location), pos_proof)| {
135 let mut record_chunk = [0; RecordChunk::SIZE];
136 sector
137 .read_at(
138 &mut record_chunk,
139 sector_contents_map_size + chunk_location * RecordChunk::SIZE as u64,
140 )
141 .map_err(|error| ReadingError::FailedToReadChunk {
142 chunk_location,
143 error,
144 })?;
145
146 record_chunk =
148 Simd::to_array(Simd::from(record_chunk) ^ Simd::from(*pos_proof.hash()));
149
150 maybe_record_chunk.replace(RecordChunk::from(record_chunk));
151
152 Ok::<_, ReadingError>(())
153 })?;
154 }
155 ReadAt::Async(sector) => {
156 let processing_chunks = read_chunks_inputs
157 .into_iter()
158 .zip(&pos_proofs.proofs)
159 .map(
160 |((maybe_record_chunk, chunk_location), pos_proof)| async move {
161 let mut record_chunk = [0; RecordChunk::SIZE];
162 record_chunk.copy_from_slice(
163 §or
164 .read_at(
165 vec![0; RecordChunk::SIZE],
166 sector_contents_map_size
167 + chunk_location * RecordChunk::SIZE as u64,
168 )
169 .await
170 .map_err(|error| ReadingError::FailedToReadChunk {
171 chunk_location,
172 error,
173 })?,
174 );
175
176 record_chunk = Simd::to_array(
178 Simd::from(record_chunk) ^ Simd::from(*pos_proof.hash()),
179 );
180
181 maybe_record_chunk.replace(RecordChunk::from(record_chunk));
182
183 Ok::<_, ReadingError>(())
184 },
185 )
186 .collect::<FuturesUnordered<_>>()
187 .filter_map(|result| async move { result.err() });
188
189 std::pin::pin!(processing_chunks)
190 .next()
191 .await
192 .map_or(Ok(()), Err)?;
193 }
194 }
195
196 Ok(record_chunks)
197}
198
199pub fn recover_extended_record_chunks(
201 sector_record_chunks: &[Option<RecordChunk>; Record::NUM_S_BUCKETS],
202 piece_offset: PieceOffset,
203 erasure_coding: &ErasureCoding,
204) -> Result<Box<[RecordChunk; Record::NUM_S_BUCKETS]>, ReadingError> {
205 let mut recovered_sector_record_chunks = vec![[0u8; RecordChunk::SIZE]; Record::NUM_S_BUCKETS];
208 {
209 let (source_sector_record_chunks, parity_sector_record_chunks) =
210 sector_record_chunks.split_at(Record::NUM_CHUNKS);
211 let (source_recovered_sector_record_chunks, parity_recovered_sector_record_chunks) =
212 recovered_sector_record_chunks.split_at_mut(Record::NUM_CHUNKS);
213
214 let source = source_sector_record_chunks
215 .iter()
216 .zip(source_recovered_sector_record_chunks.iter_mut())
217 .map(
218 |(maybe_input_chunk, output_chunk)| match maybe_input_chunk {
219 Some(input_chunk) => {
220 output_chunk.copy_from_slice(input_chunk.as_slice());
221 RecoveryShardState::Present(input_chunk.as_slice())
222 }
223 None => RecoveryShardState::MissingRecover(output_chunk.as_mut_slice()),
224 },
225 );
226 let parity = parity_sector_record_chunks
227 .iter()
228 .zip(parity_recovered_sector_record_chunks.iter_mut())
229 .map(
230 |(maybe_input_chunk, output_chunk)| match maybe_input_chunk {
231 Some(input_chunk) => {
232 output_chunk.copy_from_slice(input_chunk.as_slice());
233 RecoveryShardState::Present(input_chunk.as_slice())
234 }
235 None => RecoveryShardState::MissingRecover(output_chunk.as_mut_slice()),
236 },
237 );
238 erasure_coding.recover(source, parity).map_err(|error| {
239 ReadingError::FailedToErasureDecodeRecord {
240 piece_offset,
241 error,
242 }
243 })?;
244 }
245
246 let record_chunks = recovered_sector_record_chunks
249 .into_iter()
250 .map(RecordChunk::from)
251 .collect::<Box<_>>();
252 let record_chunks = unsafe {
254 Box::from_raw(Box::into_raw(record_chunks).cast::<[RecordChunk; Record::NUM_S_BUCKETS]>())
255 };
256
257 Ok(record_chunks)
258}
259
260pub fn recover_source_record(
262 sector_record_chunks: &[Option<RecordChunk>; Record::NUM_S_BUCKETS],
263 piece_offset: PieceOffset,
264 erasure_coding: &ErasureCoding,
265) -> Result<Box<Record>, ReadingError> {
266 let mut recovered_record = Record::new_boxed();
268
269 let (source_sector_record_chunks, parity_sector_record_chunks) =
270 sector_record_chunks.split_at(Record::NUM_CHUNKS);
271 let source = source_sector_record_chunks
272 .iter()
273 .zip(recovered_record.iter_mut())
274 .map(
275 |(maybe_input_chunk, output_chunk)| match maybe_input_chunk {
276 Some(input_chunk) => {
277 output_chunk.copy_from_slice(input_chunk.as_slice());
278 RecoveryShardState::Present(input_chunk.as_slice())
279 }
280 None => RecoveryShardState::MissingRecover(output_chunk.as_mut_slice()),
281 },
282 );
283 let parity =
284 parity_sector_record_chunks
285 .iter()
286 .map(|maybe_input_chunk| match maybe_input_chunk {
287 Some(input_chunk) => RecoveryShardState::Present(input_chunk.as_slice()),
288 None => RecoveryShardState::MissingIgnore,
289 });
290 erasure_coding.recover(source, parity).map_err(|error| {
291 ReadingError::FailedToErasureDecodeRecord {
292 piece_offset,
293 error,
294 }
295 })?;
296
297 Ok(recovered_record)
298}
299
300pub(crate) async fn read_record_metadata<S, A>(
302 piece_offset: PieceOffset,
303 pieces_in_sector: u16,
304 sector: &ReadAt<S, A>,
305) -> Result<RecordMetadata, ReadingError>
306where
307 S: ReadAtSync,
308 A: ReadAtAsync,
309{
310 let sector_metadata_start = SectorContentsMap::encoded_size(pieces_in_sector) as u64
311 + sector_record_chunks_size(pieces_in_sector) as u64;
312 let record_metadata_offset =
314 sector_metadata_start + RecordMetadata::encoded_size() as u64 * u64::from(piece_offset);
315
316 let mut record_metadata_bytes = vec![0; RecordMetadata::encoded_size()];
317 match sector {
318 ReadAt::Sync(sector) => {
319 sector.read_at(&mut record_metadata_bytes, record_metadata_offset)?;
320 }
321 ReadAt::Async(sector) => {
322 record_metadata_bytes = sector
323 .read_at(record_metadata_bytes, record_metadata_offset)
324 .await?;
325 }
326 }
327 let record_metadata = RecordMetadata::decode(&mut record_metadata_bytes.as_ref())
328 .expect("Length is correct, contents doesn't have specific structure to it; qed");
329
330 Ok(record_metadata)
331}
332
333pub async fn read_piece<PosTable, S, A>(
338 piece_offset: PieceOffset,
339 sector_id: &SectorId,
340 sector_metadata: &SectorMetadataChecksummed,
341 sector: &ReadAt<S, A>,
342 erasure_coding: &ErasureCoding,
343 table_generator: &PosTable::Generator,
344) -> Result<Piece, ReadingError>
345where
346 PosTable: Table,
347 S: ReadAtSync,
348 A: ReadAtAsync,
349{
350 let pieces_in_sector = sector_metadata.pieces_in_sector;
351
352 let sector_contents_map = {
353 let mut sector_contents_map_bytes =
354 vec![0; SectorContentsMap::encoded_size(pieces_in_sector)];
355 match sector {
356 ReadAt::Sync(sector) => {
357 sector.read_at(&mut sector_contents_map_bytes, 0)?;
358 }
359 ReadAt::Async(sector) => {
360 sector_contents_map_bytes = sector.read_at(sector_contents_map_bytes, 0).await?;
361 }
362 }
363
364 SectorContentsMap::from_bytes(§or_contents_map_bytes, pieces_in_sector)?
365 };
366
367 let sector_record_chunks = read_sector_record_chunks(
368 piece_offset,
369 pieces_in_sector,
370 §or_metadata.s_bucket_offsets(),
371 §or_contents_map,
372 &table_generator.create_proofs(§or_id.derive_evaluation_seed(piece_offset)),
373 sector,
374 )
375 .await?;
376 let record = recover_source_record(§or_record_chunks, piece_offset, erasure_coding)?;
378
379 let RecordMetadata {
380 piece_header,
381 piece_checksum,
382 } = read_record_metadata(piece_offset, pieces_in_sector, sector).await?;
383
384 let mut piece = Piece::default();
385
386 piece.header = piece_header;
387 piece.record.copy_from_slice(&**record);
390
391 let actual_checksum = Blake3Hash::from(blake3::hash(piece.as_ref()));
393 if actual_checksum != piece_checksum {
394 debug!(
395 ?sector_id,
396 %piece_offset,
397 %actual_checksum,
398 expected_checksum = %piece_checksum,
399 "Hash doesn't match, plotted piece is corrupted"
400 );
401
402 return Err(ReadingError::ChecksumMismatch);
403 }
404
405 Ok(piece.to_shared())
406}