1#[cfg(test)]
10mod tests;
11
12use ab_core_primitives::checksum::Blake3Checksummed;
13use ab_core_primitives::hashes::Blake3Hash;
14use ab_core_primitives::pieces::{PieceHeader, PieceOffset, Record};
15use ab_core_primitives::sectors::{SBucket, SectorIndex};
16use ab_core_primitives::segments::HistorySize;
17use ab_direct_io_file::AlignedPage;
18use ab_io_type::trivial_type::TrivialType;
19use parity_scale_codec::{Decode, Encode};
20use rayon::prelude::*;
21use std::mem::MaybeUninit;
22use std::ops::{Deref, DerefMut};
23use thiserror::Error;
24use tracing::debug;
25
26#[inline]
30pub const fn sector_record_chunks_size(pieces_in_sector: u16) -> usize {
31 pieces_in_sector as usize * Record::SIZE
32}
33
34#[inline]
38pub const fn sector_record_metadata_size(pieces_in_sector: u16) -> usize {
39 pieces_in_sector as usize * RecordMetadata::encoded_size()
40}
41
42#[inline]
46pub const fn sector_padding_size(pieces_in_sector: u16) -> usize {
47 let unpadded_size = sector_record_chunks_size(pieces_in_sector)
48 + sector_record_metadata_size(pieces_in_sector)
49 + SectorContentsMap::encoded_size(pieces_in_sector)
50 + Blake3Hash::SIZE;
51
52 unpadded_size.next_multiple_of(AlignedPage::SIZE) - unpadded_size
53}
54
55#[inline]
64pub const fn sector_size(pieces_in_sector: u16) -> usize {
65 sector_record_chunks_size(pieces_in_sector)
66 + sector_record_metadata_size(pieces_in_sector)
67 + SectorContentsMap::encoded_size(pieces_in_sector)
68 + sector_padding_size(pieces_in_sector)
69 + Blake3Hash::SIZE
70}
71
72#[derive(Debug, Encode, Decode, Clone)]
74pub struct SectorMetadata {
75 pub sector_index: SectorIndex,
77 pub pieces_in_sector: u16,
79 pub s_bucket_sizes: Box<[u16; Record::NUM_S_BUCKETS]>,
81 pub history_size: HistorySize,
84}
85
86impl SectorMetadata {
87 pub fn s_bucket_offsets(&self) -> Box<[u32; Record::NUM_S_BUCKETS]> {
89 let s_bucket_offsets = self
90 .s_bucket_sizes
91 .iter()
92 .map({
93 let mut base_offset = 0;
94
95 move |s_bucket_size| {
96 let offset = base_offset;
97 base_offset += u32::from(*s_bucket_size);
98 offset
99 }
100 })
101 .collect::<Box<_>>();
102
103 assert_eq!(s_bucket_offsets.len(), Record::NUM_S_BUCKETS);
104 unsafe {
106 Box::from_raw(Box::into_raw(s_bucket_offsets).cast::<[u32; Record::NUM_S_BUCKETS]>())
107 }
108 }
109}
110
111#[derive(Debug, Clone, Encode, Decode)]
113pub struct SectorMetadataChecksummed(Blake3Checksummed<SectorMetadata>);
114
115impl From<SectorMetadata> for SectorMetadataChecksummed {
116 #[inline]
117 fn from(value: SectorMetadata) -> Self {
118 Self(Blake3Checksummed(value))
119 }
120}
121
122impl Deref for SectorMetadataChecksummed {
123 type Target = SectorMetadata;
124
125 #[inline]
126 fn deref(&self) -> &Self::Target {
127 &self.0.0
128 }
129}
130
131impl DerefMut for SectorMetadataChecksummed {
132 #[inline]
133 fn deref_mut(&mut self) -> &mut Self::Target {
134 &mut self.0.0
135 }
136}
137
138impl SectorMetadataChecksummed {
139 #[inline]
146 pub const fn disk_size() -> usize {
147 Self::encoded_size().next_multiple_of(AlignedPage::SIZE)
148 }
149
150 pub fn encode_for_disk(&self) -> Box<[u8; Self::disk_size()]> {
153 let mut bytes = unsafe { Box::<[u8; Self::disk_size()]>::new_zeroed().assume_init() };
155 self.encode_to(&mut bytes.as_mut_slice());
156 bytes
157 }
158
159 pub fn decode_from_disk(
162 bytes: &[u8; Self::disk_size()],
163 ) -> Result<Self, parity_scale_codec::Error> {
164 Self::decode(&mut bytes.as_slice())
165 }
166
167 const fn encoded_size() -> usize {
169 SectorIndex::SIZE
171 + size_of::<u16>()
172 + size_of::<[u16; Record::NUM_S_BUCKETS]>()
173 + size_of::<HistorySize>()
174 + Blake3Hash::SIZE
175 }
176}
177
178#[derive(Debug, Default, Copy, Clone, Encode, Decode)]
180#[repr(C)]
181pub(crate) struct RecordMetadata {
182 pub(crate) piece_header: PieceHeader,
184 pub(crate) piece_checksum: Blake3Hash,
186}
187
188const {
189 assert!(align_of::<RecordMetadata>() == 1);
190}
191
192impl RecordMetadata {
193 pub(crate) const fn encoded_size() -> usize {
194 size_of::<Self>()
195 }
196}
197
198#[derive(Debug, Clone)]
200pub(crate) struct RawSector {
201 pub(crate) records: Vec<Record>,
203 pub(crate) metadata: Vec<RecordMetadata>,
205}
206
207impl RawSector {
208 pub(crate) fn new(pieces_in_sector: u16) -> Self {
210 Self {
211 records: Record::new_zero_vec(usize::from(pieces_in_sector)),
212 metadata: vec![RecordMetadata::default(); usize::from(pieces_in_sector)],
213 }
214 }
215}
216
217pub type FoundProofs = [u8; Record::NUM_S_BUCKETS / u8::BITS as usize];
225
226#[derive(Debug, Error, Copy, Clone, Eq, PartialEq)]
228pub enum SectorContentsMapFromBytesError {
229 #[error("Invalid bytes length, expected {expected}, actual {actual}")]
231 InvalidBytesLength {
232 expected: usize,
234 actual: usize,
236 },
237 #[error("Checksum mismatch")]
239 ChecksumMismatch,
240}
241
242#[derive(Debug, Error, Copy, Clone, Eq, PartialEq)]
244pub enum SectorContentsMapEncodeIntoError {
245 #[error("Invalid bytes length, expected {expected}, actual {actual}")]
247 InvalidBytesLength {
248 expected: usize,
250 actual: usize,
252 },
253}
254
255#[derive(Debug, Error, Copy, Clone, Eq, PartialEq)]
257pub enum SectorContentsMapIterationError {
258 #[error("S-bucket provided {provided} is out of range, max {max}")]
260 SBucketOutOfRange {
261 provided: usize,
263 max: usize,
265 },
266}
267
268#[derive(Debug, Clone, Eq, PartialEq)]
274pub struct SectorContentsMap {
275 record_chunks_used: Vec<FoundProofs>,
278}
279
280impl SectorContentsMap {
281 pub fn new(pieces_in_sector: u16) -> Self {
284 Self {
285 record_chunks_used: vec![[0; _]; usize::from(pieces_in_sector)],
286 }
287 }
288
289 pub fn from_bytes(
294 bytes: &[u8],
295 pieces_in_sector: u16,
296 ) -> Result<Self, SectorContentsMapFromBytesError> {
297 if bytes.len() != Self::encoded_size(pieces_in_sector) {
298 return Err(SectorContentsMapFromBytesError::InvalidBytesLength {
299 expected: Self::encoded_size(pieces_in_sector),
300 actual: bytes.len(),
301 });
302 }
303
304 let (single_records_bit_arrays, expected_checksum) =
305 bytes.split_at(bytes.len() - Blake3Hash::SIZE);
306 let expected_checksum = Blake3Hash::from_bytes_exact(expected_checksum)
307 .expect("Correct size, no alignment requirements; qed");
308 let actual_checksum = Blake3Hash::from(blake3::hash(single_records_bit_arrays));
310 if &actual_checksum != expected_checksum {
311 debug!(
312 %actual_checksum,
313 %expected_checksum,
314 "Hash doesn't match, corrupted bytes"
315 );
316
317 return Err(SectorContentsMapFromBytesError::ChecksumMismatch);
318 }
319
320 let record_chunks_used = single_records_bit_arrays.as_chunks().0.to_vec();
322
323 Ok(Self { record_chunks_used })
324 }
325
326 pub const fn encoded_size(pieces_in_sector: u16) -> usize {
329 size_of::<FoundProofs>() * pieces_in_sector as usize + Blake3Hash::SIZE
330 }
331
332 pub fn encode_into<'a>(
336 &self,
337 output: &'a mut [MaybeUninit<u8>],
338 ) -> Result<&'a mut [u8], SectorContentsMapEncodeIntoError> {
339 if output.len() != Self::encoded_size(self.record_chunks_used.len() as u16) {
340 return Err(SectorContentsMapEncodeIntoError::InvalidBytesLength {
341 expected: Self::encoded_size(self.record_chunks_used.len() as u16),
342 actual: output.len(),
343 });
344 }
345
346 let slice = self.record_chunks_used.as_flattened();
347 let (data_output, checksum_output) = output.split_at_mut(slice.len());
349 data_output.write_copy_of_slice(slice);
350 checksum_output.write_copy_of_slice(blake3::hash(slice).as_bytes());
351
352 Ok(unsafe { output.assume_init_mut() })
354 }
355
356 pub fn iter_record_chunks_used(&self) -> &[FoundProofs] {
358 &self.record_chunks_used
359 }
360
361 pub fn iter_record_chunks_used_mut(&mut self) -> &mut [FoundProofs] {
363 &mut self.record_chunks_used
364 }
365
366 pub fn s_bucket_sizes(&self) -> Box<[u16; Record::NUM_S_BUCKETS]> {
368 let s_bucket_sizes = (u16::from(SBucket::ZERO)..=u16::from(SBucket::MAX))
370 .into_par_iter()
371 .map(SBucket::from)
372 .map(|s_bucket| {
373 self.iter_s_bucket_piece_offsets(s_bucket)
374 .expect("S-bucket guaranteed to be in range; qed")
375 .count() as u16
376 })
377 .collect::<Box<_>>();
378
379 assert_eq!(s_bucket_sizes.len(), Record::NUM_S_BUCKETS);
380
381 unsafe {
383 Box::from_raw(Box::into_raw(s_bucket_sizes).cast::<[u16; Record::NUM_S_BUCKETS]>())
384 }
385 }
386
387 pub fn iter_record_chunk_to_plot(
391 &self,
392 piece_offset: PieceOffset,
393 ) -> impl Iterator<Item = (SBucket, usize)> + '_ {
394 (SBucket::ZERO..=SBucket::MAX)
396 .flat_map(|s_bucket| {
398 self.iter_s_bucket_piece_offsets(s_bucket)
399 .expect("S-bucket guaranteed to be in range; qed")
400 .map(move |current_piece_offset| (s_bucket, current_piece_offset))
401 })
402 .enumerate()
405 .filter_map(move |(chunk_location, (s_bucket, current_piece_offset))| {
407 (current_piece_offset == piece_offset).then_some((s_bucket, chunk_location))
409 })
410 .take(Record::NUM_CHUNKS)
412 }
413
414 pub fn par_iter_record_chunk_to_plot(
421 &self,
422 piece_offset: PieceOffset,
423 ) -> impl IndexedParallelIterator<Item = Option<usize>> + '_ {
424 let piece_offset = usize::from(piece_offset);
425 (u16::from(SBucket::ZERO)..=u16::from(SBucket::MAX))
426 .into_par_iter()
427 .map(SBucket::from)
428 .map(move |s_bucket| {
430 let byte_offset = usize::from(s_bucket) / u8::BITS as usize;
431 let bit_mask = 1 << (usize::from(s_bucket) % u8::BITS as usize);
432
433 if self.record_chunks_used[piece_offset][byte_offset] & bit_mask == 0 {
434 return None;
435 }
436
437 let chunk_offset = self
440 .record_chunks_used
441 .iter()
442 .take(piece_offset)
443 .filter(move |record_chunks_used| {
444 record_chunks_used[byte_offset] & bit_mask != 0
445 })
446 .count();
447
448 Some(chunk_offset)
449 })
450 }
451
452 pub fn iter_s_bucket_piece_offsets(
456 &self,
457 s_bucket: SBucket,
458 ) -> Result<impl Iterator<Item = PieceOffset> + '_, SectorContentsMapIterationError> {
459 let s_bucket = usize::from(s_bucket);
460
461 if s_bucket >= Record::NUM_S_BUCKETS {
462 return Err(SectorContentsMapIterationError::SBucketOutOfRange {
463 provided: s_bucket,
464 max: Record::NUM_S_BUCKETS,
465 });
466 }
467
468 Ok((PieceOffset::ZERO..)
469 .zip(&self.record_chunks_used)
470 .filter_map(move |(piece_offset, record_chunks_used)| {
471 let byte_offset = s_bucket / u8::BITS as usize;
472 let bit_mask = 1 << (s_bucket % u8::BITS as usize);
473
474 (record_chunks_used[byte_offset] & bit_mask != 0).then_some(piece_offset)
475 }))
476 }
477
478 pub fn iter_s_bucket_used_record_chunks_used(
484 &self,
485 s_bucket: SBucket,
486 ) -> Result<impl Iterator<Item = bool> + '_, SectorContentsMapIterationError> {
487 let s_bucket = usize::from(s_bucket);
488
489 if s_bucket >= Record::NUM_S_BUCKETS {
490 return Err(SectorContentsMapIterationError::SBucketOutOfRange {
491 provided: s_bucket,
492 max: Record::NUM_S_BUCKETS,
493 });
494 }
495
496 Ok(self
497 .record_chunks_used
498 .iter()
499 .map(move |record_chunks_used| {
500 let byte_offset = s_bucket / u8::BITS as usize;
501 let bit_mask = 1 << (s_bucket % u8::BITS as usize);
502
503 record_chunks_used[byte_offset] & bit_mask != 0
504 }))
505 }
506}